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.

Jacob Stern
  • 359
  • 3
  • 8
  • 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 [`ProxyJump`s or `ProxyCommand`s](https://unix.stackexchange.com/q/317491/70524) (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