I have a shell variable (we shall call x
) containing a string with shell meta characters. For example, I might have
abc \"def\'s\" ghi
The following solution will work on ALL input strings that the Bourne shell can handle (including newline characters), uses no external commands on most systems, and is portable to all modern Bourne-like shells: Each argument put into this esceval
function is printed individually properly escaped/quoted. (I named it esceval
for "escape evaluation", in case you were wondering.)
esceval()
{
case $# in 0) return 0; esac
while :
do
printf "'"
unescaped=$1
while :
do
case $unescaped in
*\'*)
printf %s "${unescaped%%\'*}""'\''"
unescaped=${unescaped#*\'}
;;
*)
printf %s "$unescaped"
break
;;
esac
done
shift
case $# in 0) break; esac
printf "' "
done
printf "'\n"
}
More pedantic elaboration on my guarantees above:
${foo#bar}
and ${foo%%bar}
substitutions (all shells you need to care about unless you're targetting Solaris 10's /bin/sh
or similar ancient relics).printf
is not a builtin in your shell (fairly uncommon for printf
to be only available as an external command, vs. for instance sed
which is almost always an external command, and I've seen more stripped-down systems which have a printf
but don't have a sed
, if that matters).Note: The version posted here leaks one variable into the global namespace (unescaped
), but you can easily fix this by either declaring local unescaped
if your shell supports that, or by wrapping the body of the function in a subshell (parentheses - they can even replace the curly braces, though that's a bit visually non-obvious if you go that route, and most shells do fork an additional process for a subshell).
On the other hand, if by some chance you do need to support systems which don't have those variable substring substitutions, you can use sed
, but you need to be careful to properly escape tricky things like newlines in your strings:
esceval()
{
case $# in 0) return 0; esac
while :
do
printf "'"
printf %s "$1" | sed "s/'/'\\\\''/g"
shift
case $# in 0) break; esac
printf "' "
done
printf "'\n"
}
Final note on trailing newlines: you should note that Bourne shells strip trailing newlines from command substitutions (most strip all trailing newlines, a few shells strip just one). In other words, be weary of this:
# Literal strings work fine:
esceval 'foo
'
# Quoted variable substitution also works fine:
ln='
'
esceval "foo$ln$ln$ln$ln"
# Breaks - newlines never make it into `esceval`:
esceval "`printf 'foo\n\n\n\n'`"
P.S. There's also this monstrosity I made, which is a polyfill which will select between the previous two versions depending on if your shell seems to be capable of supporting the necessary variable substitution syntax (it looks awful though, because the shell-only version has to be inside an eval-ed string to keep the incompatible shells from barfing when they see it): https://github.com/mentalisttraceur/esceval/blob/master/sh/esceval.sh
sh
has functions.
# quote() - Creates a shell literal
# Usage: printf '%s\n' "...$( quote "..." )..."
quote() {
printf \'
printf %s "$1" | sed "s/'/'\\\\''/g"
printf \'
}
Testing:
$ x='abc "def'\''s" ghi'
$ printf '%s\n' "$x"
abc "def's" ghi
$ printf '%s\n' "prog `quote "$x"`"
prog 'abc "def'\''s" ghi'
$ printf '%s\n' "prog $( quote "`pwd`" )"
prog '/home/ikegami/foo bar'
$ printf '%s\n' "$( quote "a'b" )"
'a'\''b'
$ printf '%s\n' "$( quote '-n' )"
'-n'
$ printf '%s\n' "$( quote '\\' )"
'\\'
$ printf '%s\n' "$( quote 'foo
bar' )"
'foo
bar'
A version that takes multiple arguments:
# quote() - Creates a shell literal
# Usage: printf '%s\n' "$( quote "..." "..." "..." )"
quote() {
local prefix=''
local p
for p in "$@" ; do
printf "$prefix"\'
printf %s "$p" | sed "s/'/'\\\\''/g"
printf \'
prefix=' '
done
}