15

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.

1
  • 1
    You can use $HOME instead of ~. Commented Apr 22, 2020 at 16:06

2 Answers 2

22

~ 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 assignments 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=~

In any case, you can always use $HOME instead of ~ (echo a=$HOME in zsh, echo "a=$HOME" in other Bourne-like shells where parameter expansions need to be quoted to prevent split+glob²).


¹ Only the literal and unquoted ones, or the ones resulting from expansions where globsubst is enabled, where filename expansion (separate from globbing aka filename generation) which ~ is a form of is performed as long as shfileexpansion is not also enabled (like in sh emulation). You'll find that var='a=~' zsh -o magicequalsubst -o globsubst -c 'echo $var' and var='a=~' zsh -o magicequalsubst -c 'echo $~var' both output a=/home/dir.

² tilde-expansion itself doesn't undergo split+glob except in older versions of bash where it used to undergo globbing. In any case, tilde-expansion is not performed inside double quotes.

-2

In any command you can include the output of another command enclosed in back quotes, even for parameters, directly without eval (it executes in a temporary shell).

Here's an example where using tilde directly without spaces around it requires this trick. Note that this works in any command, not just the echo command:

echo the simplest way to include the path to the home folder in a command is using the tilde: ~. It expands to the home folder \(`echo ~`\).

And here's an example that uses the command output as a parameter:

ls `echo -l`

1
  • In this case, it would be simpler to just switch to using $HOME to avoid any issues with tilde expansions, than to introduce further issues with unquoted command substitutions and echo. E.g. try ls `echo -n`
    – Kusalananda
    Commented Jun 26 at 13:18

You must log in to answer this question.

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