How can I execute local script on remote machine and include arguments?
You were pretty close with your example. It works just fine when you use it with arguments such as these.
Sample script:
$ more ex.bash
#!/bin/bash
echo $1 $2
Example that works:
$ ssh serverA "bash -s" < ./ex.bash "hi" "bye"
hi bye
But it fails for these types of arguments:
$ ssh serverA "bash -s" < ./ex.bash "--time" "bye"
bash: --: invalid option
...
What's going on?
The problem you're encountering is that the argument, -time
, or --time
in my example, is being interpreted as a switch to bash -s
. You can pacify bash
by terminating it from taking any of the remaining command line arguments for itself using the --
argument.
Like this:
$ ssh root@remoteServer "bash -s" -- < /var/www/html/ops1/sysMole -time Aug 18 18
Examples
#1:
$ ssh serverA "bash -s" -- < ./ex.bash "-time" "bye"
-time bye
#2:
$ ssh serverA "bash -s" -- < ./ex.bash "--time" "bye"
--time bye
#3:
$ ssh serverA "bash -s" -- < ./ex.bash --time "bye"
--time bye
#4:
$ ssh < ./ex.bash serverA "bash -s -- --time bye"
--time bye
NOTE: Just to make it clear that wherever the redirection appears on the command line makes no difference, because ssh
calls a remote shell with the concatenation of its arguments anyway, quoting doesn't make much difference, except when you need quoting on the remote shell like in example #4:
$ ssh < ./ex.bash serverA "bash -s -- '<--time bye>' '<end>'"
<--time bye> <end>
On Handling Arbitrary Arguments
If you're really only using a single string, ie. -time Aug 18 18
, then you can simply hardcode it, and existing answers tell you how to do that adequately. On the other hand, if you need to pass unknown arguments through (like a message to be displayed on the other system, or the name of a file created where end-users could control its name), then more care is needed.
With bash
or ksh
as /bin/sh
If your remote /bin/sh
is provided by bash or ksh, you can safely do the following with an untrusted argument list, such that even malicious names (like $(rm -rf $HOME).txt
) can be passed as arguments safely:
runRemote() {
local args script
script=$1; shift
# generate eval-safe quoted version of current argument list
printf -v args '%q ' "$@"
# pass that through on the command line to bash -s
# note that $args is parsed remotely by /bin/sh, not by bash!
ssh user@remote-addr "bash -s -- $args" < "$script"
}
With Any POSIX-Compliant /bin/sh
To be safe against sufficiently malicious argument data (attempting to take advantage of the non-POSIX compliant quoting used by printf %q
in bash when nonprintable characters are present in the string being escaped) even with a /bin/sh
that is baseline-POSIX (such as dash
or ash
), it gets a bit more interesting:
runRemote() {
local script=$1; shift
local args
printf -v args '%q ' "$@"
ssh user@remote-addr "bash -s" <<EOF
# pass quoted arguments through for parsing by remote bash
set -- $args
# substitute literal script text into heredoc
$(< "$script")
EOF
}
Usage (for either of the above)
The functions given above can then be invoked as:
# if your time should be three arguments
runRemote /var/www/html/ops1/sysMole -time Aug 18 18
...or...
# if your time should be one string
runRemote /var/www/html/ops1/sysMole -time "Aug 18 18"