Shell quoting for exec

TL;DR

What I use for quoting things properly in the shell so that I can call exec.

In the previous post about Rich’s sh (POSIX shell) tricks we disclosed a mine for POSIX shell “programming”.

Quoting, programmatically and properly

One interesting function is to properly quote stuff (Shell-quoting arbitrary strings). In the author’s words, here’s a function that works:

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

This is the second gold nugget in the page, following the hint about properly printing with printf instead of echo. And yes, it comes second only because printing happens more frequently 😄.

I’ve played a bit with the possibility that the target system might not have sed installed. It’s possible to build a function that does the same as quote above, but without using sed; anyway, it’s probably just a style exercise, because it’s so easy to bring sed around using Busybox (there’s a statically compiled binary that does the trick, as discussed in Busybox - multipurpose executable). So… this is left as a simple exercise for the reader 😏

Where to use it?

Where is the quote function above useful? Glad you asked!

One first place is when you have to eval something:

$ x='hello all'
$ eval "y=$x"
/bin/sh: 1: eval: all: not found

The error happens because x is expanded and then the expression is evaluated, which is the same as this:

$ eval "y=hello all"

i.e. calling the all command with environment variable y set to hello. Whooops!

Time for quote to kick in:

$ quote () { printf %s\\n "$1" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/'/" ; }
$ x='hello all'
$ eval "y=$(quote "$x")"
$ printf 'y is <%s>\n' "$y"
y is <hello all>

This is also useful when you have to call the shell and pass a whole command with option -c, like this:

$ /bin/sh -c "y=$x; printf '%s\n' \"y is <$y>\""
/bin/sh: 1: all: not found
y is <>

which might happen more frequently than you think if you’re scripting remote execution of commands via ssh:

$ ssh remote-server /bin/sh -c ":; y=$x; printf '%s\n' \"y is <\$y>\""
bash: all: command not found
y is <>

Again, quote makes the day here:

$ ssh polettix.it /bin/sh -c ":; y=$(quote "$x"); printf '%s\n' \"y is <\$y>\""
y is <hello all>

If you’re curious about why I put an initial :; in the command… it’s the only way I found to make it work. There must be some issue when running remote commands where the first command sets a variable, I don’t know.


Comments? Octodon, , GitHub, Reddit, or drop me a line!