How to use SSH to run a local shell script on a remote machine?

Viewed 1439121

I have to run a local shell script (windows/Linux) on a remote machine.

I have SSH configured on both machine A and B. My script is on machine A which will run some of my code on a remote machine, machine B.

The local and remote computers can be either Windows or Unix based system.

Is there a way to run do this using plink/ssh?

22 Answers

Also, don't forget to escape variables if you want to pick them up from the destination host.

This has caught me out in the past.

For example:

user@host> ssh user2@host2 "echo \$HOME"

prints out /home/user2

while

user@host> ssh user2@host2 "echo $HOME"

prints out /home/user

Another example:

user@host> ssh user2@host2 "echo hello world | awk '{print \$1}'"

prints out "hello" correctly.

<hostA_shell_prompt>$ ssh user@hostB "ls -la"

That will prompt you for password, unless you have copied your hostA user's public key to the authorized_keys file on the home of user .ssh's directory. That will allow for passwordless authentication (if accepted as an auth method on the ssh server's configuration)

I've started using Fabric for more sophisticated operations. Fabric requires Python and a couple of other dependencies, but only on the client machine. The server need only be a ssh server. I find this tool to be much more powerful than shell scripts handed off to SSH, and well worth the trouble of getting set up (particularly if you enjoy programming in Python). Fabric handles running scripts on multiple hosts (or hosts of certain roles), helps facilitate idempotent operations (such as adding a line to a config script, but not if it's already there), and allows construction of more complex logic (such as the Python language can provide).

cat ./script.sh | ssh <user>@<host>

Try running ssh user@remote sh ./script.unx.

chmod +x script.sh    
ssh -i key-file root@111.222.3.444 < ./script.sh

I use this one to run a shell script on a remote machine (tested on /bin/bash):

ssh deploy@host . /home/deploy/path/to/script.sh
ssh user@hostname ". ~/.bashrc;/cd path-to-file/;. filename.sh"

highly recommended to source the environment file(.bashrc/.bashprofile/.profile). before running something in remote host because target and source hosts environment variables may be deffer.

if you wanna execute command like this temp=`ls -a` echo $temp command in `` will cause errors.

below command will solve this problem ssh user@host ''' temp=`ls -a` echo $temp '''

If the script is short and is meant to be embedded inside your script and you are running under bash shell and also bash shell is available on the remote side, you may use declare to transfer local context to remote. Define variables and functions containing the state that will be transferred to the remote. Define a function that will be executed on the remote side. Then inside a here document read by bash -s you can use declare -p to transfer the variable values and use declare -f to transfer function definitions to the remote.

Because declare takes care of the quoting and will be parsed by the remote bash, the variables are properly quoted and functions are properly transferred. You may just write the script locally, usually I do one long function with the work I need to do on the remote side. The context has to be hand-picked, but the following method is "good enough" for any short scripts and is safe - should properly handle all corner cases.

somevar="spaces or other special characters"
somevar2="!@#$%^"
another_func() {
    mkdir -p "$1"
}
work() {
    another_func "$somevar"
    touch "$somevar"/"$somevar2"
}
ssh user@server 'bash -s' <<EOT
$(declare -p somevar somevar2)    # transfer variables values
$(declare -f work another_func)   # transfer function definitions
work                              # call the function
EOT

The answer here (https://stackoverflow.com/a/2732991/4752883) works great if you're trying to run a script on a remote linux machine using plink or ssh. It will work if the script has multiple lines on linux.

**However, if you are trying to run a batch script located on a local linux/windows machine and your remote machine is Windows, and it consists of multiple lines using **

plink root@MachineB -m local_script.bat

wont work.

Only the first line of the script will be executed. This is probably a limitation of plink.

Solution 1:

To run a multiline batch script (especially if it's relatively simple, consisting of a few lines):

If your original batch script is as follows

cd C:\Users\ipython_user\Desktop 
python filename.py

you can combine the lines together using the "&&" separator as follows in your local_script.bat file: https://stackoverflow.com/a/8055390/4752883:

cd C:\Users\ipython_user\Desktop && python filename.py

After this change, you can then run the script as pointed out here by @JasonR.Coombs: https://stackoverflow.com/a/2732991/4752883 with:

`plink root@MachineB -m local_script.bat`

Solution 2:

If your batch script is relatively complicated, it may be better to use a batch script which encapsulates the plink command as well as follows as pointed out here by @Martin https://stackoverflow.com/a/32196999/4752883:

rem Open tunnel in the background
start plink.exe -ssh [username]@[hostname] -L 3307:127.0.0.1:3306 -i "[SSH
key]" -N

rem Wait a second to let Plink establish the tunnel 
timeout /t 1

rem Run the task using the tunnel
"C:\Program Files\R\R-3.2.1\bin\x64\R.exe" CMD BATCH qidash.R

rem Kill the tunnel
taskkill /im plink.exe

This bash script does ssh into a target remote machine, and run some command in the remote machine, do not forget to install expect before running it (on mac brew install expect )

#!/usr/bin/expect
set username "enterusenamehere"
set password "enterpasswordhere"
set hosts "enteripaddressofhosthere"
spawn ssh  $username@$hosts
expect "$username@$hosts's password:"
send -- "$password\n"
expect "$"
send -- "somecommand on target remote machine here\n"
sleep 5
expect "$"
send -- "exit\n"

You can use runoverssh:

sudo apt install runoverssh
runoverssh -s localscript.sh user host1 host2 host3...

-s runs a local script remotely


Useful flags:
-g use a global password for all hosts (single password prompt)
-n use SSH instead of sshpass, useful for public-key authentication

If it's one script it's fine with the above solution.

I would set up Ansible to do the Job. It works in the same way (Ansible uses ssh to execute the scripts on the remote machine for both Unix or Windows).

It will be more structured and maintainable.

It is unclear if the local script uses locally set variables, functions, or aliases.

If it does this should work:

myscript.sh:

#!/bin/bash

myalias $myvar
myfunction $myvar

It uses $myvar, myfunction, and myalias. Let us assume they is set locally and not on the remote machine.

Make a bash function that contains the script:

eval "myfun() { `cat myscript.sh`; }"

Set variable, function, and alias:

myvar=works
alias myalias='echo This alias'
myfunction() { echo This function "$@"; }

And "export" myfun, myfunction, myvar, and myalias to server using env_parallel from GNU Parallel:

env_parallel -S server -N0  --nonall myfun ::: dummy

Extending answer from @cglotr. In order to write inline command use printf, it useful for simple command and it support multiline using char escaping '\n'

example :

printf "cd /to/path/your/remote/machine/log \n tail -n 100 Server.log" | ssh <user>@<host> 'bash -s'

See don't forget to add bash -s

There is another approach ,you can copy your script in your host with scp command then execute it easily .

First, copy the script over to Machine B using scp

[user@machineA]$ scp /path/to/script user@machineB:/home/user/path

Then, just run the script

[user@machineA]$ ssh user@machineB "/home/user/path/script"

This will work if you have given executable permission to the script.

Related