41

Writing my first script so I'm sure this is a basic question, but can someone please explain to me why I can:

cd ~
cd bin
cd ~/bin
cd 'bin'

But not

cd '~'
cd '~/bin'

I need to cd to a directory path with a space in one of the directory names, so I need the quotes (it's Windows Program Files under wine). I can get around it with two cd commands, but why can't I put ~ in quotes?

If I type cd '~' (or cd "~") I get:

bash: cd: ~: No such file or directory
muru
  • 197,895
  • 55
  • 485
  • 740
B.Tanner
  • 2,636
  • 29
    Ask yourself this: if if didn't work that way, how would you cd into a directory named ~? – Jörg W Mittag May 05 '18 at 15:11
  • 2
    As a sidenote, if you're writing a script you should rather use absolute paths and avoid cd alltogether. Use variables to store pathnames you don't want to type multiple times, e.g. pf=~/.wine/drive_c/Program\ Files/; cp /path/to/file "$pf" – dessert May 05 '18 at 15:40
  • 2
    @Jörg W Mittag: Why would a sensible system allow you to name a directory '~'? How about a directory named '/'? (Of course I realize that just having a directory called "Windows Program Files" means we're probably not dealing with a sensible system :-() – jamesqf May 05 '18 at 17:37
  • 8
    @jamesqf: Why would a sensible system presume to know better than me what I want to name my files and directories? Also, I never understood why Unix went with magic in-band signaling of path component separators and path terminators. What's wrong with a file/directory named / or NUL? – Jörg W Mittag May 05 '18 at 17:40
  • Thank you @dessert, I do appreciate that it is better not to have side effects but using a full path didn't work. Because of the '~' issue though...now it does! – B.Tanner May 05 '18 at 17:53
  • @jamesqf Following this logic Ubuntu is not a sensible system – did you try touch \~? – dessert May 05 '18 at 17:58
  • 4
    @JörgWMittag presumably cd ./~ – k_g May 06 '18 at 03:31
  • @Jörg W Mittag: Maybe because having a set of allowable filename characters and a disjoint set of path/command characters makes life much simpler for everyone. Personally, I can't understand why spaces were ever allowed. (Other that the Windows compatibility thing, but that just pushes the question off on Microsoft.) – jamesqf May 07 '18 at 03:52
  • 2
    @jamesqf Just to note that historically, Unix filesystems allowed any char in a filename except '/' (path separator) and '\0' (NUL - string-termination char) - as Jorg notes. No other restrictions. There have been many Unix filesystem implementations, so introducing restrictions in any one of them would have broken compatibility and increased complexity - and been seen as "weird"! And '~' expansion was only introduced comparatively recently in csh and more widely in bash, files/dirs with '~' chars in are not unknown (eg myfile~ as a backup for myfile). – SusanW May 07 '18 at 12:53

4 Answers4

86

As @karel noted in his answer, ~ is a special character and expanded by Bash into the current user's home directory. See Bash's manual on "Tilde Expansion", or search for the headline "Tilde Expansion" in the man page (man bash).

Any kind of quotation around the ~ prevents this tilde expansion.


To answer your question about how you still can use it to cd into a directory with spaces in its name, there are a few alternatives:

  • Omit quotes and escape the spaces with backslashes instead:

    cd ~/foo/spaces\ are\ cool/bar
    
  • Quote the rest of the path, but omit them around the tilde and the first slash:

    cd ~/"foo/spaces are cool/bar"
    

    As you see, you can concatenate quoted and unquoted strings in Bash by simply writing them next to each other without any spaces in between.

  • Use the environment variable $HOME instead of the tilde, which still gets expanded inside "double quotes" (but not 'single quotes'):

    cd "$HOME/foo/spaces are cool/bar"
    
ilkkachu
  • 1,837
Byte Commander
  • 107,489
  • 4
    @rexkogitans ~'/...' won't work and isn't what this answer has. ~/'...' or ~/"..." will work. – hvd May 05 '18 at 13:33
  • tld;dr: because thats shell syntax – Sergiy Kolodyazhnyy May 05 '18 at 14:48
  • @hvd thanks. Fixed typo. This is the most complete answer and also explains how to solve what the OP wants to do. ~/'path' is the way to go. – rexkogitans May 05 '18 at 14:49
  • 2
    @hvd just little of curiosity, why wouldn't ~'/...' work? The slash isn't a special character so it seems it should work either inside or outside the quotes. – Cave Johnson May 05 '18 at 18:41
  • 7
    @KodosJohnson Have a look at the Bash manpage's section about "Tilde Expansion". The relevant paragraph of that is also quoted in dessert's answer below. TL;DR: Tilde expansion takes place if the first character in a word is an unquoted tilde, then everything between that tilde and the next unquoted slash is considered the "tilde prefix". If this prefix is not a valid thing like +, -, a number, or a user name (quotes in there are not allowed either), the expansion fails and you get a literal ~ character. – Byte Commander May 05 '18 at 18:52
18

~ is a special character that is interpreted by the shell as the logged in user's home directory. '~' is interpreted by the shell as a literal ~ character, not as the logged in user's home directory because enclosing a string inside two single quote characters results in that string being interpreted as a literal text string.

karel
  • 114,770
  • ~ is not an alias in the sense bash interprets it: Aliases allow a string to be substituted for a word when it is used as the first word of a simple command.. As I explain in my answer below, it's rather an expansion. – dessert May 05 '18 at 11:38
16

This is a bash feature called Tilde Expansion. Citing man bash:

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.

For the expansion to work the tilde character ~ needs to be unquoted, else the character is taken literally and cd fails if there is no directory named ~ present in the current directory. See this entensive answer for an explanation of quoting in bash. If you need to quote part of the path, you can therefore:

  1. quote at least the characters that need quoting with single quotes, e.g.

    ~/dir' 'with' 'spaces/
    

    or

    ~/'dir with spaces/'
    
  2. quote at least the characters that need quoting with double quotes, e.g.

    ~/dir" "with" "spaces/
    

    or

    ~/"dir with spaces/"
    
  3. quote only the characters that need quoting with backslashes , e.g.

    ~/dir\ with\ spaces/
    

Tilde Expansion has some more interesting features, e.g.:

  • ~+ expands to the value of PWD, i.e. the current working directory
  • ~- expands to the value of OLDPWD, i.e. the previous working directory
  • ~john expands to the home directory associated with the login name “john”
dessert
  • 39,982
3

Explore using echo command

The easiest way of exploring how things work in bash is with the echo command. In the case of ~ use this:

$ echo ~
/home/rick
$ echo '~'
~
$ echo "~"
~
$ echo `~`
bash: /home/rick: Is a directory

As you can see, when you single quote or use double quotes around ~ it is interpreted literally as a string and not expanded as a variable. When you use backticks (`) it is executed as a command and generates an error message.