What is the cleanest way to ssh and run multiple commands in Bash?

BashUnixSsh

Bash Problem Overview


I already have an ssh agent set up, and I can run commands on an external server in Bash script doing stuff like:

ssh blah_server "ls; pwd;"

Now, what I'd really like to do is run a lot of long commands on an external server. Enclosing all of these in between quotation marks would be quite ugly, and I'd really rather avoid ssh'ing multiple times just to avoid this.

So, is there a way I can do this in one go enclosed in parentheses or something? I'm looking for something along the lines of:

ssh blah_server (
   ls some_folder;
   ./someaction.sh;
   pwd;
)

Basically, I'll be happy with any solution as long as it's clean.

Edit

To clarify, I'm talking about this being part of a larger bash script. Other people might need to deal with the script down the line, so I'd like to keep it clean. I don't want to have a bash script with one line that looks like:

ssh blah_server "ls some_folder; ./someaction.sh 'some params'; pwd; ./some_other_action 'other params';"

because it is extremely ugly and difficult to read.

Bash Solutions


Solution 1 - Bash

How about a Bash Here Document:

ssh otherhost << EOF
  ls some_folder; 
  ./someaction.sh 'some params'
  pwd
  ./some_other_action 'other params'
EOF

To avoid the problems mentioned by @Globalz in the comments, you may be able to (depending what you're doing on the remote site) get away with replacing the first line with

ssh otherhost /bin/bash << EOF

Note that you can do variable substitution in the Here document, but you may have to deal with quoting issues. For instance, if you quote the "limit string" (ie. EOF in the above), then you can't do variable substitutions. But without quoting the limit string, variables are substituted. For example, if you have defined $NAME above in your shell script, you could do

ssh otherhost /bin/bash << EOF
touch "/tmp/${NAME}"
EOF

and it would create a file on the destination otherhost with the name of whatever you'd assigned to $NAME. Other rules about shell script quoting also apply, but are too complicated to go into here.

Solution 2 - Bash

Edit your script locally, then pipe it into ssh, e.g.

cat commands-to-execute-remotely.sh | ssh blah_server

where commands-to-execute-remotely.sh looks like your list above:

ls some_folder
./someaction.sh
pwd;

Solution 3 - Bash

To match your sample code, you can wrap your commands inside single or double qoutes. For example

ssh blah_server "
  ls
  pwd
"

Solution 4 - Bash

I see two ways:

First you make a control socket like this:

 ssh -oControlMaster=yes -oControlPath=~/.ssh/ssh-%r-%h-%p <yourip>

and run your commands

 ssh -oControlMaster=no -oControlPath=~/.ssh/ssh-%r-%h-%p <yourip> -t <yourcommand>

This way you can write an ssh command without actually reconnecting to the server.

The second would be to dynamically generate the script, scping it and running.

Solution 5 - Bash

This can also be done as follows. Put your commands in a script, let's name it commands-inc.sh

#!/bin/bash
ls some_folder
./someaction.sh
pwd

Save the file

Now run it on the remote server.

ssh user@remote 'bash -s' < /path/to/commands-inc.sh

Never failed for me.

Solution 6 - Bash

Put all the commands on to a script and it can be run like

ssh <remote-user>@<remote-host> "bash -s" <./remote-commands.sh

Solution 7 - Bash

Not sure if the cleanest for long commands but certainly the easiest:

ssh user@host "cmd1; cmd2; cmd3"

Solution 8 - Bash

SSH and Run Multiple Commands in Bash.

Separate commands with semicolons within a string, passed to echo, all piped into the ssh command. For example:

echo "df -k;uname -a" | ssh 192.168.79.134

Pseudo-terminal will not be allocated because stdin is not a terminal.
Filesystem     1K-blocks    Used Available Use% Mounted on
/dev/sda2       18274628 2546476  14799848  15% /
tmpfs             183620      72    183548   1% /dev/shm
/dev/sda1         297485   39074    243051  14% /boot
Linux newserv 2.6.32-431.el6.x86_64 #1 SMP Sun Nov 10 22:19:54 EST 2013 x86_64 x86_64 x86_64 GNU/Linux

Solution 9 - Bash

This works well for creating scripts, as you do not have to include other files:

#!/bin/bash
ssh <my_user>@<my_host> "bash -s" << EOF
    # here you just type all your commmands, as you can see, i.e.
    touch /tmp/test1;
    touch /tmp/test2;
    touch /tmp/test3;
EOF

# you can use '$(which bash) -s' instead of my "bash -s" as well
# but bash is usually being found in a standard location
# so for easier memorizing it i leave that out
# since i dont fat-finger my $PATH that bad so it cant even find /bin/bash ..

Solution 10 - Bash

The posted answers using multiline strings and multiple bash scripts did not work for me.

  • Long multiline strings are hard to maintain.
  • Separate bash scripts do not maintain local variables.

Here is a functional way to ssh and run multiple commands while keeping local context.

LOCAL_VARIABLE=test

run_remote() {
    echo "$LOCAL_VARIABLE"
    ls some_folder; 
    ./someaction.sh 'some params'
    ./some_other_action 'other params'
}

ssh otherhost "$(set); run_remote"

Solution 11 - Bash

For anyone stumbling over here like me - I had success with escaping the semicolon and the newline:

First step: the semicolon. This way, we do not break the ssh command:

ssh <host> echo test\;ls
                    ^ backslash!

Listed the remote hosts /home directory (logged in as root), whereas

ssh <host> echo test;ls
                    ^ NO backslash

listed the current working directory.

Next step: breaking up the line:

                      v another backslash!
ssh <host> echo test\;\
ls

This again listed the remote working directory - improved formatting:

ssh <host>\
  echo test\;\
  ls

If really nicer than here document or quotes around broken lines - well, not me to decide...

(Using bash, Ubuntu 14.04 LTS.)

Solution 12 - Bash

The easiest way to configure your system to use single ssh sessions by default with multiplexing.

This can be done by creating a folder for the sockets:

mkdir ~/.ssh/controlmasters

And then adding the following to your .ssh configuration:

Host *
    ControlMaster auto
    ControlPath ~/.ssh/controlmasters/%r@%h:%p.socket
    ControlMaster auto
    ControlPersist 10m

Now, you do not need to modify any of your code. This allows multiple calls to ssh and scp without creating multiple sessions, which is useful when there needs to be more interaction between your local and remote machines.

Thanks to @terminus's answer, http://www.cyberciti.biz/faq/linux-unix-osx-bsd-ssh-multiplexing-to-speed-up-ssh-connections/ and https://en.wikibooks.org/wiki/OpenSSH/Cookbook/Multiplexing.

Solution 13 - Bash

> What is the cleanest way to ssh and run multiple commands in Bash?

I recommend using this escaping function. The function takes one argument - a function to escape. Then sshqfunc outputs declare -f of the function and then outputs a string that will call the function with "$@" arguments properly quoted. Then the whole is "%q" quoted and bash -c is added. In case the remote does not have bash, you could change bash to sh.

sshqfunc() { echo "bash -c $(printf "%q" "$(declare -f "$@"); $1 \"\$@\"")"; };

Then define a function with the work you want to do on the remote. The function is defined normally, so it will be properly "clean". You can test such function locally. After defining, properly escaped function is passed to the remote.

work() {
   ls
   pwd
   echo "Some other command"
}

ssh host@something "$(sshqfunc work)"

Passing You can also pass arguments, and they will be passed to your function as positional arguments. The right next argument after the function will be assigned to $0 - usually a placeholder like -- or _ is used to separate arguments from call.

work() {
   file=$1
   num=$2
   ls "$file"
   echo "num is $num"
}

ssh host@something "$(sshqfunc work)" -- /this/file 5

But note that arguments should also be properly quoted if there are any magic characters:

ssh host@something "$(sshqfunc work)" -- "$(printf "%q" "$var1" "$var2")"

Solution 14 - Bash

For simple commands you can use:

ssh <ssh_args> command1 '&&' command2

or

ssh <ssh_args> command1 \&\& command2

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionEliView Question on Stackoverflow
Solution 1 - BashPaul TomblinView Answer on Stackoverflow
Solution 2 - BashbosmacsView Answer on Stackoverflow
Solution 3 - BashAndrei BView Answer on Stackoverflow
Solution 4 - BashterminusView Answer on Stackoverflow
Solution 5 - BashR JView Answer on Stackoverflow
Solution 6 - BashJai PrakashView Answer on Stackoverflow
Solution 7 - BashDimiDakView Answer on Stackoverflow
Solution 8 - BasharnabView Answer on Stackoverflow
Solution 9 - BashsjasView Answer on Stackoverflow
Solution 10 - BashMichael PetrochukView Answer on Stackoverflow
Solution 11 - BashAconcaguaView Answer on Stackoverflow
Solution 12 - BashJonathanView Answer on Stackoverflow
Solution 13 - BashKamilCukView Answer on Stackoverflow
Solution 14 - BashSoufiane SakhiView Answer on Stackoverflow