linuxshelltilde-expansion

Why is a tilde in a path not expanded in a shell script?


I tried to get the Android Studio launcher (studio.sh) to use my manually installed Java (not the system-wide default Java). Since I already declared PATH and JAVA_HOME in my .bashrc file, I simply sourced that file in the shell script:

. /home/foobar/.bashrc

but for some reason, $JAVA_HOME/bin/java was still not recognized as an executable file by the script.

I added some logging and found out that JAVA_HOME was expanded as ~/install/java..., i.e. the tilde operator was not expanded into the home directory.

I did some searching, but couldn't find any reason why it was not expanded. Is tilde a Bash-specific feature (the script uses #!/bin/sh, and Linux Mint uses dash, not bash)? Does tilde not work in some circumstances?

I replaced ~ with $HOME in my .bashrc declaration, and then it worked, so HOME is known at runtime.


Solution

  • In the bash manual, note that brace expansion occurs during parameter substitution, but not recursively:

    The order of expansions is: brace expansion; tilde expansion, parameter and variable expansion, arithmetic expansion, and command substitution (done in a left-to-right fashion); word splitting; and filename expansion.

    The POSIX standard supplied by The Open Group lists this behavior in section 2.6 as well.

    This implies that any tilde (or parameter references or command substitution) stored unexpanded in a bash variable will not automatically resolve. Your JAVA_HOME variable contains a literal tilde, so bash will not expand it automatically.

    It is likely that your fix worked because tilde expansion does not apply in quotes:

    $ echo "~"
    ~
    $ echo ~
    /home/jeffbowman
    

    ...but parameter expansion like $HOME does occur in quotes. Replacing it with $HOME expands to your home directory during the assignment of JAVA_HOME. Remember that quotes in bash can start mid-word.

    FOO=~/bar             # stores /home/jeffbowman/bar
    FOO=~jeffbowman/bar   # stores /home/jeffbowman/bar
    FOO=~"jeffbowman"/bar # stores ~jeffbowman/bar
    FOO=~"/bar"           # stores ~/bar
    FOO="~/bar"           # stores ~/bar
    FOO=$HOME/bar         # stores /home/jeffbowman/bar
    FOO="$HOME/bar"       # stores /home/jeffbowman/bar
    

    Though the better option is to ensure your assignment is correct, if you want to expand it manually, these SO questions have some good options:

    Note that it's not only the quoting status of the tilde itself that is pertinent: all characters up to the first unquoted slash (should one exist) are considered a "tilde-prefix", and only if none of the characters in that prefix were quoted is expansion as a login name considered.