1

Similar to my question here, except with multiple ssh, and similar to this question but with white space in the arguments...

I would like to run a local script, test.sh, on a remote server via multiple ssh. test.sh takes an argument that often has multiple words.

test.sh:

#!/bin/bash
while getopts b: opt;
do
  case $opt in
    b)
       bval="$OPTARG"
       ;;
  esac
done
echo $bval

call_test.sh

#!/bin/bash
# Do some stuff
PHRASE="multi word arg"
ssh -A user@host1 "bash -s" -- < ./test.sh -b "${PHRASE@Q}"

and run ./call_test.sh, this correctly outputs

multi word arg

But when I change the last line of call_test.sh to the following:

ssh -A user@host1 ssh -A user@host2 "bash -s" -- < ./test.sh -b "${PHRASE@Q}"

and run ./call_test.sh, it outputs:

multi

Basically, changing from single SSH to multiple SSH breaks my multi-word argument.

I think that the multiple ssh commands are unwrapping the quotes for the multi-word argument at each step, but I'm not sure how to prevent that. Any ideas how to successfully pass the multi-word argument to the script running across multiple ssh?

Edit:

I believe this answer gets at the problem that I'm running into.

  • Each additional SSH hop (ssh host1 ssh host2 ... ssh hostn) will require an additional layer of quotes. That way lies insanity. You should instead use ProxyJumps or ProxyCommands (see edit in the question as well as the answer), so that SSH internally manages the hops, so you only need to quote for the ssh command you actually execute. – muru Mar 17 '20 at 05:45
  • @muru what if the intermediate host won't let you do forwardings? (e.g. with DisableForwarding yes) How would the -J work? –  Mar 17 '20 at 13:25
  • @pizdelect it should, since DisableForwarding is about forwarding ports or X11 or agents. Unless there's a ForceCommand blocking execution of anything else, proxyjump should work. Or, if OP can run netcat, there's also that as a worst case option. – muru Mar 17 '20 at 15:00
  • Oh wait, ProxyJump does rely on TCP forwarding. In that case, netcat or a similar command as proxycommand is probably the only option – muru Mar 17 '20 at 15:05

1 Answers1

1

If you pass it through 2 ssh commands, you have to quote-escape the string TWICE. Do that with an extra PHRASE=${PHRASE@Q} assignment.

Beware however that both ${var@Q} and printf %q (described below) will use the $'...' quote-escape format, which may not be supported by the remote shell.

After fixing the last line of your test.sh script to echo "$bval" instead of echo $bval:

PHRASE="multi word     arg"
PHRASE=${PHRASE@Q}
ssh -A user@host1 ssh -A user@host2 "bash -s" -- < ./test.sh -b "${PHRASE@Q}"

multi word arg

Instead of the ${var@P} expansion form which is not supported in older versions of bash, you can use printf -v var %q:

PHRASE="multi word     arg"
printf -v PHRASE %q "$PHRASE"
printf -v PHRASE %q "$PHRASE"
ssh -A user@host1 ssh -A user@host2 "bash -s" -- < ./test.sh -b "$PHRASE"

Also, instead of giving your script via stdin, which is inefficient (bash will do a read system call for each byte when reading the script from stdin), you can just put the whole script and its arguments in a variable:

printf -v cmd 'bash -c %q bash -b %q' "$(cat test.sh)" 'multi word   wahterve'
printf -v cmd %q "$cmd"
ssh localhost ssh localhost "$cmd"

multi word wahterve

  • Excellent answer, both options work perfectly. I appreciate the extra notes on efficiency and backwards compatibility. – Jacob Stern Mar 17 '20 at 21:11