Zsh Tilde – How Tilde Expansion Works

tildezsh

I stumbled upon this behavior of zsh when using FreeBSD:

% dd if=/dev/zero bs=1M count=1 of=~/test2
dd: failed to open '~/test2': No such file or directory

This really confused me because the same thing works just fine in bash.

I can touch files using tilde in zsh, and then ls them:

% touch ~/test2
% ls ~/test2
/home/christoph/test2

At first, I assumed that zsh doesn't realize that there comes a path after of= so it didn't expand ~. But autocompleting file names works just fine. In fact, if use an existing file name, begin its path with ~, and then hit Tab at some point, the path gets expanded in the command I'm typing in.

Why does zsh pass ~/test2 to dd, not /home/christoph/test2?

zsh behaves the same on Linux. In fact, I executed these commands above and copied their outputs on a Linux machine.

Best Answer

~ is expanded only in a few contexts. POSIX, for the standard sh mandates echo a=~ to output a=~ (while it mandates ~ to be expanded in a=~ alone).

zsh however has a magicequalsubst option which you can use for ~ to be expanded after = even if it's not in assigments or arguments to the export/typeset... pseudo-keywords.

So:

$ echo a=~
a=~
$ set -o magicequalsubst
$ echo a=~
a=/home/chazelas

Note that bash, when not in POSIX/sh mode, expands ~ in word=~ but only when what's on the left of = looks like a literal unquoted bash variable name (regardless of whether it's in arguments to typeset/declare/export or any other command):

$ bash -c 'echo a=~'
a=/home/chazelas
$ bash -c 'echo "a"=~'
a=~
$ bash -c 'var=a; echo $var=~'
a=~
$ bash -c 'echo a.b=~'
a.b=~
$ (exec -a sh bash -c 'echo a=~')
a=~
Related Question