views:

66

answers:

1

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?

+5  A: 

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.

paxdiablo
Thanks! Simply replacing the ~ with $HOME worked perfectly.
thornate
Philipp
@Phillipp, if you're going to make a blanket statement like "you should never use eval" in a meritocracy, you might want to think about backing it up with your reasoning :-)
paxdiablo
@pax "[breaks] if the home directory contains spaces" -Phillipp, looks like a reason to me and a correct one at that. Multiple `eval` passes are to be avoided because they require as much analysis as your answer did which will still break where filename is `"~/my file"`.
msw
@msw et al, eval doesn't break with spaces in the home directory. You can do: `filename="~/xyz"` followed by `HOME="a b c" eval echo $filename` and it will successfully output `a b c/xyz` as expected. Now `-e` might break but that's then a simple matter of using `"$(eval echo $filename)"` to ensure it's quoted.
paxdiablo