Differences between revisions 1 and 2
Revision 1 as of 2008-12-16 21:22:08
Size: 2448
Editor: GreyCat
Comment: ssh eats my word boundaries! I can't do ssh remotehost make CFLAGS="-g -O"!
Revision 2 as of 2012-02-06 20:52:26
Size: 3319
Editor: GreyCat
Comment: alternative solution (not applicable in all cases): feed commands as stdin to remote shell
Deletions are marked like this. Additions are marked like this.
Line 3: Line 3:
[[http://www.openssh.org/|ssh]] emulates the behavior of the Unix remote shell command (`rsh` or `remsh`), including this bug. There are a couple way to work around it, depending on exactly what you need.... [[http://www.openssh.org/|ssh]] emulates the behavior of the Unix remote shell command (`rsh` or `remsh`), including this bug. There are a few ways to work around it, depending on exactly what you need.
Line 14: Line 14:
The simplest workaround is to smash everything together into a single argument, and manually add quotes in just the right places, until we get it to work. What's happening is the command and its arguments are being smashed together into a string on the client side, then shoved through the ssh connection to the server side, where that string is handed to your shell as an argument for re-parsing. This is not what we want.

Th
e simplest workaround is to mash everything together into a single argument, and manually add quotes in just the right places, until we get it to work.
Line 23: Line 25:
The first problem with this approach is that it's tedious. If we already have both kinds of quotes, and lots of shell substitutions that need to be performed, then we may end up needing to rearrange quite a lot, add backslashes to protect the right things, and so on. The second problem is that it doesn't work very well if our exact command isn't known in advance -- e.g., if we're writing a WrapperScript.  Let's now consider a more realistic problem: we want to write a wrapper script that invokes `make` on a remote host, with the arguments provided by the user being passed along intact. The first problem with this approach is that it's tedious. If we already have both kinds of quotes, and lots of shell substitutions that need to be performed, then we may end up needing to rearrange quite a lot, add backslashes to protect the right things, and so on. The second problem is that it doesn't work very well if our exact command isn't known in advance -- e.g., if we're writing a WrapperScript.
Line 25: Line 27:
This is a lot harder than it would appear at first, because we can't just mash everything together into one word -- the script's caller might use really complex arguments, and quotes, and pathnames with spaces and shell metacharacters, that all need to be preserved carefully. Fortunately for us, bash provides a way to protect such things safely: `printf %q`. Together with an [[BashFAQ/005|array]] and a loop, we can write our wrapper: Let's now consider a more realistic problem: we want to write a wrapper script that invokes `make` on a remote host, with the arguments provided by the user being passed along intact. This is a lot harder than it would appear at first, because we can't just mash everything together into one word -- the script's caller might use really complex arguments, and quotes, and pathnames with spaces and shell metacharacters, that all need to be preserved carefully. Fortunately for us, bash provides a way to protect such things safely: `printf %q`. Together with an [[BashFAQ/005|array]] and a loop, we can write a wrapper:
Line 48: Line 50:

Another workaround is to pass the command(s) as stdin to the remote shell, rather than as an argument. This won't work in all cases; it means the command being executed on the remote system can't use stdin for any other purpose, since we're tying up stdin to send our commands. But in the cases where it ''can'' be used, it works quite well:

{{{
# POSIX
ssh remotehost sh <<EOF
cd "$PWD" || exit
make CFLAGS"-g -O"
EOF
}}}

(Sanitizing $PWD, again, is left as an exercise for the reader. If you don't need to change directory on the remote host, you can just delete that line altogether.)

ssh eats my word boundaries! I can't do ssh remotehost make CFLAGS="-g -O"!

ssh emulates the behavior of the Unix remote shell command (rsh or remsh), including this bug. There are a few ways to work around it, depending on exactly what you need.

First, here is a full illustration of the problem:

~$ ~/bin/args make CFLAGS="-g -O"
2 args: 'make' 'CFLAGS=-g -O'
~$ ssh localhost ~/bin/args make CFLAGS="-g -O"
Password: 
3 args: 'make' 'CFLAGS=-g' '-O'

What's happening is the command and its arguments are being smashed together into a string on the client side, then shoved through the ssh connection to the server side, where that string is handed to your shell as an argument for re-parsing. This is not what we want.

The simplest workaround is to mash everything together into a single argument, and manually add quotes in just the right places, until we get it to work.

~$ ssh localhost '~/bin/args make CFLAGS="-g -O"'
Password: 
2 args: 'make' 'CFLAGS=-g -O'

The shell on the remote host will re-parse the argument, break it into words, and then execute it.

The first problem with this approach is that it's tedious. If we already have both kinds of quotes, and lots of shell substitutions that need to be performed, then we may end up needing to rearrange quite a lot, add backslashes to protect the right things, and so on. The second problem is that it doesn't work very well if our exact command isn't known in advance -- e.g., if we're writing a WrapperScript.

Let's now consider a more realistic problem: we want to write a wrapper script that invokes make on a remote host, with the arguments provided by the user being passed along intact. This is a lot harder than it would appear at first, because we can't just mash everything together into one word -- the script's caller might use really complex arguments, and quotes, and pathnames with spaces and shell metacharacters, that all need to be preserved carefully. Fortunately for us, bash provides a way to protect such things safely: printf %q. Together with an array and a loop, we can write a wrapper:

# Bash
unset a i
for arg; do
  a[i++]=$(printf %q "$arg")
done
exec ssh remotehost make "${a[@]}"

If we also need to change directory on the remote host before running make, we can add that as well:

# Bash
unset a i
for arg; do
  a[i++]=$(printf %q "$arg")
done
exec ssh remotehost cd "$PWD" "&&" make "${a[@]}"

(If $PWD contains spaces, then it also need to be protected with the same printf %q trick, left as an exercise for the reader.)

Another workaround is to pass the command(s) as stdin to the remote shell, rather than as an argument. This won't work in all cases; it means the command being executed on the remote system can't use stdin for any other purpose, since we're tying up stdin to send our commands. But in the cases where it can be used, it works quite well:

# POSIX
ssh remotehost sh <<EOF
cd "$PWD" || exit
make CFLAGS"-g -O"
EOF

(Sanitizing $PWD, again, is left as an exercise for the reader. If you don't need to change directory on the remote host, you can just delete that line altogether.)

BashFAQ/096 (last edited 2020-03-10 20:44:49 by GreyCat)