3

Using Linux, bash, and OpenSSH, I can execute a shell script on a remote host in at least three ways:

# Method 1: Script is never stored on the remote host
ssh <username>@<hostname> 'bash -s' < myShellScript

# Method 2: Script is permanently stored on the remote host
ssh <username>@<hostname> 'myShellScript'

# Method 3: Copy script to the remote host, execute it, and delete it
scp myShellScript <username>@<hostname>:~
ssh <username>@<hostname> 'myShellScript; rm -f myShellScript'

I prefer method 1 to method 2 because it prevents the proliferation of copies of the script and the associated maintenance headaches.

I prefer method 1 to method 3 because it is faster and generally "cleaner" than copying and deleting the script.

But there's a problem I need to get around if I am to succeed in my goal of sticking with method 1. myShellScript is of this form:

#!/bin/bash

# Do stuff...
myProgram input myProgramInputFile
# Do more stuff...

Here, myProgram is a standard utility that is available on all remote hosts I might want to interact with. However, myProgramInputFile is a regular file that exists only on my local machine. It does not exist on any of the remote hosts.

I'd like to be able to "bundle up" myShellScript and myProgramInputFile into one nice "package" that gets executed on the remote host via SSH without explicitly storing anything on the remote file system, either permanently or temporarily. Is this possible?

2
  • Do you have the ability to share files out via, say, NFS or something like that? Commented Jun 27, 2017 at 0:32
  • See this question. Your Method 1 may behave against your expectations if myShellScript does certain "stuff". Know the pitfalls if you keep using this method. Commented Jul 6, 2017 at 6:54

1 Answer 1

2

I would rather copy at least myProgramInputFile (Method 3 alike); I think it would be more robust. There is, however, a way to make it work with the sole Method 1.



My approach

I advise you to make a (local) script that copies the appropriate file to the remote side, triggers execution and cleans afterwards.

We will need three local files: myLocalScript (executable), myRemoteScript and myProgramInputFile.

The content of myLocalScript:

#!/bin/bash

# step 1: storing command line arguments to meaningful names
ssh_command="ssh $1"
script="$2"
input="$3"

# step 2: creating remote temporary file
remote_input=$($ssh_command 'mktemp')

# step 3: copying the content of local input file to the remote temporary file
cat "$input" | $ssh_command "cat > \"$remote_input\""

# step 4: sourcing the script to the remote side
$ssh_command "input=\"$remote_input\" bash -s" < "$script"

# step 5: removing the remote temporary file
$ssh_command "rm \"$remote_input\""

myRemoteScript will look like this:

# shebang is not needed, this script will effectively be sourced

# Do stuff…
myProgram input "$input"
# Do more stuff…

The files myProgramInputFile (local) and myProgram (remote) will stay the way they are in your current setup.

Local usage:

./myLocalScript <username>@<hostname> myRemoteScript myProgramInputFile

Technical notes and explanations:

  • In step 2 mktemp creates a file in a temporary directory on the remote machine. We don't want to use mktemp -u to avoid the situation when somebody else actually creates a file with the same name between our step 2 and step 3.
  • In step 3 we could use scp if we used mktemp -u. The remote temporary file already exists so we use cat to write to it.
  • In step 4 myRemoteScript needs to know the path to the temporary file in the remote machine context. That's why we pass our local $remote_input as input variable to the remote bash. This way when myRemoteScript is fed to the said bash, the $input in it indicates the temporary file path.
  • If we break the remote bash (say, with single Ctrl + c) then myLocalScript will advance to step 5 and do the cleaning anyway. This is the reason why the cleaning is not at the end of myRemoteScript, although it could be.


Sole Method 1 approach

Thanks to Here Documents you can embed myProgramInputFile into myShellScript.

This will be easy if myProgramInputFile is a text file and myProgram can read stdin (maybe with myProgram input - syntax? or maybe when input is omitted?)

myShellScript will look like this:

# shebang is not needed, this script will effectively be sourced

# Do stuff…

