5

I have a local script that I want to run on several remote servers. The command syntax I'm using is:

ssh <remote_server> "bash -s" < ./local_script.sh

This works fine, and even lets me pass parameters to local_script.sh. However, I'd really like to pass an input file to it, as in:

local_script.sh < local_file.txt

Combining these two statements gives:

ssh <remote_server> "bash -s" < ./local_script.sh < local_file.txt

When I run this, I get

bash: line 1: FOO: command not found
bash: line 2: BAR: command not found
bash: line 3: BAZ: command not found
...

where FOO, BAR, BAZ, etc. are the first word on each line of local_file.txt:

FOO     FOO_PARAM1
BAR     BAR_PARAM1
BAZ     BAZ_PARAM2
...

So, it looks like "bash -s" on the remote server is interpreting local_file.txt as a script file, instead of an input file to local_script.sh. Is there any way to fix this (other than creating a wrapper script)?

6

While ssh provides for two separate output streams (for stdout and stderr), it only feeds one input stream (stdin). So you'd need either to pass the script content and input file via different mechanisms.

For instance, one via a variable, one via stdin:

LC_CODE=$(cat local_script.sh) ssh host 'eval "$LC_CODE"' < input

(assuming both your ssh client passes the LC_* variable (SendEnv in ssh_config) and the sshd server accepts them (AcceptEnv in sshd_config))

Or simply pass the content of the local_script.sh as the remote shell code assuming the login shell of the remote user is the right one for the syntax of that script:

ssh host "$(cat local_script.sh)" < input

Or concatenate the code and input fed to ssh's stdin like with:

{
  echo '{'; cat local_script.sh; echo '}; exit'
  cat input
} | ssh host 'bash -s some arguments'

Here using bash because bash will take care of reading the input one byte at a time so as not to read past that }; exit line while not all other shells do that.

Or if sed on the remote host is GNU sed:

echo '#END-OF-SCRIPT' | cat local_script.sh - input |
  ssh host 'set some arguments; eval "$(sed -u "/^#END-OF-SCRIPT/q")"'

(here having the code evaluated by the login shell of the remote user and assuming it's Bourne-like)

| improve this answer | |
  • 1
    The second example ssh host "$(cat local_script.sh)" < input worked great, and is an elegant solution as well. Thanks! – Nester Sep 30 '16 at 13:10
  • From the Bash manual: "The command substitution $(cat file) can be replaced by the equivalent but faster $(< file)." – Jeremy Jul 17 '19 at 13:39
1

This works well and even lets you pass the contents of multiple files to the script:

ssh host "bash -s -- '$(cat local_file_1.txt)'  '$(cat local_file_2.txt)'" < local_script.sh

The explanation is that ssh will execute bash -s on login with the script arguments being everything after --. So $1 will be the contents of local_file_1.txt and $2 will be the contents of local_file_2.txt. bash -s reads commands from stdin which comes from < local_script.sh.

| improve this answer | |
0

Here is a more elegant solution using the BASH 'here-strings' operator ('<<<')

ssh -t host "/bin/bash <(base64 --decode <<<'$(base64 < script)' )"

IT takes the script, converts it to a base64 string, in the SSH command it converts it back and gives it to the bash as a pipe-filename.

For an script to ste up an interactive shell prefix the 'pipe filename' with "--rcfile"

ssh -t host "/bin/bash --rcfile <(base64 --decode <<<'$(base64 < script)' )"

More on... https://antofthy.gitlab.io/info/apps/ssh_remote_commands.txt

| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.