I am working on creating a simple script to make it easy to set up a virtual host on Apache server. Currently I don't seem to be able to write the config file because it's in a variable. How do I get around it. Here is my code that does not work.
siteConf="/etc/apache2/sites-available/$domain.conf"
echo "creating conf file"
echo "<VirtualHost *:80>" >> $siteConf
echo "ServerName *.$domain" >> $siteConf
echo "DocumentRoot $publicHtmlLoc" >> $siteConf
echo "DirectoryIndex index.php" >> $siteConf
echo "ServerAlias $database.newphp.junglecoders.dk" >> $siteConf
echo "</VirtualHost>" >> $siteConf
I am running the script with the bash command.
Edit: The Error i get is this
$siteConf: ambiguous redirect
Domain comes from here:
echo "Write websiter url example.com, no sub dir allowed"
read -p "Name: " domain
As suggested in comments its the path that is wrong, i tried to echo out the variable and i can see it has removed the '.' chars from some reason and left a space instead, why would the script do that ?
Edit2:
Was using IFS earlier in the script, to split the the domain name
The code looks as if it should work. You might do better with
{
echo "<VirtualHost …>"
…
echo "</VirtualHost>"
} > $siteConf
(or >> $siteConf if you really want to add to the existing file). That does a single redirection for all the output, and truncates the file. It is also a good idea as a general rule to enclose uses of variables in double quotes:
} > "$siteConf"
Basic debugging for shell scripts:
What do you get from bash -x your-script.sh?
You get 'ambiguous redirect' when the variable named doesn't exist or is empty, or if it expands to two or more words. That suggests that the first line of your script isn't an accurate representation of what you've got, but it is hard to guess how you've got it wrong. Misspelled name, or an unwanted space are probably the most likely, but it could be something else.
Alright looks like I needed to wrap it like in quotes like in the answer Getting an 'ambiguous redirect' error that antak suggested.
That's a good question to cross-reference. At one point, this question was closed as a duplicate of it, but the issue here turned out to be about IFS being set, which is not an alternative source of trouble identified in that other question.
Have you gone messing with IFS at any point in the script?
Yes — been doing IFS for splitting the URL into parts.
Note that:
set_with_spaces="name1 name2"
echo Hi >> $set_with_spaces
yields
bash: $set_with_spaces: ambiguous redirect too.
Two names were generated from one variable.
$ IFS=.
$ domain=abc.def.ghi.jkl
$ echo $domain
abc def ghi jkl
$ echo "$domain"
abc.def.ghi.jkl
$ IFS=$' \t\n'
$ echo $domain
abc.def.ghi.jkl
$
If you have been messing with IFS, then its current value is probably altering how the names are being interpreted. Reinstate it to its default value (blank, tab, newline):
IFS=$' \t\n'
Related
I normally just browse around and not looking to post questions however I am currently learning how to shell script and I've fallen into a pickle.
At the moment I have a structure of:
Main
---Hub1
---Hub2
---Hub3
---Hub4
All the way to Hub20.
What i want the structure to be is:
Main
---Hub1
--------Notes_1.txt >> "The <file_name> belongs to <user> and was created in Hub1"
---Hub2
--------Notes_2.txt >> "The <file_name> belongs to <user> and was created in Hub2"
and so on.
At the moment my code is looking like this
i=1
until (($i>20))
filename="~/Main/Hub${i}/Notes_$i.txt"
do
touch ~/Main/Hub${i}/Notes_$i.txt
echo "The $(basename -- "$filename") belongs to $USER was created in the Hub${i}" >> ~/Main/Hub${i}.txt
((i++))
done
Its not doing what it needs to be doing and I just cant figure out what I am doing wrong. I am aware it's not formatted right. It may be a simply solution or maybe I am completely off. I want to get it working before I start moving it around. Any advice would be great. Thank you!
You can use a brace range instead of until.
Don't put the pathname in quotes, that prevents expanding ~.
You need $ before filename in the echo statement to expand the variable.
for i in {1..20}; do
filename=~/Main/Hub${i}/Notes_$i.txt
touch "$filename"
echo "The $(basename -- "$filename") belongs to $USER was created in the Hub${i}" >> ~/Main/Hub${i}.txt
done
I am having a problem where cmd1 works, but not cmd2 in my Bash script ending in .sh. I have made the Bash script executable.
Additionally, I can execute cmd2 just fine from my Bash terminal. I have tried to make a minimally reproducible example, but my larger goal is to run a complicated executable with command line arguments and pass output to a file that may or may not exist (rather than displaying the output in the terminal).
Replacing > with >> also gives the same error in the script, but not the terminal.
My Bash script:
#!/bin/bash
cmd1="cat test.txt"
cmd2="cat test.txt > a"
echo $cmd1
$cmd1
echo $cmd2
$cmd2
test.txt has the words "dog" and "cat" on two separate lines without quotes.
Short answer: see BashFAQ #50: I'm trying to put a command in a variable, but the complex cases always fail!.
Long answer: the shell expands variable references (like $cmd1) toward the end of the process of parsing a command line, after it's done parsing redirects (like > a is supposed to be) and quotes and escapes and... In fact, the only thing it does with the expanded value is word splitting (e.g. treating cat test.txt > a as "cat" followed by "test.txt", ">", and finally "a", rather than a single string) and wildcard expansion (e.g. if $cmd expanded to cat *.txt, it'd replace the *.txt part with a list of matching files). (And it skips word splitting and wildcard expansion if the variable is in double-quotes.)
Partly as a result of this, the best way to store commands in variables is: don't. That's not what they're for; variables are for data, not commands. What you should do instead, though, depends on why you were storing the command in a variable.
If there's no real reason to store the command in a variable, then just use the command directly. For conditional redirects, just use a standard if statement:
if [ -f a ]; then
cat test.txt > a
else
cat test.txt
fi
If you need to define the command at one point, and use it later; or want to use the same command over and over without having to write it out in full each time, use a function:
cmd2() {
cat test.txt > a
}
cmd2
It sounds like you may need to be able to define the command differently depending on some condition, you can actually do that with a function as well:
if [ -f a ]; then
cmd() {
cat test.txt > a
}
else
cmd() {
cat test.txt
}
fi
cmd
Alternately, you can wrap the command (without redirect) in a function, then use a conditional to control whether it redirects:
cmd() {
cat test.txt
}
if [ -f a ]; then
cmd > a
else
cmd
fi
It's also possible to wrap a conditional redirect into a function itself, then pipe output to it:
maybe_redirect_to() {
if [ -f "$1" ]; then
cat > "$1"
else
cat
fi
}
cat test.txt | maybe_redirect_to a
(This creates an extra cat process that isn't really doing anything useful, but if it makes the script cleaner, I'd consider that worth it. In this particular case, you could minimize the stray cats by using maybe_redirect_to a < test.txt.)
As a last resort, you can store the command string in a variable, and use eval to parse it. eval basically re-runs the shell parsing process from the beginning, meaning that it'll recognize things like redirects in the string. But eval has a well-deserved reputation as a bug magnet, because it's easy for it to treat parts of the string you thought were just data as command syntax, which can cause some really weird (& dangerous) bugs.
If you must use eval, at least double-quote the variable reference, so it runs through the parsing process just once, rather than sort-of-once-and-a-half as it would unquoted. Here's an example of what I mean:
cmd3="echo '5 * 3 = 15'"
eval "$cmd3"
# prints: 5 * 3 = 15
eval $cmd3
# prints: 5 [list of files in the current directory] 3 = 15
# ...unless there are any files with shell metacharacters in their names, in
# which case something more complicated might happen.
BashFAQ #50 discusses some other possible reasons and solutions. Note that the array approach will not work here, since arrays also get expanded after redirects are parsed.
If you pop an 'eval' in front of $cmd2 it should work as expected:
#!/bin/bash
cmd2="cat test.txt > a"
eval $cmd2
If you're not sure about the operation of a script you could always use the debug mode to see if you can determine the error.
bash -x scriptname
This will run the command and display the output of variable evaluations. Hopefully this will reveal any issues with syntax.
I am relatively new to linux and am trying to create a TikZ figure parsing a file. In order to do so I read in the file with a $%&-bash script containing the following statement
echo "\fill[color=blue] ($xp,$zp) circle (5pt);" >> $fout
this results in the following output
^Lill[color=blue] ($xp,$zp) circle (5pt);
Obviously echo escapes the \f and I did not find a way around it.
I have tried all options like "-e" "-n" and what have you, have tried all kinds of combinations of " ' etc, but to no avail.
I am stuck as so often with linux, but this time even google didn't help (OMG=Oh My Google!!!!!!!!).
echo should not do backslash escapes by default, unless -e is specified. You can try echo -E to force turning them off (in case you have aliased echo to echo -e or something).
Alternatively, try using single quotes (although now that I think about it, I don't see how it would help):
echo '\fill[color=blue] ('"$xp,$zp"') circle (5pt);' >> $fout
Cheers everyone :)
I'm trying to make a linux script. This script shall be called with one parameter, a file stored in my home directory. I can't seem to use
cat $var1 >> $1
So i have this variable $var1 and I want to save it in a file that does exist and its name is given in $1
Anyone help me please!
The cat command shows the contents of a file.
Unless the value of $var1 is a file that you want to 'copy' to $1, it won't work (probably gives a 'file not found' kind of error).
The easiest solution, I can think of, is to echo the variable:
echo "$var1" >> "$1"
As stated by #glglgl it is better to put the variable between double quotes. They prevent spaces messing up the command as they split a parameter into multiple parameters
Then you want to create a script that saves things in the file you indicate as a parameter.
file_name=$1 #you get the parameter
...do things...
echo "everything you've done" >> $file_name #case want to append
echo "everything you've done" > $file_name #case want to overwrite
I would like to copy the contents of a variable (here called var) into a file.
The name of the file is stored in another variable destfile.
I'm having problems doing this. Here's what I've tried:
cp $var $destfile
I've also tried the same thing with the dd command... Obviously the shell thought that $var was referring to a directory and so told me that the directory could not be found.
How do I get around this?
Use the echo command:
var="text to append";
destdir=/some/directory/path/filename
if [ -f "$destdir" ]
then
echo "$var" > "$destdir"
fi
The if tests that $destdir represents a file.
The > appends the text after truncating the file. If you only want to append the text in $var to the file existing contents, then use >> instead:
echo "$var" >> "$destdir"
The cp command is used for copying files (to files), not for writing text to a file.
echo has the problem that if var contains something like -e, it will be interpreted as a flag. Another option is printf, but printf "$var" > "$destdir" will expand any escaped characters in the variable, so if the variable contains backslashes the file contents won't match. However, because printf only interprets backslashes as escapes in the format string, you can use the %s format specifier to store the exact variable contents to the destination file:
printf "%s" "$var" > "$destdir"
None of the answers above work if your variable:
starts with -e
starts with -n
starts with -E
contains a \ followed by an n
should not have an extra newline appended after it
and so they cannot be relied upon for arbitrary string contents.
In bash, you can use "here strings" as:
cat <<< "$var" > "$destdir"
As noted in the comment by Ash below, #Trebawa's answer (formulated in the same room as mine!) using printf is a better approach than cat.
All of the above work, but also have to work around a problem (escapes and special characters) that doesn't need to occur in the first place: Special characters when the variable is expanded by the shell. Just don't do that (variable expansion) in the first place. Use the variable directly, without expansion.
Also, if your variable contains a secret and you want to copy that secret into a file, you might want to not have expansion in the command line as tracing/command echo of the shell commands might reveal the secret. Means, all answers which use $var in the command line may have a potential security risk by exposing the variable contents to tracing and logging of the shell.
For variables that are already exported, use this:
printenv var >file
That means, in case of the OP question:
printenv var >"$destfile"
Note: variable names are case sensitive.
Warning: It is not a good idea to export a variable just for the sake of printing it with printenv. If you have a non-exported script variable that contains a secret, exporting it will expose it to all future child processes (unless unexported, for example using export -n).
If I understood you right, you want to copy $var in a file (if it's a string).
echo $var > $destdir
When you say "copy the contents of a variable", does that variable contain a file name, or does it contain a name of a file?
I'm assuming by your question that $var contains the contents you want to copy into the file:
$ echo "$var" > "$destdir"
This will echo the value of $var into a file called $destdir. Note the quotes. Very important to have "$var" enclosed in quotes. Also for "$destdir" if there's a space in the name. To append it:
$ echo "$var" >> "$destdir"
you may need to edit a conf file in a build process:
echo "db-url-host=$POSTGRESQL_HOST" >> my-service.conf
You can test this solution with running before export POSTGRESQL_HOST="localhost"