myProgram input - <<"EOF"
The content of myProgramInputFile is pasted here,
it continues here
and here,
and so on.
EOF

# Do more stuff…

Then we will run it as in Method 1:

ssh <username>@<hostname> 'bash -s' < myShellScript

If myProgram cannot read stdin but the remote system allows you to use /proc/self/ then we should write the crucial line in myShellScript like this:

myProgram input /proc/self/fd/0 <<"EOF"

If myProgramInputFile is more than text then we should encode it locally (see uuencode, base64) and decode on the remote side. We should also check if there's no EOF line in the encoded text, change the here document delimiter if so. Note that base64 doesn't use _ so E_O_F is perfectly safe in this case (but maybe not with uuencode, I don't know).


As a proof of concept I gzipped the Super User favicon, encoded it with base64 and embedded in the script. I chose to demonstrate /proc/self/fd/0 approach with cp but it might be cat > ~/SUfavicon.ico as well; or better gzip -cd > ~/SUfavicon.ico earlier in the pipe.

And of course in your case there would be myProgram instead of cp.

Note in this case {} are important, they make the here document be directed to base64 -d.

# shebang is not needed, this script will effectively be sourced

{ base64 -d | gzip -cd | cp /proc/self/fd/0 ~/SUfavicon.ico ; } <<"E_O_F"
H4sICE3cXFkCA2Zhdmljb24uaWNvAO2XT2jTUBzHf2lnO1HXIAiKYHuSIYhzoCgIlV1EdtDDTmMg
CIKI7DQ9Taq71Il0VfBSmP9QmKziYRd3GCoqu40JIhtiB7sIOm1AcJ1r8/y+5bXEZ1KbtH07uMCH
3/pLvvm8lzYvC5FGAdJ1Qo3R+RaivUQUi1mfx9BPodeOns77ZPXXthZy25IgX2bbZkrz5uIjmvz+
lPKCBZAEmkOeH8/ABOjXt1AXb86NUg+Ov/AtSwOoy6gl1PYq+UtuA0RuHjCw32d+Dn6znrzwH/SZ
fy78w6gBr3lkOpH9DPg53nrNLzykVpzjDp8DatFLfrCXArkHlBLzfwcOgUANed4zE320/es4fRDz
7/Zw/XiP3TxHm5bGK9f/mJ/rv5StfP++8sjmxLU/7DWP7FHkiqhfUNuq5J+AM7h/jvDm/F06geOv
AJ5bBX0uwyvnTV5x/2bE/fsGmQK8U6hdTsGhsMXItI3XNq5hPQlbVPZjtRiKgKi1zvDV5bh9ndH/
9jDG/iCi6yuAVaOcXczuYy78ALPgOtjhND/Za/OzBvjtfARb19HP6a3Dr5GHDS4NvJL8g3795GOD
77bkv6rYn97w/9f+Ecl/Q7G/X/K/B0GF/t1gRRpDBoRU+MUYLjusgznR39lsvxjDAFh2GMdPFX4x
hrNOzwQF178NPHNwl8BEk39//Bk06eC9BfY06veH/Z1gFNwHKRAW/pMO8+5p9P2H/Wnp+A7hT0ru
qWasP1X88vqbbpL/FJgBs+AF0FX6PTx/Nvzr6x9W7B+T/BdV+fm7BshL/g6F/ozknq7z/Uur0bsL
PJbcv8ABRe9/Jekz///ntNt4m/z+N+M27xr8qz79n8A90M2fv//6vso+I0QJEF8jYcQNVnAjCiIG
KwYNZgIWtP4uhA2zNQriFRzz/NwwJF5yHzNCGgiy3wHSerE2FQAA
E_O_F

Run it as before:

ssh <username>@<hostname> 'bash -s' < myShellScript

Then you will find ~/SUfavicon.ico on the remote machine.

1
  • Thank you so much for such a thoroughly and clearly explained proposal! I will look it over closely later this morning. Commented Jul 5, 2017 at 14:26

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.