What is the best way to escape a string for safe usage as a command-line argument? I know that using
from subprocess import Popen
from paramiko import SSHClient()
from subprocess import list2cmdline
ssh = SSHClient()
#... load host keys and connect to a server
stdin, stdout, stderr = ssh.exec_command(list2cmdline(['touch', 'foo;uptime']))
stdin, stdout, stderr = ssh.exec_command(list2cmdline(['touch', 'foo;echo test']))
Assuming the remote user has a POSIX shell, this should work:
def shell_escape(arg): return "'%s'" % (arg.replace(r"'", r"'\''"), )
POSIX shell single quotes are defined as:
Enclosing characters in single-quotes ( '' ) shall preserve the literal value of each character within the single-quotes. A single-quote cannot occur within single-quotes.
The idea here is that you enclose the string in single quotes. This, alone, is almost good enough --- every character except a single quote will be interpreted literally. For single quotes, you drop out of the single-quoted string (the first
'), add a single quote (the
\'), and then resume the single quoted string (the last
This should work for any POSIX shell. I've tested it with dash and bash. Solaris 5.10's
/bin/sh (which I believe is not POSIX-compatible, and I couldn't find a spec for) also seems to work.
For arbitrary remote hosts, I believe this is impossible. I think
ssh will execute your command with whatever the remote user's shell (as configured in
/etc/passwd or equivalent). If the remote user might be running, say,
git-shell or something, not only is any quoting scheme probably going to run into cross-shell inconsistencies, but you command execution is probably going to fail too.
Slightly more problematic is the possibility that the remote user might be running
tcsh, since some people actually do run that in the wild and might expect paramiko's
exec_command to work. (Users of
/usr/bin/python as a shell probably have no such expectations...)
tcsh seems to mostly work. However, I can't figure out a way to quote a newline such that it will be happy. Including a newline in single-quoted string seems to make tcsh unhappy:
$ tcsh -c $'echo \'foo\nbar\'' Unmatched '. Unmatched '.
Other than newlines, everything I've tried seems to work with tcsh (including single quotes, double quotes, backslashes, embedded tabs, asterisks, ...).
If you have an escaping scheme, here are some things you might want to test with:
Newlines are worth a special note. The
re.escape solution doesn't handle this right --- it escapes any non-alphanumeric character, and POSIX shell considers an escaped newline (ie, in Python, the two-letter string
"\\\n") to be zero characters, not a single newline character. I think
re.escape handles all other cases correctly, though it scares me to use something designed for regular expressions to do escaping for shell. It might turn out to work, but I'd worry about a subtle case in
re.escape or shell escaping rules (like newlines), or possible future changes in the API.
You should also be aware that escape sequences can get processed at various stages, which complicates testing things --- you only care about what the shell passes to a program, not what the program does. Using
printf "%s\n" escaped-string-to-test is probably the best bet.
echo works surprisingly poorly: In dash, the
echo built-in processes backslash escapes like
/bin/echo is usually safe, but on a Solaris 5.10 machine I tested on, it also handles sequences like