Ssh multiple hops to execute commands with arguments

Hi

I need to write a script to ssh through several hops (e.g. HostA-HostB-HostC-HostD), where Host A does not have direct assess to HostC ; HostB cannot access HostD directly.

when I ssh 3 hops and run command with arg1, arg2 and redirect the output to a file, e.g.
HostA> ssh -t HostB ssh -t HostC ssh HostD command arg1 arg2 > output.txt

but it doesn't work as expect it does not pass 2nd argument to command and the output file will be written in HostC, instead of in HostD as expected.

I did some tests to see if quotes or double quotes pairs helps, e.g
(Test 1)

HostA> ssh -t HostB ssh -t HostC  ssh  HostD  hostname
HostD
Connection to  HostC  closed.
Connection to  HostB  closed.

(Test 2)

HostA> ssh -t HostB ssh -t HostC  ssh  HostD  hostname;hostname
HostD
Connection to  HostC  closed.
Connection to  HostB  closed.
HostC

(Test 3)

HostA> ssh -t HostB ssh -t HostC  ssh  HostD  "hostname;hostname"
HostD
Connection to  HostC  closed.
HostB
Connection to  HostB  closed.

(Test 4)

HostA> ssh -t HostB ssh -t HostC  "ssh HostD   hostname;hostname"
HostD
Connection to  HostC  closed.
Connection to  HostB  closed.
HostB

(Test 5)

HostA> ssh -t HostB "ssh -t HostC  ssh HostD   hostname;hostname"
HostD
Connection to  HostC  closed.
Connection to  HostB  closed.
HostB

(Test 6)

HostA> ssh -t HostB ssh -t HostC  ssh HostD   hostname > test.txt
Connection to HostB closed 
(in HostA)

HostA> cat test.txt 
HostD
Connection to HostD closed.
Connection to HostC closed.

(Test 6)

HostA> ssh -t HostB ssh -t HostC  ssh HostD   hostname > test.txt
Connection to HostB closed 
(in HostA)

HostA> cat test.txt 
HostD
Connection to HostC closed.

(Test 7)

HostA> ssh -t HostB ssh -t HostC  ssh HostD   "hostname > test.txt"
Connection to HostC closed
Connection to HostB closed 
(in HostB)

HostB> cat test.txt 
HostD

(Test 8)

HostA> ssh -t HostB ssh -t HostC  "ssh HostD   hostname > test.txt"
Connection to HostC closed
Connection to HostB closed 
(in HostB)

HostB> cat test.txt 
HostD

(Test 9)

HostA> ssh -t HostB "ssh -t HostC  ssh HostD   hostname > test.txt"
Connection to HostC closed
Connection to HostB closed 
(in HostB)

HostB> cat test.txt 
HostD

(Test 10)

HostA> ssh -t HostB "ssh -t HostC  ssh HostD   'hostname > test.txt' "
Connection to HostC closed
Connection to HostB closed 
(in HostC)

HostC> cat test.txt 
HostD

(Test 11)

HostA> ssh -t HostB "ssh -t HostC  'ssh HostD   hostname > test.txt' "
Connection to HostC closed
Connection to HostB closed 
(in HostC)

HostC> cat test.txt 
HostD

(Test 11)

HostA> ssh -t HostB "ssh -t HostC  'ssh HostD   "hostname > test.txt" ' "
Connection to HostB closed 
(in HostA)

HostA> cat test.txt 

bash: -c: line 0: unexpected EOF while looking for matching `''
bash: -c: line 1: syntax error: unexpected end of file

After these tests, I still cannot find the logics of passing arguments correctly in ssh, can anyone help how to get it work correctly?

Thanks!

Rgds,
Dominic

First off -- what exactly is your goal? As far as I can tell, test 1 did exactly what you wanted.

It's not a question of ssh, exactly -- the problem is that you're going through multiple layers of shell. To prevent things from splitting locally, you quote them.

One command deeper, you have to put quotes in quotes -- either single-quotes, or escaped double-quotes -- so the quotes don't "disappear" when they're processed by the local shell.

One command deeper, you have to start escaping them, so they don't disappear here, get processed on the second server, so the third server gets a command its happy with.

Four deep... It starts getting impractical, you end up escaping escaped things, doubling and quadrupling and octupling the number of backslashes to get enough for the last layer to consider it "one" backslash.

I would avoid that completely, and feed text into standard input instead. That way it does not fly through 5 levels of parsing and quote removal.

ssh server1 ssh server2 ssh server3 ssh server4 ssh server5 exec /bin/sh -s <<EOF
hostname
echo ${LOCALVARIABLE}
echo \${REMOTEVARIABLE}
EOF
1 Like

You might want to go to the server where you want the command to run and write a shell script that defines all of the variables that it needs and doesn't rely on a .bashrc or any other login script, ideally with no input, and get that to work. Then exit out of that server and in the next to last server before logging into the final server write a shell script that calls the script you just wrote and passes any variables that it might need and test it, then repeat that process with each previous server.

The benefit will be that you are testing each level as you go, and not trying to figure out why something doesn't work when its 5-10 levels deep. Hopefully I am not helping you to hack someone else's server. :wink:

Have you considered using the ssh ProxyCommand feature to handle this ssh tunneling automatically

There are heaps of documents on the web about doing this sort of thing for example:

Transparent Multi-hop SSH

Note: newer version of ssh (version 2 and later) don't even need the netcat (nc) command and will work with the ssh -w host:port option.

2 Likes