ssh command quoting hell
Latest update:
When you type
$ ssh user@host 'cat /tmp/foo.txt'
cat /tmp/foo.txt
part of that string is evaluated twice: 1) by your current shell as a single quoted string, 2) by a shell on a remote host.
Lets assume you want to write a script that backups some directory from a remote machine. A naive version:
$ cat mybackup.sh
#!/bin/sh
[ -z "$1" -o -z "$2" ] && exit 1
tcd=$1
tdir=$2
ssh user@host "tar cvf - -C $tcd $tdir | gzip" > foo.tar.gz
and if you run it like this:
$ ./mybackup.sh /home joe
And if everything goes ok, you'll get foo.tar.gz which will contain joe's home directory files. But what if $1 or $2 arguments contain spaces and/or quotes? I'll tell you:
$ ./mybackup.sh /home/joe 'tmp/foo "bar'
bash: -c: line 0: unexpected EOF while looking for matching `"'
bash: -c: line 1: syntax error: unexpected end of file
This is a bash error from a remote host because it tries to run
tar czv -C /home/joe tmp/foo "bar | gzip
and "bar
contains an unmached quote. Obvously this is not the command you had in mind.
How can we fix that? Another naive approach would be to single-quote some variables in the script:
ssh user@host "tar cvf - -C '$tcd' '$tdir' | gzip" > foo.tar.gz
And this will work for our example but will fail if tmp/foo "bar
directory would have a name tmp/foo 'bar
(with a single quote instead of a double).
To make it work regardless of such shades we need somehow to transform $1 and $2 script arguments to quoted strings. Such transformed strings shall be a safe choice for substrings that represent to-be-executed commands on the remote host.
One nuance: transforming must be done not by the rules of /bin/sh or
your current local shell, but by the rules of user's shell on a remote
host. (See do_child()
function in session.c of openssh source: it
extracts user's shell name from users db on a remote machine &
constructs arguments for execve(2) as "/path/to/shell_name"
,
"shell_name"
, "-c"
, "foo"
, "bar"
.)
If the remote shell is a sh-derived one, the trasformation function can look like:
sq() {
printf '%s\n' "$*" | sed -e "s/'/'\\\\''/g" -e 1s/^/\'/ -e \$s/\$/\'/
}
(Taken from of http://unix.stackexchange.com/a/4774.)
Then, a final version of the 'backup' script would be:
#!/bin/sh
sq() {
printf '%s\n' "$*" | sed -e "s/'/'\\\\''/g" -e 1s/^/\'/ -e \$s/\$/\'/
}
[ -z "$1" -o -z "$2" ] && exit 1
tcd=$1
tdir=$2
out=`basename "$tdir"`.tar.gz
cmd="tar cvf - -C `sq $tcd` `sq $tdir` | gzip"
echo "$cmd"
ssh user@host "$cmd" > "$out"
Hint: when in doubt, run (openssh) ssh with -v option and search for 'debug1: Sending command'
string in the output.
Tags: ойті
Authors: ag