6

How can I quote a string with single quotes?

Eg, I can do:

$ printf "%q\n" 'two words'
two\ words
$

Is there a way to get a single- (or double-) quoted string as output, ie:

$ MAGIC 'two words'
'two words'
$

I find the single-quoted version much easier to read.

I'd like an answer which works for {ba,z}sh. POSIX shell would be a bonus.

5

Assuming that:

$ value=$'This isn\'t a \n\x1b "correct" test'
$ printf '%s\n' "$value"
This isn't a
"correct" test

quote () { printf %s\\n "$1" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/'/" ; }

Use:

$ quote "${value}"
'This isn'\''t a
"correct" test'

From Rich's sh posix tricks

This function simply replaces every instance of «'» (single quote) within the string with «'\''» (single quote, backslash, single quote, single quote), then puts single quotes at the beginning and end of the string. Since the only character whose meaning is special within single quotes is the single quote character itself, this is totally safe. Trailing newlines are handled correctly, and the single quote at the end doubles as a safety character to prevent command substitution from clobbering the trailing newlines, should one want to do something like:

 quoted=$(quote "$var")

Warning: the ESC (\033 or \x1b or decimal 27) characters above gets (technically) quoted, but is invisible. When sent to a terminal, like other control characters, could even do harm. Only when they are visually presented as $'\033', $'\C-[' or $'\E', they are clearly visible and unambiguous.

printf '%s\n' "${value@Q}" $'This isn\'t a \n\E "correct" test'

printf '%s\n' ${(q)value} This\ isn\'t\ a\ $'\n'$'\033'\ \"correct\"\ test
printf '%s\n' ${(qq)value} 'This isn'\''t a "correct" test'
printf '%s\n' ${(qqq)value} "This isn't a \"correct\" test"
printf '%s\n' ${(qqqq)value} $'This isn\'t a \n\033 "correct" test'
printf '%s\n' ${(q-)value} 'This isn'\''t a "correct" test'
printf '%s\n' ${(q+)value} $'This isn\'t a \n\C-[ "correct" test'

Be careful with some zsh quoted strings: the ESC (\033 or \x1b or decimal 27) characters above are all (technically) quoted, but invisible. When sent to a terminal, like other control characters, could even do harm. Only when they are visually presented as $'\033', $'\C-[' or $'\E', they are clearly visible and unambiguous.

From Bash's manual:

${parameter@operator}
Q The expansion is a string that is the value of parameter quoted in a format that can be reused as input.

From the zshexpn man page:

q
Quote characters that are special to the shell in the resulting words with backslashes; unprintable or invalid characters are quoted using the $'\NNN' form, with separate quotes for each octet.

If this flag is given twice, the resulting words are quoted in single quotes and if it is given three times, the words are quoted in double quotes; in these forms no special handling of unprintable or invalid characters is attempted. If the flag is given four times, the words are quoted in single quotes preceded by a $. Note that in all three of these forms quoting is done unconditionally, even if this does not change the way the resulting string would be interpreted by the shell.

If a q- is given (only a single q may appear), a minimal form of single quoting is used that only quotes the string if needed to protect special characters. Typically this form gives the most readable output.

If a q+ is given, an extended form of minimal quoting is used that causes unprintable characters to be rendered using $'...'. This quoting is similar to that used by the output of values by the typeset family of commands.

  • Note that zsh also has a ${var:q} (csh as well, though in csh it's so that the variable is expanded verbatim as if its expansion was quoted). – Stéphane Chazelas May 24 '18 at 7:50
4

Zsh has a bunch of quoting options that can be applied to parameter expansion:

q

Quote characters that are special to the shell in the resulting words with backslashes; unprintable or invalid characters are quoted using the $'\NNN' form, with separate quotes for each octet.

If this flag is given twice, the resulting words are quoted in single quotes and if it is given three times, the words are quoted in double quotes; in these forms no special handling of unprintable or invalid characters is attempted. If the flag is given four times, the words are quoted in single quotes preceded by a $. Note that in all three of these forms quoting is done unconditionally, even if this does not change the way the resulting string would be interpreted by the shell.

If a q- is given (only a single q may appear), a minimal form of single quoting is used that only quotes the string if needed to protect special characters. Typically this form gives the most readable output.

If a q+ is given, an extended form of minmal quoting is used that causes unprintable characters to be rendered using $'...'. This quoting is similar to that used by the output of values by the typeset family of commands.

So a function like:

MAGIC () {
    printf "%s\n" "${(q+)@}"
}

Would give output like:

$ MAGIC 'two words'
'two words'
$ MAGIC 'two words "'
'two words "'
$ MAGIC 'two '"'"'words'
'two '\''words'
2

Here's a fairly simple solution using sed. The input is in $raw, the output is in $quoted.

quoted=$(printf '%sz\n' "$raw" | sed "s/'/'\\\\''/g; s/'''/'/g")
quoted="'${quoted%z}'"

The trick with the z is to handle trailing newlines correctly. With just printf %s "$raw", you need to rely on sed's behavior when its input does not end with a newline, and then the command substitution always eats all trailing newlines.

The second replacement in the sed script is not necessary, but it produces slightly nicer output by avoiding useless '' in the output when there are consecutive ''s in the input.

Here's a pure POSIX sh solution (it also works in zsh even in native mode). It also avoids useless '', but keeps '' for the empty string.

tail=$raw
quoted=
sq=\'
while
  case "$tail" in
    '') false;;
    \'*) quoted="$quoted\\'"; tail="${tail#?}";;
    [!\']*\'*) quoted="$quoted'${tail%%$sq*}'\\'"; tail="${tail#*$sq}";;
    *) quoted="$quoted'${tail%%$sq*}'"; false;;
  esac
do
  :
done
if [ -z "$quoted" ]; then quoted="''"; fi
  • sed's input ends in a newline if you feed it printf '%s\n' "$raw". Its output won't end in a newline if you do $s/$/'/. – Stéphane Chazelas May 23 '18 at 22:04
-2

You could quote the string a second time with double quotes.

$ printf "%s\n" "'two words'"
\'two\ words\'
$ _

I don't think POSIX shell's printf knows about %q, though.

Edit: If you're talking about the MAGIC, then you could do:

$ printf "%s\n" "'two words'"
'two words'
$ _
  • How would you use this in the generic case, say when being passed "$variable" as an argument? – Tom Hale May 23 '18 at 11:27
  • Two options would be to double-quote or escape the wrapping ', as in wrap() { echo \'$@\'; }. – dsstorefile1 May 23 '18 at 14:48

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.