Bash script -e not detecting filename in a variable - linux

In a BASH script, I'm trying to detect whether a file exists. The filename is in a variable but the -e command seems to be unable to detect the file. The following code always outputs "~/misc/tasks/drupal_backup.sh does not exist"
filename="~/misc/tasks/drupal_backup.sh"
if [ -e "$filename" ]; then
echo "$filename exists"
else
echo "$filename does not exist"
fi
On the other hand, the following code detects the file correctly:
if [ -e ~/misc/tasks/drupal_backup.sh ]; then
echo "$filename exists"
else
echo "$filename does not exist"
fi
Why would this be? How can I get it to detect the file when the filename is in a variable?

That's an interesting one. Substituting $HOME for ~ works as does removing the quotes from the assignment.
If you put a set -x at the top of that script, you'll see that the version with quotes sets filename to ~/... which is what's given to -e. If you remove the quotes, filename is set to the expanded /home/somebody/.... So in the first case, you see:
+ [ -e ~/... ]
and it doesn't like it. In the second case, you see:
+ [ -e /home/somebody/... ]
and it does work.
If you do it without the variable, you see:
+ [ -e /home/somebody/... ]
and, yes, it works.
After a bit of investigation, I've found that it's actually the order in which bash performs its expansions. From the bash man page:
The order of expansions is: brace expansion, tilde expansion, parameter, variable and arithmetic expansion and command substitution (done in a left-to-right fashion), word splitting, and pathname expansion.
That's why it's not working, the variable is substituted after the tilde expansion. In other words, at the point where bash wants to expand ~, there isn't one. It's only after variable expansion does the word get changed into ~/... and there will be no tilde expansion after that.
One thing you could do is to change your if statement to:
if [[ -e $(eval echo $filename) ]]; then
This will evaluate the $filename argument twice. The first time (with eval), there will be no ~ during the tilde expansion phase but $filename will be changed to ~/... during the variable expansion phase.
Then, on the second evaluation (the one being done as part of the if itself), the ~ will be there during the tilde expansion phase.
I've tested that on my .profile file and it seems to work, I suggest you confirm in your particular case.

Related

Expanding the path stored in a variable [duplicate]

Say I have a folder called Foo located in /home/user/ (my /home/user also being represented by ~).
I want to have a variable
a="~/Foo" and then do
cd $a
I get
-bash: cd: ~/Foo: No such file or directory
However if I just do cd ~/Foo it works fine. Any clue on how to get this to work?
You can do (without quotes during variable assignment):
a=~/Foo
cd "$a"
But in this case the variable $a will not store ~/Foo but the expanded form /home/user/Foo. Or you could use eval:
a="~/Foo"
eval cd "$a"
You can use $HOME instead of the tilde (the tilde is expanded by the shell to the contents of $HOME).
Example:
dir="$HOME/Foo";
cd "$dir";
Although this question is merely asking for a workaround, this is listed as the duplicate of many questions that are asking why this happens, so I think it's worth giving an explanation. According to https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_06:
The order of word expansion shall be as follows:
Tilde expansion, parameter expansion, command substitution, and arithmetic expansion shall be performed, beginning to end.
When the shell evaluates the string cd $a, it first performs tilde expansion (which is a no-op, since $a does not contain a tilde), then it expands $a to the string ~/Foo, which is the string that is finally passed as the argument to cd.
A much more robust solution would be to use something like sed or even better, bash parameter expansion:
somedir="~/Foo/test~/ing";
cd "${somedir/#\~/$HOME}"
or if you must use sed,
cd $(echo "$somedir" | sed "s#^~#$HOME#")
If you use double quotes the ~ will be kept as that character in $a.
cd $a will not expand the ~ since variable values are not expanded by the shell.
The solution is:
eval "cd $a"

How to start a custom binary with options and arguments from a shell script

