This works given some fairly plausible assumptions, but it is far from obvious code (and isn't a one-liner, either):
# Working function - painful, but can you simplify any of it?
# NB: Assumes that ~user does not expand to a name with double spaces or
# tabs or newlines, etc.
expand_tilde()
{
case "$1" in
(\~) echo "$HOME";;
(\~/*) echo "$HOME/${1#\~/}";;
(\~[^/]*/*) local user=$(eval echo ${1%%/*})
echo "$user/${1#*/}";;
(\~[^/]*) eval echo ${1};;
(*) echo "$1";;
esac
}
# Test cases
name1="~/Documents/over enthusiastic"
name2="~crl/Documents/double spaced"
name3="/work/whiffle/two spaces are better than one"
expand_tilde "$name1"
expand_tilde "$name2"
expand_tilde "$name3"
expand_tilde "~"
expand_tilde "~/"
expand_tilde "~crl"
expand_tilde "~crl/"
# This is illustrative of the 'normal use' of expand_tilde function
x=$(expand_tilde "$name1")
echo "x=[$x]"
When run on my machine (where there is a user crl
), the output is:
/Users/jleffler/Documents/over enthusiastic
/Users/crl/Documents/double spaced
/work/whiffle/two spaces are better than one
/Users/jleffler
/Users/jleffler/
/Users/crl
/Users/crl/
x=[/Users/jleffler/Documents/over enthusiastic]
The function tilde_expansion
deals with the various cases separately and differently. The first clause deals with a value ~
and simply substitutes $HOME
. The second is a case of paranoia: ~/
is mapped to $HOME/
. The third deals with ~/anything
(including an empty 'anything'). The next case deals with ~user
. The catch-all *
deals with everything else.
Note that the code makes the (plausible) assumption that ~user
will not expand to a value containing any double spaces, nor any tabs or newlines (and possibly other space-like characters). If you have to deal with that, life is going to be hell.
Note the answer to chdir()
to home directory, which explains that POSIX requires ~
to expand to the current value of $HOME
, but ~user
expands to the value of the home directory from the password database.