Why does mkdir fail (no such file or directory) in a script with BIN_DIR="~/bin/"?

8,776

Solution 1

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.

Solution 2

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.

Share:
8,776
Lloyd Perera
Author by

Lloyd Perera

Updated on September 18, 2022

Comments

  • Lloyd Perera
    Lloyd Perera over 1 year

    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
    
  • terdon
    terdon over 6 years
    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.
  • pa4080
    pa4080 over 6 years
    @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.
  • terdon
    terdon over 6 years
    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.
  • dessert
    dessert over 6 years
    While that's totally true it's also right that using $HOME in scripts is a good idea.
  • Eliah Kagan
    Eliah Kagan over 6 years
    @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.
  • pa4080
    pa4080 over 6 years
    @EliahKagan, I've updated the answer with what I had in mind. Sorry for the late update but I had some work to do.
  • Eliah Kagan
    Eliah Kagan over 6 years
    Thanks--your edit has somewhat improved this answer. But I'm not sure the reasoning you've given stands up to scrutiny. ~/bin is as practical as $HOME/bin. Really more--it's what most people already use interactively. You say quoting is different or is used differently in scripts, which seems obviously false to me, but maybe you could explain it in another edit? Most importantly, using $HOME does not make attention unnecessary! Filenames often contain ", $, or `. Whether $HOME or ~ is used, it's always necessary to consider if and how to quote the rest of the path.
  • pa4080
    pa4080 over 6 years
    @EliahKagan, In an attempt to clarify what I mean, I've edited the answer one more time: )
  • pa4080
    pa4080 over 6 years
    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.
  • Eliah Kagan
    Eliah Kagan over 6 years
    Thanks, but I don't know what you mean by "In the scripts things are back, for different reasons in most cases we want to quote the strings that we assign as variable's values." What does "things are back" mean? What are the "different reasons"?
  • Barmar
    Barmar over 6 years
    @pa4080 Expansion of ~ is not part of parameter expansion.
  • pa4080
    pa4080 over 6 years
    @EliahKagan, I think I found what is the problem with this answer. Something more, I found that, I spent more effort to explain what I mean instead of just saying it. Thank you for the criticism and best regards!