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.