170

I have written a script that runs fine when executed locally:

./sysMole -time Aug 18 18

The arguments "-time", "Aug", "18", and "18" are successfully passed on to the script.

Now, this script is designed to be executed on a remote machine but, from a local directory on the local machine. Example:

ssh root@remoteServer "bash -s" < /var/www/html/ops1/sysMole

That also works fine. But the problem arises when I try to include those aforementioned arguments (-time Aug 18 18), for example:

ssh root@remoteServer "bash -s" < /var/www/html/ops1/sysMole -time Aug 18 18

After running that script I get the following error:

bash: cannot set terminal process group (-1): Invalid argument
bash: no job control in this shell

Please tell me what I'm doing wrong, this greatly frustrating.

2

4 Answers 4

235

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>
9
  • 9
    you are an Genius! What do I have to do to get to your level? I have alot of work cut out. Thanks so much.
    – AllenD
    Aug 20, 2013 at 2:21
  • 27
    @AllenD - just keep asking questions, and try and participate on the site as much as you can. I always try and learn something new each day. Before your question I didn't know how to do this either. 8-). Thanks for your question!
    – slm
    Aug 20, 2013 at 2:25
  • 8
    note that the redirection can appear at any point in the command: for example bash -s -- --time bye < ./ex.bash or even < ./ex.bash bash -s -- --time bye. this is because the shell first takes out the redirection instruction (regardless of where it is in the command), then sets up the redirection, then executes the rest of the command line with the redirection in place.
    – Lesmana
    Aug 20, 2013 at 9:30
  • @sim I'm trying to do this exact same thing, but from within a script, not from the command line. Any idea of how to do this? For some reason, enclosing your exact answer in backticks does not work at all. It instead runs the script specified locally, and the output is sent as a command to the bash over ssh
    – krb686
    Mar 3, 2015 at 21:42
  • 1
    @krb686 - I'd ask it as a new Q.
    – slm
    Mar 4, 2015 at 1:33
8

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"
1
  • Nice script. But beware that globbing (expansion of ) always runs on local machine, the bash running the script. 'runRemote rsync --exclude=".log" /from/* /to' will always get an unquoted * expanded by the bash running the script, ie. on the local machine. Its possible to disable globing with 'set -f'. But it doesn't really help, as * and "*.log" both just becomes strings containing a * so we cannot tell the difference afterwards.
    – arberg
    Mar 30, 2021 at 15:28
0

Enclose the command(s) in quotes as follows:

ssh myserver "date +\"Date: %Y%m%d %H:%M\";printf \"Uptime: \";uptime;printf \"uname: \";uname -a"
Date: 20200409 20:02
Uptime:  20:02:29 up 66 days, 55 min,  2 users,  load average: 0.06, 0.05, 0.08
uname: Linux zltcmtn23aecc1rx7322 4.4.0-116-generic #140-Ubuntu SMP Mon Feb 12 21:23:04 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux
Connection to 105.31.182.217 closed.
-3

say a.a is a local file that contains ls

$ssh servername "cat | bash" < a.a

change 127.0.0.1 to whatever your remote ip is

these two give a message about pseudo tty allocation but they work.

$ cat a.a | ssh 127.0.0.1

$ ssh 127.0.0.1 <a.a

Or

$ cat a.a | ssh 127.0.0.1 bash or

$ ssh 127.0.0.1 bash < a.a

6
  • 2
    I'm struggling to see in how far this answers the question.
    – ChrisWue
    Sep 8, 2016 at 1:39
  • The quality and formatting is doubtful. I don't see any new useful information that's not present in the accepted answer either. Sep 8, 2016 at 4:07
  • @JuliePelletier I gave some 'cat' examples not in the accepted answer. It's good to know alternative ways of doing things.
    – barlop
    Sep 8, 2016 at 5:18
  • That's not a good thing; your cats are useless. Sep 8, 2016 at 5:20
  • 2
    Oh my goodness. I left out the link to Useless use of cat because I assumed that everybody had at least heard of the phrase by now; apparently that was a bad assumption on my part. Sep 8, 2016 at 5:43

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .