57

I am looking for a way to push configuration from one central machine to several remote machines without the need to install anything on the remote machines.

The aim is to do something like you would find with tools like cfengine, but on a set of machines that don't have agents set up. This might actually be a good technique of setting up cfagent on a set of existing remote machines.

2

9 Answers 9

65

You can pass a script and have it execute ephemerally by piping it in and executing a shell.

e.g.

echo "ls -l; echo 'Hello World'" | ssh me@myserver /bin/bash

Naturally, the "ls -l; echo 'Hello World'" part could be replaced with a bash script stored in a file on the local machine.

e.g.

cat script.sh | ssh me@myserver /bin/bash

Cheers!

4
  • 2
    How do I make this run as sudo on the remote system.e.g. If I was logged on the remote server I would usually run this as sudo -u testuser script.sh
    – Sharjeel
    Commented Mar 14, 2013 at 11:53
  • What if the script I am calling involves user interactions??? Commented Mar 18, 2013 at 5:14
  • How would I suppress the banner then?
    – Gqqnbig
    Commented Feb 25, 2021 at 15:34
  • 1
    The /bin/bash part is not necessary unless you are worried that the user's login shell on the remote machine might be something other than bash and you want to ensure that bash is used or if you want to supply invocation options to bash, like -s. If that's really the case, you can make it more efficient by changing it to exec /bin/bash. Commented May 25, 2021 at 1:32
30

There are several ways to do it.

1:

ssh user@remote_server 'bash -s' < localfile

2:

cat localfile  | ssh user@remote_server

3:

ssh user@remote_server "$(< localfile)"

number 3 is my prefered way, it allows interactive commands e.g. sudo -S service nginx restart

(#1 and #2 will consume the rest of the script as input for the password question when you use sudo -S.)

4
  • 6
    Regarding running local script on remote machine -- Is there a way to send variable as an argument to the remote machine.. ? i.e along with the script, i want to send a variable (having multiple lines) as argument to the remote machine. The script then intends to use the variable.
    – user257194
    Commented Dec 4, 2014 at 23:08
  • You are a savior. Thanks for such descriptive answer. Commented Mar 14, 2023 at 12:58
  • @user257194: I had the similar scenario, could not send the variable value to remote machine., so found a work around, I am not writing the data on a file on remote server and reading the value from remote server. Commented Mar 14, 2023 at 13:00
  • those of us who might be puzzled as to what $(< foo) does, it performs the same work as $(cat foo), just more efficiently. yes. the ability to keep stdin working is a great advantage with method 3. short of resorting to all sorts of parsing shenanigans.
    – init_js
    Commented Jul 19, 2023 at 1:31
13

I would recommend python's Fabric for this purpose:

#!/usr/bin/python
# ~/fabfile.py

from fabric_api import *

env.hosts = ['host1', 'host2']
def deploy_script():
    put('your_script.sh', 'your_script.sh', mode=0755)
    sudo('./your_script.sh')

# from shell
$ fab deploy_script

You should be able to use the above to get started. Consult Fabric's excellent documentation to do the rest. As an addendum, it's totally possible to write your script wholly within Fabric -- no copying needed, however it should be noted that to change the script on all machines, you would only need to edit the local copy and redeploy. Furthermore, with a little more than basic usage of the API, you can modify the script based on which host it is currently running on and/or other variables. It's a sort of pythonic Expect.

2
  • I don't think this exactly answers the question, but I like the idea and I could see Fabric as a useful tool.
    – tremoloqui
    Commented Dec 23, 2010 at 22:24
  • 1
    @tremoloqui Fabric is a python wrapper around ssh - nothing needs to be installed on the target machines, save the script being pushed. Which, if rewritten as a series of Fabric commands (using run and sudo), isn't even needed.
    – Izkata
    Commented Feb 9, 2012 at 15:18
6

This is exactly what Ansible is used for. There is no agent, you just have to create a text file called:

/etc/ansible/hosts

with content that looks something like:

[webhosts]
web[1-8]

This would specify that machines "web1, web2...web8" are in the group "webhosts". Then you can do things like:

ansible webhosts -m service -a "name=apache2 state=restarted" --sudo

to restart the apache2 service on all your machines, using sudo.

You can do on the fly commands like:

ansible webhosts -m shell -a "df -h"

or you can run a local script on the remote machine:

ansible webhosts -m script -a "./script.sh"

or you can create a playbook (look up the documentation for details) with a complete configuration that you want your servers to conform to and deploy it with:

ansible-playbook webplaybook.yml

Basically you can start using it as a command line tool for running commands on multiple servers and expand its usage out into a complete configuration tool as you see fit.

4
  • 3
    I like ansible as much as the next guy, but if he's asking about a script, then ansible has a really nice script module: ansible webhosts -m script script.sh
    – ptman
    Commented Aug 15, 2014 at 11:47
  • 1
    All the other answers involve a bash script, but it's not what he specifically asked for. He just said pushing config to remote machines. But good mention of the script module :)
    – seumasmac
    Commented Aug 15, 2014 at 16:15
  • Please vote up this answer! This is the way to do it!
    – chicks
    Commented Aug 22, 2015 at 17:31
  • @ptman I have only just noticed that while he doesn't mention a script in the question, he does in the title! Sorry. I've updated.
    – seumasmac
    Commented Sep 22, 2015 at 20:42
4

As explained in this answer you can use heredoc :

ssh user@host <<'ENDSSH'
#commands to run on remote host
ENDSSH

You have to be careful with heredoc, because it just sends text, but it doesn't really wait for the response. That means it will not wait for your commands to be executed.

1
  • I highly doubt "it doesn't really wait" in general. This just passes commands to ssh's stdin like other approaches in other answers. As with other commands / scripts, the (remote) shell exits when those are done (except for background jobs with & or commands that send themselves in the background, but that has nothing to do with neither ssh nor here documents). Commented Feb 22, 2022 at 14:00
1

Why not simply copy the script first, then running it?

scp your_script.sh the_server:
ssh the_server "chmod +x your_script.sh; ./your_script.sh"

Of course you should be careful not to upload it to a world-writable place, so nobody else could fiddle with it before you run it (possibly as root).

1
  • 1
    The reason I don't want to upload the script is so it doesn't have to be managed and have the risks that you mentioned. Also, it seems to me that it's simpler than the multi-step process of uploading, processing and (optionally) deleting.
    – tremoloqui
    Commented Dec 23, 2010 at 20:21
1

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

it 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 as follows 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

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
0

Rewrite the script in a way that each command in it already is prefixed with ssh and a hostname/ip or list of such is passed to the script as an argument (assuming you have passwordless/ssh-agent key authentication set up). Some work might be necessary to correctly pass error/return codes back from the remote commands ....

0

If the script isn't too big, and you're using bash or ksh ...

ssh vm24 -t bash -c "$(printf "%q" "$(< shell-test.sh )")"

Both stdin and stdout work correctly but the script is limited to the argument size (usually about 100k). Arguments to the script may work, at the end of the line, possibly following an extra "--" argument. The "-t" to allocate a pty is optional.

Beware: This confuses bash-completion, don't hit tab.

1
  • What benefit does this have over: ssh vm24 -t "$(< shell-test.sh )". Is it to ensure that the remote shell is bash instead of ksh or something? Commented Mar 9, 2021 at 21:21

You must log in to answer this question.

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