Run local script with local input file on remote host

13,343

Solution 1

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)

Solution 2

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

Solution 3

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.

Share:
13,343

Related videos on Youtube

Nester
Author by

Nester

Updated on September 18, 2022

Comments

  • Nester
    Nester over 1 year

    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)?

  • Nester
    Nester over 7 years
    The second example ssh host "$(cat local_script.sh)" < input worked great, and is an elegant solution as well. Thanks!
  • Jeremy
    Jeremy almost 5 years
    From the Bash manual: "The command substitution $(cat file) can be replaced by the equivalent but faster $(< file)."