Shell – Setting a variable to ~/directory doesn’t expand

shell-scripttar

I'm trying to setup a simple backup script with tar as you can see below:

#!/bin/bash
TIME=`date +%F_%H-%M`
FILENAME=backup-$TIME.tar.gz
SRCDIR="~/PI/tutos/* ~/scripts/*"
DESTDIR=/media/sf_ubuntuSharedFolder/backups
tar cvpzf $FILENAME $SRCDIR

However, the usage of $SRCDIR causes a tar error that I do not have if I just expand it myself.

alex@ALeX-VirtualBox:~$ save
tar: ~/PI/tutos/*: Cannot stat: No such file or directory
tar: ~/scripts/*: Cannot stat: No such file or directory

What is wrong here at using a shell variable? Otherwise, how can I pass a list of folders to tar in a single line?

Best Answer

The issue is that ~-expansion is not performed upon variable expansion (when you refer to that $SRCDIR unquoted) nor inside double quotes (when you assign that SRCDIR variable).

In leaving $SRCDIR unquoted, you're invoking the split+glob operator. That is the string that is stored in that scalar $SRCDIR variable (~/PI/tutos/* ~/scripts/*) is first split according to $IFS (blanks by default), and then each word undergoes globbing, that is are treated as patterns that expand to the list of matching files.

Because ~ is not expanded to your home directory there, that ~ is just treated like any other character, so it's just looking for files in the ~/PI/tutos directory, where ~ would be a directory in the current directory, which in your case doesn't exist.

Best here would be to make $SRCDIR an array and have the globs expanded at the time of the assignment:

SRCDIR=(~/PI/tutos/* ~/scripts/*) # ~ and globs expanded at this point
tar cvpzf "$FILENAME" "${SRCDIR[@]}"

Note that applying the split+glob operator on $FILENAME doesn't make sense, so we're disabling it by quoting $FILENAME.

Note that if ~/PI/tutos/* doesn't match, it will be left as-is, so you'd still get an error from tar. To avoid that, you could do:

shopt -s nullglob # remove non-matching globs
SRCDIR=(~/PI/tutos/* ~/scripts/*)
if ((${#SRCDIR[@]} != 0)); then
  tar cvpzf "$FILENAME" "${SRCDIR[@]}"
fi

You may be tempted to do:

SRCDIR="$HOME/PI/tutos/* $HOME/scripts/*"
tar cvpzf "$FILENAME" $SRCDIR

As variables (such as $HOME) are expanded within double quotes, but I would advise against it as that wouldn't work properly if $HOME contains glob characters or characters of $IFS.

~s are expanded in variable assignments when not quoted and when at the start or following : (that's so that it works in assignments of variables like $PATH, $LD_LIBRARY_PATH... such as PATH=~/bin:~/sbin). So you may be tempted to do:

SRCDIR=~/PI/tutos/*:~/scripts/* # ~ (not globs) expanded here
IFS=:
tar cvpzf "$FILENAME" $SRCDIR

But that's the same as above, that won't work properly if $HOME contains $IFS characters (this time :, so very unlikely as /etc/passwd that defines your home directory is a colon-separated table) or glob characters.

Related Question