Does ~ always equal $HOME

What's important to understand is that ~ expansion is a feature of the shell (of some shells), it's not a magic character than means your home directory wherever it's used.

It is expanded (by the shell, which is an application used to interpret command lines), like $var is expanded to its value under some conditions when used in a shell command line before the command is executed.

That feature first appeared in the C-shell in the late 1970s (the Bourne shell didn't have it, nor did its predecessor the Thompson shell), was later added to the Korn shell (a newer shell built upon the Bourne shell in the 80s). It was eventually standardized by POSIX and is now available in most shells including non-POSIX ones like fish.

Because it's in such widespread use in shells, some non-shell applications also recognise it as meaning the home directory. That's the case of many applications in their configuration files or their own command line (mutt, slrn, vim...).

bash specifically (which is the shell of the GNU project and widely used in many Linux-based operating systems), when invoked as sh, mostly follows the POSIX rules about ~ expansion, and in areas not specified by POSIX, behaves mostly like the Korn shell (of which it is a part clone).

While $var is expanded in most places (except inside single quotes), ~ expansion, being an afterthought is only expanded in a few specific conditions.

It is expanded when on its own argument in list contexts, in contexts where a string is expected.

Here are a few examples of where it's expanded in bash:

  • cmd arg ~ other arg
  • var=~
  • var=x:~:x (required by POSIX, used for variables like PATH, MANPATH...)
  • for i in ~
  • [[ ~ = text ]]
  • [[ text = ~ ]] (the expansion of ~ being taken as a pattern in AT&T ksh but not bash since 4.0).
  • case ~ in ~) ...
  • ${var#~} (though not in some other shells)
  • cmd foo=~ (though not when invoked as sh, and only when what's on the left of the = is shaped like an unquoted bash variable name)
  • cmd ~/x (required by POSIX obviously)
  • cmd ~:x (but not x:~:x or x-~-x)
  • a[~]=foo; echo "${a[~]} $((a[~]))" (not in some other shells)

Here are a few examples where it's not expanded:

  • echo "~" '~'
  • echo ~@ ~~ (also note that ~u is meant to expand to the home directory of user u).
  • echo @~
  • (( HOME == ~ )), $(( var + ~ ))
  • with extglob: case $var in @(~|other))... (though case $var in ~|other) is OK).
  • ./configure --prefix=~ (as --prefix is not a valid variable name)
  • cmd "foo"=~ (in bash, because of the quotes).
  • when invoked as sh: export "foo"=~, env JAVA_HOME=~ cmd...

As to what it expands to: ~ alone expands to the content of the HOME variable, or when it is not set, to the home directory of the current user in the account database (as an extension since POSIX leaves that behaviour undefined).

It should be noted that in ksh88 and bash versions prior to 4.0, tilde expansion underwent globbing (filename generation) in list contexts:

$ bash -c 'echo "$HOME"'
/home/***stephane***
$ bash -c 'echo ~'
/home/***stephane*** /home/stephane
$ bash -c 'echo "~"'
~

That should not be a problem in usual cases.

Note that because it's expanded, the same warning applies as other forms of expansions.

cd ~

Doesn't work if $HOME starts with - or contains .. components. So, even though it's very unlikely to ever make any difference, strictly speaking, one should write:

cd -P -- ~

Or even:

case ~ in
  (/*) cd -P ~;;
  (*) d=~; cd -P "./$d";;
esac

(to cover for values of $HOME like -, +2...) or simply:

cd

(as cd takes you to your home directory without any argument)

Other shells have more advanced ~ expansions. For instance, in zsh, we have:

  • ~4, ~-, ~-2 (with completion) used to expand the directories in your directory stack (the places you've cd to before).
  • dynamic named directories. You can define your own mechanism to decide how ~something is being expanded.

In any version of Bash on any system, yes. ~ as a term on its own is defined to expand to:

The value of $HOME

so it will always be the same as whatever $HOME is to the current shell. There are several other tilde expansions, such as ~user for user's home directory, but a single unquoted ~ on its own will always expand to "$HOME".

Note that the behaviour of ~ and $HOME can be different in some cases: in particular, if $HOME contains spaces (or other IFS characters), then $HOME (unquoted) will expand to multiple words, while ~ is always a single word. ~ expands equivalently to "$HOME" (quoted).

In regard to your specific question:

[[ $HOME == ~ ]]

is always true, because [[ suppresses word-splitting. [[ ~ == $HOME ] may not be if HOME has pattern matching characters in it, but [[ ~ == "$HOME" ]] (i.e., quoted "$HOME") is always true. Using it inside single brackets can be a syntax error for values of HOME containing spaces or special characters. For any sensible home directory configuration ~ and "$HOME" are the same and compare as equal.


Stéphane Chazelas has noted a case in the comments where ~ and $HOME give different values: if you unset HOME, then when you use ~ Bash will call getpwuid to read a value out of the password database. This case is excluded by your condition of having no configuration changing $HOME, but I'll mention it here for completeness.