2

I'm trying to create a very simple bash script that does the following:

  • Inputs the user to enter a project folder
  • Sets the value entered in a variable
  • cd to the folder entered
  • remove the readme.md file

From what I've read around I've come up with this:

#!/bin/bash

echo "Enter the project folder path:"
read -e DESTINATION_FOLDER

cd "$DESTINATION_FOLDER"
rm -rf "$DESTINATION_FOLDER/readme.md"

The folder's path I entered was

~/Dropbox/myproject

And the error I get is the one below. However that folder exists and I can access it manually using the cd command.

./cleanup.sh: line 6: cd: ~/Dropbox/myproject/: No such file or directory

Can anyone please point out what I'm doing wrong?

Frank Parent
  • 2,136
  • 19
  • 33
  • Perhaps this might solve your question: https://stackoverflow.com/questions/5748216/bash-problem-with-cd-using-tilde-expansion – ConcurrentHashMap Aug 31 '14 at 17:36
  • Is the `cd` necessary? If you just want to remove the file you can do that without it. You would still need to force bash to expand the `~`, possibly using `eval`. After changing directory, `rm -f readme.md` would be sufficient. Also, you can use `read -p "prompt:"` to avoid a separate `echo`. – Tom Fenech Aug 31 '14 at 17:54

2 Answers2

4

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:

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

  2. I've never used ~+N tilde expansions, but there are probably people who find them essential.

Community
  • 1
  • 1
rici
  • 234,347
  • 28
  • 237
  • 341
  • Interesting idea with regard to the expansion, it would be the icing on the cake if it worked for `~user` as well. What you have said in the second paragraph doesn't work for me - try `read a; echo $a` using the input `~`. – Tom Fenech Aug 31 '14 at 18:32
  • Your second paragraph is not correct, variable expansion follows tilde expansion, so with `cd $FOO`, a tilde in `$FOO` would not be expanded. – user2719058 Aug 31 '14 at 19:43
  • @user2719058: 100% correct. My mistake. Fixed, I hope. – rici Aug 31 '14 at 19:58
  • 1
    @TomFenech: Yeah, my second paragraph was wrong. I rewrote it. I'll give `~user` a think; it should be possible, but I'm not 100% certain how. – rici Aug 31 '14 at 20:04
  • @TomFenech: I'm not at all happy about the added solution, but it's there. – rici Sep 01 '14 at 01:01
2

It's the shell that expands a ~ into your home directory when you're typing things into an interactive shell. It won't work from a script.

You'd need to type the full path in to get it to work. (It can be a relative path, of course.)

See this SO question for how to expand a tilde in a bash script.

Community
  • 1
  • 1
chiastic-security
  • 20,430
  • 4
  • 39
  • 67