10

Why does the mkdir command fail with: "No such file or directory"?

#!/bin/bash

set -e

BIN_DIR="~/bin/"

if [ ! -d "$BIN_DIR" ]; then
  mkdir "$BIN_DIR"
fi
1

2 Answers 2

23

It doesn't work because ~ is quoted. Double quotes " suppress tilde expansion. There is no directory with the literal name ~/bin. As explained in man bash (emphasis mine):

Tilde Expansion

If a word begins with an unquoted tilde character (`~'), all of the characters preceding the first unquoted slash (or all characters, if there is no unquoted slash) are considered a tilde-prefix. If none of the characters in the tilde-prefix are quoted, the characters in the tilde-prefix following the tilde are treated as a possible login name. If this login name is the null string, the tilde is replaced with the value of the shell parameter HOME. If HOME is unset, the home directory of the user executing the shell is substituted instead. Otherwise, the tilde-prefix is replaced with the home directory associated with the specified login name.

You can remove the quotes, since ~ is the only character in the path ~/bin that will cause the shell to perform an expansion, and we want the expansion in this case. The shell won't perform any further expansions on the result of tilde expansion, at least in Bash 4, which all current or remotely recent releases of Ubuntu have. So even if your home directory contains unusual characters like spaces, it's okay.

Or you can use $HOME instead of ~, because parameter expansion is not suppressed by double quotes, only by single quotes. The double quotes do ensure that the expanded value is not itself subject to any further expansions, so word splitting or filename expansion will not occur. So $HOME works even with strangely named home directories, too, so long as you keep the double quotes.

2
  • According to this statement "parameter expansion is not suppressed by double quotes, only by single quotes": the output of cd '~' is -bash: cd: ~: No such file or directory.
    – pa4080
    Nov 17, 2017 at 16:24
  • 2
    @pa4080 Expansion of ~ is not part of parameter expansion.
    – Barmar
    Nov 17, 2017 at 17:24
11

The error message is produced because the tilde ~ is quoted, as described in Zanna's answer. If you want to use the ~, the relevant part of the script should be:

BIN_DIR=~/bin/

If for whatever reason you want to quote the string, you can use the environment variable $HOME:

BIN_DIR="$HOME/bin/"

In my opinion the second approach is better practice.

10
  • 6
    There's nothing wrong with using ~ in scripts. it works in exactly the same way as in the command line. The problem is that quoting blocks tilde expansion as explained in Zanna's answer.
    – terdon
    Nov 17, 2017 at 10:44
  • @terdon, I'm agree. But I haven't told there is something wrong, but it is a better idea, because you should pay less attention.
    – pa4080
    Nov 17, 2017 at 10:59
  • 5
    But there's absolutely no difference between the command line and a script here. The fact that this is in a script is completely irrelevant, you would have exactly the same error in the commandline. The issue is the quoting, not that it's in a script.
    – terdon
    Nov 17, 2017 at 11:06
  • While that's totally true it's also right that using $HOME in scripts is a good idea.
    – dessert
    Nov 17, 2017 at 11:42
  • 3
    @pa4080 Can you add an explanation of why you think it's better to expand $HOME than to use tilde expansion? The only explanation you've given is to say, "it is a better idea, because you should pay less attention." I have no idea what that means. Can you expound on it in an edit? Without it, there is nothing supporting your answer, so surely it belongs in it. Tilde expansion has been required by POSIX for quite a while now and the script's hashbang line is #!/bin/bash so I suppose portability isn't the reason. Nov 17, 2017 at 12:45

You must log in to answer this question.

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