27

My bash script receives a filename (or relative path) as a string, but must then read from that file. I can only read from a filename if I declare it as a literal directly in the script (without quotes)...which is impossible for arguments since they are implicitly strings to begin with. Observe:

a="~/test.txt"
#Look for it
if [[ -a $a ]] ; then
    echo "A Found it"
else
    echo "A Error"
fi
#Try to use it
while read line; do
    echo $line
done < $a

b='~/test.txt'
#Look for it
if [[ -a $b ]] ; then
    echo "B Found it"
else
    echo "B Error"
fi
#Try to use it
while read line; do
    echo $line
done < $b

c=~/test.txt
#Look for it
if [[ -a $c ]] ; then
    echo "C Found it"
else
    echo "C Error"
fi
#Try to use it
while read line; do
    echo $line
done < $c

YIELDS:

A Error
./test.sh: line 10: ~/test.txt: No such file or directory
B Error
./test: line 12: ~/test.txt: No such file or directory
C Found it
Hello

As stated above, I can't pass a command line argument to the routines above since I get the same behavior that I get on the quoted strings.

Keith Wiley
  • 593
  • 2
  • 5
  • 14
  • 4
    "~/test.txt" and '~/test.txt' stop expansion of the ~ into your home directory. ~/test.txt works because it is unquoted. Stop using the ~ notation or stop using quotes.... – jim mcnamara May 23 '13 at 00:20
  • 1
    If you just use the script's command line argument (`$1`), then everything will work fine because the home directory expansion will have already been done before the script is called. – rici May 23 '13 at 01:45
  • @rici Yes, provided that the `~` is not quoted when his script is called from the command line or from another script. It's a different story when his script is called from some other program that passes _a literal_ `~` That would be a mistake that should be fixed in the other program; but if he want's to deal with such a case in his own script, he probably needs `eval`. – Uwe May 23 '13 at 06:15

2 Answers2

49

This is part of the rules of ~-expansion. It is clearly stated in the Bash manual that this expansion is not performed when the ~ is quoted.

Workaround 1

Don't quote the ~.

file=~/path/to/file

If you need to quote the rest of the filename:

file=~/"path with spaces/to/file"

(This is perfectly legal in a garden-variety shell.)

Workaround 2

Use $HOME instead of ~.

file="$HOME/path/to/file"

BTW: Shell variable types

You seem to be a little confused about the types of shell variables.

Everything is a string.

Repeat until it sinks in: Everything is a string. (Except integers, but they're mostly hacks on top of strings AFAIK. And arrays, but they're arrays of strings.)

This is a shell string: "foo". So is "42". So is 42. So is foo. If you don't need to quote things, it's reasonable not to; who wants to type "ls" "-la" "some/dir"?

  • 2
    `file=~"/path with spaces/to/file"` does not work too. It is necessary to write `file=~/"path with spaces/to/file"` – sercxjo Nov 07 '16 at 10:47
  • 1
    @sercxjo for me it works on zsh and doesn't work on bash. I.e. `~/"` is more portable. – Nick Volynkin Nov 07 '16 at 10:51
  • 1
    @sercxjo (and Nick) fixed – michaelb958--GoFundMonica Nov 07 '16 at 11:16
  • Personally I'd flip these recommendations - using `$HOME` is much more consistent and intuitive. `~` should be treated as a convenience only where it works. Jumping through hoops like taking advantage of quoting semantics may "work" but it's brittle and confusing. – dimo414 Feb 23 '19 at 18:48
  • Just to re-highlight the actual issue as stated above 'This is part of the rules of ~-expansion. It is clearly stated in the Bash manual that this expansion is not performed when the ~ is quoted' OP can you maybe put in big text!! @michaelb958-gofundmonica my fix unixpath=${unixpath/\~/$HOME} – zzapper Dec 03 '20 at 14:32
1

Still not as glamorous, but worked for me:

home_folder="/home/$(logname)"

can work from there...

Imran Rafiq Rather
  • 7,068
  • 1
  • 14
  • 32