8

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:

#!/bin/bash

# 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.

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

DIR="~$USERNAME/Library/Caches"

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:

HOME_DIR=~george
echo $HOME_DIR

It gives me:

/a/path/to/georges_home

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

USERNAME="george"
cd ~$USERNAME
-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?


Addendum

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.

#!/bin/bash

# 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
fi

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."
    else
        echo "Normal directory found at $CACHEDIR was left untouched."
    fi
else
    # We almost certainly have a broken symlink instead of the directory
    if [ -L "$CACHEDIR" ] ; then
        echo "Removing broken symlink at $CACHEDIR."
        rm "$CACHEDIR"
    else
        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
    fi
fi

# exit, indicating that the script ran successfully,
# and that the Cache folder is (almost certainly) now in a good state
exit 0  

4 Answers 4

16

Use $(eval echo ...):

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

So your code should look like:

HOMEDIR="$(eval echo ~$USERNAME)"
1
  • 1
    +1 solution specific to your exact script.
    – Warner
    Apr 21, 2010 at 17:21
2

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 test.sh
#!/bin/bash
set -x

cd ~root

DIR=~root

cd $DIR

DIR="~root"

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

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.

DIR="$HOME/Library/Caches"

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)}'

or

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.

4
  • +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. Apr 21, 2010 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. Apr 22, 2010 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. Apr 22, 2010 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. Apr 22, 2010 at 16:52
1

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).

1
  • 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. Apr 22, 2010 at 16:29

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .