Server Fault is a question and answer site for system and network administrators. Join them; it only takes a minute:

Sign up
Here's how it works:
  1. Anybody can ask a question
  2. Anybody can answer
  3. The best answers are voted up and rise to the top

I am writing a script that gets called when a user logs in and check if a certain folder exists or is a broken symlink. (This is on a Mac OS X system, but the question is purely bash).

It is not elegant, and it is not working, but right now it looks like this:


# Often users have a messed up cache folder -- one that was redirected
# but now is just a broken symlink.  This script checks to see if
# the cache folder is all right, and if not, deletes it
# so that the system can recreate it.

if [ "$USERNAME" == "" ] ; then
    echo "This script must be run at login!" >&2
    exit 1


cd $DIR || rm $DIR && echo "Removed misdirected Cache folder" && exit 0

echo "Cache folder was fine."

The crux of the problem is that the tilde expansion is not working as I'd like.

Let us say that I have a user named george, and that his home folder is /a/path/to/georges_home. If, at a shell, I type:

cd ~george

it takes me to the appropriate directory. If I type:

echo $HOME_DIR

It gives me:


However, if I try to use a variable, it does not work:

-bash: cd: ~george: No such file or directory

I've tried using quotes and backticks, but can't figure out how to make it expand properly. How do I make this work?


I just wanted to post my completed script (really, it isn't as ugly as the work in progress above!) and say that it appears to be working right.


# Often users have a messed up cache folder -- one that was redirected
# but now is just a broken symlink.  This script checks to see if
# the cache folder is all right, and if not, deletes it
# so that the system can recreate it.

#set -x # turn on to help debug

USERNAME=$3 # Casper passes the user name as parameter 3
if [ "$USERNAME" == "" ] ; then
    echo "This script must be run at login!" >&2
    exit 1  # bail out, indicating failure

CACHEDIR=`echo $(eval echo ~$USERNAME/Library/Caches)`

# Show what we've got
ls -ldF "$CACHEDIR"

if [ -d "$CACHEDIR" ] ; then
    # The cache folder either exists or is a working symlink
    # It doesn't really matter, but might as well output a message stating which
    if [ -L "$CACHEDIR" ] ; then
        echo "Working symlink found at $CACHEDIR was not removed."
        echo "Normal directory found at $CACHEDIR was left untouched."
    # We almost certainly have a broken symlink instead of the directory
    if [ -L "$CACHEDIR" ] ; then
        echo "Removing broken symlink at $CACHEDIR."
        rm "$CACHEDIR"
        echo "Abnormality found at $CACHEDIR.  Trying to remove." >&2
        rm -rf "$CACHEDIR"
        exit 2  # mark this as a bad attempt to fix things; it isn't clear if the fix worked

# exit, indicating that the script ran successfully,
# and that the Cache folder is (almost certainly) now in a good state
exit 0  
share|improve this question
up vote 15 down vote accepted

Use $(eval echo ...):

michael:~> USERNAME=michael
michael:~> echo ~michael
michael:~> echo ~$USERNAME
michael:~> echo $(eval echo ~$USERNAME)

So your code should look like:

HOMEDIR="$(eval echo ~$USERNAME)"
share|improve this answer
+1 solution specific to your exact script. – Warner Apr 21 '10 at 17:21
Beautiful! Thank you. – Clinton Blackmore Apr 22 '10 at 16:23

It's because it's going to enclose the ~george within singlequotes when set as a variable. set -x is useful for debugging.

Remove the quoting when setting DIR and the shell will expand when setting the variable, which will give you your desired performance.

lrwxrwxrwx  1 root root 4 Sep 10  2004 /bin/sh -> bash*
wmoore@bitbucket(/tmp)$ cat
set -x

cd ~root


cd $DIR


cd $DIR
wmoore@bitbucket(/tmp)$ sh
+ cd /root line 4: cd: /root: Permission denied
+ DIR=/root
+ cd /root line 8: cd: /root: Permission denied
+ DIR=~root
+ cd '~root' line 12: cd: ~root: No such file or directory
share|improve this answer
+1, I was way off on that one :-) – Kyle Brandt Apr 21 '10 at 16:45
set -x is a nice tip. Removing the quoting from DIR did not work for me under bash 3.2. – Clinton Blackmore Apr 22 '10 at 16:23
GNU bash, version 2.05b.0(1)-release (i486-slackware-linux-gnu) here. – Warner Apr 22 '10 at 16:25

Bash has built-in exported variables for username and user's home directory. If you are calling your script when the user logs in from their ~/.bash_profile for example, you won't need to pass the values as arguments to your script.

You can use $USER and $HOME since they're already set and available in the environment of your script since they're marked as exported. I think tilde expansion is meant to be more of a command-line convenience than something that's used in scripts.


cd "$DIR" || rm "$DIR" && echo "Removed misdirected Cache folder" && exit 1

It might be more reliable to get the user's home directory in one of the following ways:

getent passwd $USER | awk -F: '{print $(NF - 1)}'


awk -F: -v user=$USER 'user == $1 {print $(NF - 1)}' /etc/passwd

Also, exit 0 indicates success. In a way, your process is successful in deleting the directory, but the fact that it needs deleting is an error of a sort. In any case, if you exit 0 at this point you won't be able to tell the difference when the script exits after the final echo since the exit code there will most likely be zero.

share|improve this answer
+1 Those were some of my toughts, but with the HOME variables I think this is maybe meant not to be run as the USER, as he overrides the USERNAME variable with the 3rd argument to the script (which through me off as well). I also tend to think the whole || and && as flow control (replacing if statements) is bad style. – Kyle Brandt Apr 21 '10 at 21:38
The script will be running through Casper. I'm not sure if it looks at the exit codes, but if it does, I want any instance where the script either deletes the cache folder or doesn't find one to indicate a successful run of the script, and any instance where the username was not provided (indicating the script was not called at login or logout) to show up as an error. Errors are flagged in red when we look at the logs of where the script ran. – Clinton Blackmore Apr 22 '10 at 15:07
@Clinton: Then you'll want to have a non-zero number for the exit value (including an explicit exit n at the end of the script if falling through is also an error. Zero indicates success and you can use different non-zero numbers to indicate different types of errors (it's up to you to determine their meaning for your own purposes). If you don't need to distinguish one type of an error from another, then exit 1 is as good as any to use in all cases to indicate a general error. – Dennis Williamson Apr 22 '10 at 15:23
I guess unix is unix and is all the same ... except when it's not! It appears that getent doesn't exist on the Mac, and normal users aren't mentioned in /etc/passwd. The sibling answer on dscl suggests a way to get the same info under OS X. I appreciate your answer, and might well change the script to not rely on on lazy evaluation of logical operators. – Clinton Blackmore Apr 22 '10 at 16:52

Judging by the path $HOME/Library/Caches, this is Mac OS X, so dscl is your friend.

As noted above, bash does it for you, though, and if it’s a Mac, you can guarantee bash will be available (and so you don’t need to worry about a strictly-conforming /bin/sh not being able to cope with it).

share|improve this answer
That's a good point. dscl /Search read /Users/$USERNAME NFSHomeDirectory | cut -f 2 -d " " seems to be about right for getting the information I was after. – Clinton Blackmore Apr 22 '10 at 16:29

Your Answer


By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.