Home > Enterprise >  Testing for a file with a variable with `if [[ -f $foo ]]` doesn't work in bash
Testing for a file with a variable with `if [[ -f $foo ]]` doesn't work in bash

Time:12-28

After doing a touch ~/foo.baz, I want to run this simple code in my .bashrc:

bar='~/foo.baz'

echo "$bar"; ls -l "$bar"

if [[ -f ~/foo.baz ]] # This is the old code I want to refactor
then
    echo 'A: It works as expected.'
else
    echo "A: The file $bar is not there."
fi

if [[ -f "$bar" ]] # This should be the 'dynamic' replacement, but doesn't work as I wish.
then
    echo 'B: It works as expected.'
else
    echo "B: The file $bar is not there."
fi

It gives me

~/foo.baz
ls: cannot access '~/foo.baz': No such file or directory
A: It works as expected.
B: The file ~/foo.baz is not there.

Why is this the case? How can i use the [[ -f ... ]] syntax to check for files dynamically?


I already tried to change multiple things, such as [[ ... ]] to [ ... ] or "$bar" to $($bar), $bar, ``$bar` (with one front backtick less, I don't know how to format it) and combinations of them. Non brought any different result. I also looked into this Q&A and tried -e instead of -f among other ideas, none of which solved my issue. bash --version yields

GNU bash, version 5.1.16(1)-release (x86_64-pc-linux-gnu)

I expect there to be an answer somewhere, but I couldn't find much that was helpful or even solve my question. If this is a duplicate, please advise on how to look for it instead of just closing or providing a link.

CodePudding user response:

Learn how to quote properly in shell, it's very important :

"Double quote" every literal that contains spaces/metacharacters and every expansion: "$var", "$(command "$var")", "${array[@]}", "a & b". Use 'single quotes' for code or literal $'s: 'Costs $5 US', ssh host 'echo "$HOSTNAME"'. See
http://mywiki.wooledge.org/Quotes
http://mywiki.wooledge.org/Arguments
http://wiki.bash-hackers.org/syntax/words

$ cat file
bar=~/foo.baz
touch "$bar"

if [[ -f ~/foo.baz ]] 
then
    echo 'A: It works.'
else
    echo "A: The file $bar is not there."
fi

if [[ -f $bar ]] 
then
    echo 'B: It works.'
else
    echo "B: The file $bar is not there."
fi
$ bash file
A: It works.
B: It works.

CodePudding user response:

So I learned a lot and, as yolenoyer commented, the subtle difference of some single quotation marks ' being present or not was the source of my error. They make the ~ not be expanded correctly, aqn already commented pointing into this right direction and gniourf proposed some solutions to that, including

  • bar="$HOME/foo.baz" (probably the way to go)
  • bar=~/foo.baz (without quotes, other traps see below:)

if you ever replace part of this with something with a field separator in it, then it will break. Quoting the values of variables is probably a good thing to get into the habit of anyway.

  • Quoting [the] variable [bar=~/"foo.baz"] will also work but I think that is rather ugly.

(by Michael Hoffman, added formatting/applied info in italic by me)

Please consider this question where I have obtained the quoted answer by Michael Hoffman and more details about tilde expansion. His answer also showed me info "(bash) Tilde Expansion" (along with more information). Sanghyun Lee shows a questionable workaround there: bar=$(eval 'echo ~/foo.baz') (applied by me).

More reading on tilde expansion can be found in this Q/A.


It's also worth mentioning the hints of Gilles Quenot('s answer) about how to quote variables. But "whether or not $bar is quoted later - it should be quoted, but that's not related to this problem" - wasn't the real issue as Gordon Davisson commented. He was also the one pointing to this mentioned Q/A.

  •  Tags:  
  • bash
  • Related