Wrapping ssh inside a script, and passing arbitrary commands to another program

A way to solve this problem, which I presented in a previous post, was found with the help from the gurus on #bash. The easy way to make this work is to save the arguments to a file, one per line, and read them back at the other end. Using bash syntax for arrays "${a[@]}" which quotes every element separately, the original arguments can be reconstructed at the remote end, with the same separation they had at the origin.

Here is the code that does just that:

$ cat ssh-wrapper
#!/bin/bash
tmp=$(mktemp -p $(pwd))
for i; do
  echo $i >>$tmp
done
ssh -t localhost "$(pwd)/remote_script" "$(pwd)" "./prog" "$tmp"
rm -f $tmp

$ cat remote-script
declare -a a
declare i
while read; do
  echo $REPLY
  a[i++]="$REPLY"
done <$3
cd $1 && $2 "${a&#91;@&#93;}"

$ cat prog
#!/bin/bash
eval "$@"
# This program traps INT and queries the user for input
function process_int {
  echo -n "INT> "
  read LINE
  echo $LINE
  exit 2
}
trap process_int SIGINT
sleep 30
$

Now it works as expected for both types of end user quoting:

# Double quotes quoting
$ ./ssh-wrapper "FOO='a b' && echo ' hi' && echo $FOO"
 hi

^CINT> it works!
it works!
Connection to localhost closed.

# Single quotes quoting
$ ./ssh-wrapper 'FOO="a b" && echo " hi" && echo $FOO'
 hi
a b
^CINT> it works too!
it works too!
$
Connection to localhost closed.

If the two hosts were on different file systems, scp could be used to send the arguments over to a file on the remote host. You could even send environment information this way.

Experiment in wrapping ssh inside a script and passing arbitrary commands to another program

Note: This has been solved. Please see the answer here.

Wrapping ssh inside a script and passing it arbitrary commands is hard. What I am trying to do here is to come up with a generic way to invoke an ssh-wrapper, have it record the $(pwd), perform an ssh to a remote host, and at the remote host, reposition inside $(pwd), and run an arbitrary, possibly interactive, program.

Let’s first start with an ssh-wrapper

$ cat ssh-wrapper
#!/bin/bash
ssh -t localhost "$(pwd)/remote_script '$*'"

Then we define the remote-script

$ cat remote-script
#!/bin/bash
eval $*

Now let’s try some commands, and compare with what bash -c does:

$ ssh-wrapper "echo ' hi'"
hi
$ bash -c "echo ' hi'"
 hi

Notice that bash -c correctly renders the space preceding hi, but ssh-wrapper does not. This indicates potential future problems. Let’s try another case:

$ ssh-wrapper "echo hi && echo hello"
hi
hello
$ bash -c "echo hi && echo hello"
hi
hello

Nothing here. Let’s try another one:

$ bash -c "FOO='a b' && echo ' hi' && echo $FOO"
 hi

$ ssh-wrapper "FOO='a b' && echo ' hi' && echo $FOO"
remote_script: line 4: b: command not found

It starts to hurt here. The bash -c does the correct thing ($FOO is expanded before the value is set, which is correct), but in the ssh-wrapper, things go wrong. However, a different quoting produces a better outcome:

$ ssh-wrapper 'FOO="a b" && echo " hi" && echo $FOO'
 hi
a b
$ bash -c 'FOO="a b" && echo " hi" && echo $FOO'
 hi
a b

This works better, and even the space preceding “hi” is present. However, we can see that ssh-wrapper is not very end user proof. And end users are non-linear: give them that script, and they will break it right away.

Let’s modify the original scripts to position the remote execution in the correct path and add a potentially interactive program:

$ cat ssh-wrapper
#!/bin/bash
ssh -t localhost "$(pwd)/remote_script $(pwd) ./prog '$*'"

$ cat remote-script
#!/bin/bash
cd $1 && $2 $3

$ cat prog
#!/bin/bash
eval "$@"
# This program traps INT and queries the user for input
function process_int {
  echo -n "INT> "
  read LINE
  echo $LINE
  exit 2
}
trap process_int SIGINT
sleep 30

The difference here is that the remote-script will place us in the path where the ssh-wrapper was originally called before calling ./prog. Now we try our command again:

$ ssh-wrapper 'FOO="a b" && echo " hi" && echo $FOO'
 hi
a b
^CINT> it works!
it works!
$

You need to hit ctrl-c to get out here, then type some arbitrary string. It works, but is far from being robust: the way to make it fail is by changing the quoting:

$ ssh-wrapper "FOO='a b' && echo ' hi' && echo $FOO"
Executing FOO=a
^CINT> ^CConnection to localhost closed.
$

We are not better off. The prog did not interpreted the whole command, only FOO=a!

This is annoying, because I may want to allow variables to be expanded on the original command line, and hence it would be very nice to allow users to use double-quotes in their argument to ssh-wrapper. In fact, it is necessary to allow either type of quoting to work, bash -c works with both, so should ssh-wrapper.

Let’s change remote-script to pass all arguments after $2:

$ cat remote-script
#!/bin/bash
cd $1 && $2 ${@:3}

And we try again:

$ ssh-wrapper "FOO='a b' && echo ' hi' && echo $FOO"
./prog: line 6: b: command not found
^CINT> ^CConnection to localhost closed.

This time, the quoting around the variable assignment is gone, so b is interpreted as a command! At this point I have run out of ideas on how to make this work.

This has been solved. Please see the answer here.

environment modules interaction with ssh

Environment modules is a nice software environment that lets you switch verilog simulator version very easily without having to edit your environment (.cshrc or other). But it seems to have some drawbacks. One of which is that is does not return proper exit status when it fails, making it hard to write scripts that fail when they should. The other one is it does not play well with ssh, or so it seems.

When I hit ctrl-c on VCS at runtime, it will drop the command line interface, aka the verilog CLI. When I try to make this work with ssh and environment modules, I have a nasty surprise: environment modules interferes somewhere.

Let’s first emulate the verilog command line with a simple script, since you don’t need an expensive verilog simulator to see the problem:

$ cat prog
#!/bin/bash

# This program traps INT and queries the user for input

function process_int {
  echo -n "cli> "
  read LINE
  echo to stdout $LINE
  echo to stderr $LINE >&2
  exit 2
}

trap process_int SIGINT
echo test stdout
echo test stderr >&2
sleep 30
$

Next, we want to run this script through ssh and environment modules, hit ctrl-c and be dropped to the cli> prompt:

$ ssh -t localhost 'tcsh -c "source Modules/init/tcsh && module list && ./prog"'

Currently Loaded Modulefiles:
  1) make/3.81a
test stdout
test stderr


Connection to localhost closed.
$

Well, this did not drop to the command line interface. Let’s try without module list:

$ ssh -t localhost 'tcsh -c "source Modules/init/tcsh && ./prog"'
test stdout
test stderr
cli> t
to stdout t
to stderr t


Connection to localhost closed.
$

This time, it has let me typed something on the cli> command line prompt. Okay, so there is some interaction between ssh and modules. I’ve struggled with this for a few days and it is getting annoying. The same commands will work with rsh, but rsh does not return the a proper exit status, so I really do want to use ssh.