I somehow can't figure out how to start a self-written programm with arguments from a shell script. If I'm in a folder whose parent folder contains the binary, then I can start the program with
$ ../binary --opt1 arg1 --opt2 arg2
Now, say the arguments and options are listed in a file args in the current folder.
args.txt:
--opt1 arg1 --opt2 arg2
If I'm trying to execute the binary from a shell script in the current folder like:
$ ./script.sh args.txt
script.sh:
#!/bin/bash
if [ $# != 1 ]
then
exit 1;
fi
params=$(<"$1")
../binary "$params"
# ../binary <<<"$params" doesn't work either.
How can I make this work?
Edit (updated script):
#!/bin/bash
if [ $# != 1 ]
then
exit 1;
fi
params=$(<"$1")
START=$(date +%s)
../binary "$params"
# ../binary <<<"$params" doesn't work either.
END=$(date +%s)
DIFF=$(( $END - $START ))
echo "Test took $DIFF seconds"
Use command substituion:
$ ./script.sh args.txt
content of ./script.sh
#!/bin/bash
../binary $(< "$1")
Command Substitution
Command substitution allows the output of a command to replace the command name. There are two forms:
$(command)
or
`command`
Bash performs the expansion by executing command and replacing the command substitution with the standard output of the command, with any trailing newlines deleted. Embedded newlines are not deleted, but they may be removed during word splitting. The command substitution $(cat file) can be replaced by the equivalent but faster $(< file).
When the old-style backquote form of substitution is used, backslash retains its literal meaning except when followed by $, `, or \. The first back‐quote not preceded by a backslash terminates the command substitution. When using the $(command) form, all characters between the parentheses make up the command; none are treated specially.
Command substitutions may be nested. To nest when using the backquoted form, escape the inner backquotes with backslashes. If the substitution appears within double quotes, word splitting and pathname expansion are not performed on the results.

Read file in bash script with loop

Given file socat.conf
AUTOSTART=default
SOCAT_default="TCP4-LISTEN:3724,nodelay,fork,reuseaddr,su=nobody TCP4:your.wow.server.ip.address:3724,nodelay"
The relevant part of the bash script that reads this file:
[ ! -f /etc/default/socat.conf ] || . /etc/default/socat.conf
start () {
echo "Starting $DESC:"
maxfds
umask 027
cd /tmp
if test "x$AUTOSTART" = "xnone" -o -z "x$AUTOSTART" ; then
echo "Autostart disabled."
exit 0
fi
for NAME in $AUTOSTART ; do
ARGS=`eval echo \\\$SOCAT_$NAME`
echo $ARGS
start_socat
echo " $NAME $ARGS"
done
return $?
}
For the full file see here: https://blog.bentrax.de/2009/08/26/socat-start-automatisieren-und-iptables-regeln-laden/
My question is, how can I add another command to socat.conf? I tried with
AUTOSTART=default,another
SOCAT_default="TCP4-LISTEN:3724,nodelay,fork,reuseaddr,su=nobody TCP4:your.wow.server.ip.address:3724,nodelay"
SOCAT_another="..."
However this did not work. I am not very familiar with bash scripts to understand the for NAME in $AUTOSTART loop. I think the answer lays there. Any ideas?
The for NAME in $AUTOSTART works by splitting $AUTOSTART into words using the environmental variable $IFS as delimiters (default is space, tab and newline). Each word in turn is then stored in $NAME and processed within the loop until no words remain.
The solution to your problem, then, is to separate your words using spaces (or tabs, or newlines..):
AUTOSTART="default another"
The double quotes are necessary, otherwise it will be read as two separate commands, AUTOSTART=default and another (again because of word-splitting using IFS).

bash outputs star and quotation mark incorrectly

so I have this bash function:
function xyz(){
echo $#
}
now when I run
xyz whaat "loool * hahhaa"
instead of echoing whaat "loool * hahhaa"
it insteads echoes:
whaat loool all default hahhaa
First of all the quotation marks got stripped off.
Secondly the * got replaced by "all default" . This is because the current directory has 2 folders called "all" and "default" hence it think * refers to all directory in the current directory
Is there a way to modify my function so that the output becomes the intended whaat "loool * hahhaa" accordingly (including the quotation marks and *)
I tried doing ${#} "$#" and "${#}" to no avail
The quotation marks are really gone, so don't be expecting anything to put them back. Bash removed them as part of parsing the command line (search for "quote removal" in man bash).
However, either echo "$#" or echo "${#}" will avoid the glob expansion (replacing * with the directory listing).
You practically never want $#. Best to get in the habit of writing "$#".
If you want to see a quoted version of the arguments, you can a bash-extension to printf:
xyz() {
printf "%q " "$#"
printf "\n"
}

Search log file for string with bash script

I just started learning PHP. I'm following phpacademy's tutorials which I would recommend to anyone. Anyways, I'm using XAMPP to test out my scripts. I'm trying to write a bash script that will start XAMPP and then open firefox to the localhost page if it finds a specific string, "XAMPP for Linux started.", that has been redirected from the terminal to the file xampp.log. I'm having a problem searching the file. I keep getting a:
grep: for: No such file or directory
I know the file exists, I think my syntax is wrong. This is what I've got so far:
loaded=$false
string="XAMPP for Linux started."
echo "Starting Xampp..."
sudo /opt/lampp/lampp start 2>&1 > ~/Documents/xampp.log
sleep 15
if grep -q $string ~/Documents/xampp.log; then
$loaded=$true
echo -e "\nXampp successfully started!"
fi
if [$loaded -eq $true]; then
echo -e "Opening localhost..."
firefox "http://localhost/"
else
echo -e "\nXampp failed to start."
echo -e "\nHere's what went wrong:\n"
cat ~/Documents/xampp.log
fi
In shell scripts you shouldn't write $variable, since that will do word expansion on the variable's value. In your case, it results in four words.
Always use quotes around the variables, like this:
grep -e "$string" file...
The -e is necessary when the string might start with a dash, and the quotes around the string keep it as one word.
By the way: when you write shell programs, the first line should be set -eu. This enables *e*rror checking and checks for *u*ndefined variables, which will be useful in your case. For more details, read the Bash manual.
You are searching for a string you should put wihtin quotes.
Try "$string" instead of $string
There are a couple of problems:
quote variables if you want to pass them as a simple argument "$string"
there is no $true and $false
bash does variable expansion, it substitutes variable names with their values before executing the command. $loaded=$true should be loaded=true.
you need spaces and usually quotes in the if: if [$loaded -eq $true] if [ "$loaded" -eq true ]. in this case the variable is set so it won't cause problems but in general don't rely on that.

Resources