shell: Quote string with single quotes rather than backslashes

Assuming that:

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

posixquote () { 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.

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

zshprintf '%s\n' ${(q)value} This\ isn\'t\ a\ $'\n'$'\033'\ \"correct\"\ test
zshprintf '%s\n' ${(qq)value} 'This isn'\''t a "correct" test'
zshprintf '%s\n' ${(qqq)value} "This isn't a \"correct\" test"
zshprintf '%s\n' ${(qqqq)value} $'This isn\'t a \n\033 "correct" test'
zshprintf '%s\n' ${(q-)value} 'This isn'\''t a "correct" test'
zshprintf '%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.


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'

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