In Zsh 5.9, we can use printf to shell escape a string:

$ printf '%q' 'One! Two'

One\!\ Two

This produces the correct output of escaping the ! and the space. Now let’s make it as a script:


printf '%q' "${1}"

Now if we run it, the ! is not escaped but the space is:

$ ./my-script 'One! Two'

One!\ Two

If I change the script to /bin/bash (version 3.2), it correctly escapes the ! and space.

Is this a Zsh bug, or is there some subtle detail I’m missing?

1 Answer 1


It looks like it checks the current shell settings to determine if the bang is special. History expansion isn't enabled by default in noninteractive shells, so it's not special in a script.

% (setopt   banghist; printf '%q\n' 'One! Two')
One\!\ Two
% (setopt nobanghist; printf '%q\n' 'One! Two')
One!\ Two

The same appears to apply to the ${(q)var} parameter expansion.

Honestly, I find that a somewhat odd choice, as while it means the output is suitable as input to the same shell session, it's not generally suitable as input to every zsh session. Always escaping the bang shouldn't cause any issues as \! gives a ! regardless of if banghist is set.

You could use ${(qq)var} instead to single-quote the string (or ${(qqqq)var} to use the $'...' quotes). The bang would then be quoted along with everything else. Or make it into a function:

% printq() { printf '%s\n' "${(qq)1}" }
% printq one\!two
  • Note that with qqq which uses double quotes, ! must be escaped when banghist is enabled and must not be escaped when not, so zsh has no choice in that case. Then makes sense that it be done consistently. Also note that the rcquotes option affects the quoting. Commented May 17 at 18:48
  • @StéphaneChazelas, well, that's true, though IMO that only works as an argument for not being too insistent on only using the double quotes... qqq could turn ! into "\!", similarly to how qq deals with the single quote. And regardless of rcquotes, '\'' seems to work. (At the cost of being uglier than the rc style.)
    – ilkkachu
    Commented May 17 at 19:01
  • That's the point, if banghist is not enabled, expanding to "\!" would be wrong as "\!" is \! when banghist is disabled. To output the quoting of foo!bar that is valid both with and without banghist, that would have to be something like "foo"\!"bar". Commented May 18 at 8:35
  • @StéphaneChazelas, yes, exactly.
    – ilkkachu
    Commented May 18 at 8:38
  • On the other hand, note that = in leading position is escaped (with (q)) even if the equals option is turned off which is an argument for doing the same with !. Same for ^ with extendedglob (also used in history substitution) Commented May 18 at 8:38

You must log in to answer this question.

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