The tilde (~) is only expanded to the home directory if it appears directly (and is not quoted). When you use cd "$DESTINATION_FOLDER"
, the characters in $DESTINATION_FOLDER
are used literally, including the leading ~. Even if you had used cd $DESTINATION_FOLDER
, the ~ would have been inserted directly, although whitespace and glob metacharacters in $DESTINATION_FOLDER
would have been expanded. So there is really no way to do tilde expansion on a value in a variable. [Note 1]
Some people will suggest using eval
, but you have to be very careful about that. eval cd "$DESTINATION_FOLDER"
could have unexpected results if $DESTINATION_FOLDER
includes a shell metacharacter. Consider the following, for example (don't try this in a directory which contains files you care about):
$ mkdir -p ~/tmp/foo && cd ~/tmp/foo
$ touch a few files
$ ls
a few files
$ x="~/tmp/foo;rm *"
$ # BEWARE!
$ eval cd "$x"
$ # OOPS!
$ ls
$
The correct way to do this is to expand the tilde yourself:
cd "${DESTINATION_FOLDER/#~/$HOME}"
In bash
, ${var/pattern/replacement}
produces a string based on the value of $var
, where the first instance of pattern
(if any) is replaced with replacement
. The form ${name/#pattern/replacement}
only does the replacement if pattern
occurs at the beginning of $var
. pattern
is a glob, not a regular expression.
EDIT: In a comment, Tom Fenech suggested that it would be cool if it were possible to handle ~user
as well as just ~
. In the same vein, it would be even cooler to handle bashisms like ~+2
[Note 2].
Indeed. Unfortunately, bash
does not provide a builtin which does tilde expansion. Or any other kind of expansion. So I think it is necessary to use eval
, but that requires considerable caution, as mentioned above. I think that the best that can be done is to extract the tilde-prefix only if it contains no metacharacters, and only then expand it with eval printf %s
, something like this:
tilde-expand() {
(
shopt -s extglob
if [[ $1 =~ ^~[[:alnum:]_.+-]*(/.*)?$ ]]; then
printf %s "$(eval printf ${1%%/*})${1##*([^/])}"
else
printf %s "$1"
fi
)
}
(The above hasn't been carefully tested. As always, eval
should be treated with the greatest of caution, so please examine carefully before deploying.)
Notes:
In general, this doesn't matter, because most scripts take input as arguments rather than reading them from the console. Such a script might be written as follows:
cdrm() {
cd "$1" && rm -rf readme.md
}
then you would be able to invoke it using a tilde:
cdrm ~/Dropbox/myproject/
because the ~ will be expanded in the call to cdrm
, so that $1
will have the expanded value.
I've never used ~+N
tilde expansions, but there are probably people who find them essential.