By Eli


2010-12-10 18:50:48 8 Comments

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.

12 comments

@sjas 2015-10-16 07:55:29

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 ..

@jsears 2016-03-25 16:06:59

The "$(which bash) -s" part gives you the location of bash on the local machine, rather than the remote machine. I think you want '$(which bash) -s' instead (single-quotes to suppress local parameter substitution).

@jww 2016-10-25 08:21:18

Paul Tomblin provided the Bash Here Document answer 6 years earlier. What value is added by this answer?

@sjas 2016-10-25 09:44:32

-s flag, I cite him you may be able to get away with replacing the first line with..., and I wasn't able to get away with just calling bash I dimly remember.

@DimiDak 2018-11-09 17:42:05

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

ssh [email protected] "cmd1; cmd2; cmd3"

@not2savvy 2020-05-08 15:53:37

Best in simplicity!

@Paul Tomblin 2010-12-10 19:03:15

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.

@Eli 2010-12-10 19:06:58

This looks like exactly what I want! How's it work? Would you happen to have a link to a page that explains it?

@bosmacs 2010-12-10 19:07:04

+1 Was just thinking that myself -- here's one place to read about it: tldp.org/LDP/abs/html/here-docs.html

@Globalz 2012-11-05 22:43:20

When I use a heredoc like this the "Message of the Day" login is outputted to my local. Is there a way to silence this from displaying?

@Globalz 2012-11-05 22:53:14

I also get this output to my local: Pseudo-terminal will not be allocated because stdin is not a terminal.

@assem 2013-05-07 09:27:33

It may be important for you to quote the word (i.e. first 'EOF') in order to prevent expansion of the command lines. See: man bash | less +/'Here Documents'

@JStrahl 2013-05-14 12:53:11

I wanted this tabbed in a script, by using <<- EOF it will consume the tabs, but note the last EOF must be on the same line.

@akc42 2014-02-12 07:52:12

I seemed to have to quote it all so I could pipe the output somewhere like this [code] ssh piback "/bin/bash << EOF lvcreate -L500M -s -n mailbackup /dev/cruz/mail >/dev/null mkdir -p /mnt/mail-backup mount -o ro,noload /dev/cruz/mailbackup /mnt/mail-backup tar -czp /mnt/mail-backup umount /mnt/mail-backup lvremove -f /dev/cruz/mailbackup > /dev/null EOF" | cat - > pibak-mail.tar.gz

@akc42 2014-02-12 07:59:40

comment system wouldn't let me format previous comment, so its a mess

@Paul Tomblin 2014-02-12 13:03:35

@akc42 looks a lot like my remote backup script except I use rsync with --link-dest to keep multiple versions.

@akc42 2014-02-13 20:07:10

@PaulTomblin it is just like your script except to make it work I had to quote (with ") all the way from /bin/bash right through to the EOF at the end

@Rishi 2014-03-28 21:29:09

For some reason "here document" didn't work for me. It was always existing after a particular command (may be that command is returning non zero exit code). However this worked - ssh $HOST 'ls ; pwd ; cmd3 ; cmd4'

@s3v1 2014-11-25 12:21:57

It even worked when I used ~ for the home folder. It took the remote server users' home folder, just like I wanted.

@Richard 2016-05-10 09:42:06

When I use a 'sudo -S' command inside the EOF-brackets I'm not asked for my password (like I am when running a single SSH command in a script). How do we do this?

@Paul Tomblin 2016-05-10 10:59:34

@Richard don't use sudo -S in a script. You should never put your password in a script, especially not if you have sudo privileges.

@Richard 2016-05-10 13:12:46

I don't put the password in the script. I type it in the terminal. But it doesn't work in heredocs

@Paul Tomblin 2016-05-10 13:26:48

@Richard because you use -S which says to take the password from stdin rather than from the terminal.

@exic 2017-08-15 10:25:07

You can also use something like /bin/sh -e instead of /bin/bash to exit immediately if any command fails.

@koyae 2017-11-13 23:06:51

Paul, I'm wondering if you might add a note about quoting here? Since heredocs don't know they're being used to control another shell, variables containing spaces or quotes could get dangerous. In really recent versions of bash it would be easiest to do e.g. ssh <host> <<-EOF mv ${[email protected]} ${[email protected]} EOF. For older versions, variations on printf '%q' "$myVariable" should suffice.

@Paul Tomblin 2017-11-13 23:46:16

@koyae the original question didn't include anything that needed special quoting. Let's not overcomplicate a seven-year-old question with detail that's interesting but not necessarily relevant.

@koyae 2017-11-15 00:28:54

Paul, I only suggest it because with nearly 200k views on this question, it looks like a lot of people are coming here when scripting with ssh. It's common to need to inject values when scripting. If not for the noise in the other questions and comments I would just make one and be on my way, but it's unlikely to be seen at this stage. A one-line footnote might save people some major headaches.

@DimiDak 2018-11-09 17:57:50

Please consider taking the "/bin/bash" to your standard answer and not offer it as optional as it does not work correctly without it. Thanx for the answer anyway.

@Maciej Krawczyk 2019-05-16 08:37:19

I added the /bin/bash and variables work just fine, e.g. echo $NAME;

@Michael Petrochuk 2017-07-04 04:11:37

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"

@Charles Addis 2017-10-31 16:24:19

You're thinking about this as if the only reason someone would want do to this is if they're sitting at a command line. There are other common reasons for this question as well, such as executing commands across distributed environments with tools such as rundeck, jenkins, etc.

@Aconcagua 2016-10-28 11:33:22

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

@Miro Kropacek 2019-05-14 17:51:23

What's even better you can use && and || this way, too - echo test \&\& ls

@Aconcagua 2019-05-14 19:24:59

@MiroKropacek That's nice, too. Better? Depends on what you want; run second command conditionally, then yes, run it unconditionally (no matter if first one succeeded or failed), then not...

@not2savvy 2020-05-08 15:50:44

@MiroKropacek, I didn't have to escape the && when putting double quotes around the commands: ssh host "echo test && ls"

@Jonathan 2016-09-23 08:07:14

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/%[email protected]%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.

@arnab 2015-05-06 21:44:04

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

@Eric Leschinski 2015-05-07 01:20:17

arnab, in the future, please describe what your code does briefly. Then talk about how it works, then put in the code. Then put the code in a code block so it's easy to read.

@jww 2016-10-25 08:15:57

From the question, you offered exactly what the OP wants to avoid: "I don't want to have a bash script with one line that looks like..."

@R J 2014-08-26 23:26:35

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 [email protected] 'bash -s' < /path/to/commands-inc.sh

Never failed for me.

@flow2k 2018-02-27 19:32:34

Similar to what I was thinking originally! But why is bash -s needed?

@flow2k 2018-02-27 19:36:42

Also, is #!/bin/bash really used?

@R J 2018-02-28 01:45:47

The -s is there for compatibility. From man bash -s If the -s option is present, or if no arguments remain after option processing, then commands are read from the standard input. This option allows the positional parameters to be set when invoking an interactive shell.

@R J 2018-02-28 01:49:04

But you are right, the #!/bin/bash is not really needed. This was an example of running an existing local script on a remote server.

@flow2k 2018-02-28 05:04:33

R J, thanks! With my first comment, it looks like we just do ssh [email protected] < /path/to/commands-inc.sh (it seems to work for me). Well, I guess your version ensures we use the bash shell, and not some other shell - that's the purpose, isn't it?

@R J 2018-02-28 08:30:28

Thanks. Yep, some of us have our notes, and habits from older versions of bash and other shells. :-)

@trainoasis 2019-08-16 06:55:59

Not bad, but if you need variables/flags from the original script used within your ssh commands this is not useful unfortunately. It is clean though, if you dont need that.

@Jai Prakash 2014-05-05 04:50:40

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

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

@gaoithe 2016-03-10 17:17:09

Somewhat odd but it works well. And args can be passed to the script (either before or after the redirect). e.g. 'ssh <remote-user>@<remote-host> "bash -s" arg1 <./remote-commands.sh arg2' e.g. 'ssh <remote-user>@<remote-host> "bash -s" -- -arg1 <./remote-commands.sh arg2'

@flow2k 2018-02-27 19:37:32

I had a similar question with another answer above, but what is the purpose of including bash -s?

@Jai Prakash 2018-02-27 23:52:24

@flow2k -s is to indicate the bash to read the commands from standard input. Here is the description from the man pages If the -s option is present, or if no arguments remain after option processing, then commands are read from the standard input. This option allows the positional parameters to be set when invoking an interactive shell.

@flow2k 2018-02-28 00:20:17

@JaiPrakash Thanks for this, but I was actually thinking if we can just do away with the bash command altogether, i.e. ssh [email protected] <./remote-commands.sh. I tried my way and it seemed to work, though I'm not sure if it's deficient in some way.

@Jai Prakash 2018-03-02 00:28:24

@flow2k from my understanding of the man pages there wont be any deficiency without that option. Its required if you are trying to pass on any arguments. -- thanks

@trainoasis 2019-08-16 06:56:57

You cannot use flag variables & constants from your original script inside your remote-commands.sh ... :)

@terminus 2010-12-10 18:56:09

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.

@Andrei B 2013-06-27 08:17:51

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

ssh blah_server "
  ls
  pwd
"

@Signus 2013-10-04 18:49:07

I like this format, however it sadly is not useful for storing std data into a variable.

@Andrei B 2013-11-16 12:40:58

Signus, what do you mean by "storing std data into a variable"?

@tripleee 2015-12-29 11:25:20

@Signus It is perfectly possible to do what you describe, although you will probably want to use single quotes instead of double quotes around the remote commands (or escape the operators which need to be escaped inside double quotes to prevent your local shell from intercepting and interpolating them).

@jkonst 2019-02-05 09:10:22

I am not able to put inside double quotes code a command like this fileToRemove=$(find . -type f -name 'xxx.war'). fileToRemove should have a filename inside but instead it has an empty string. Does something need to be escaped?

@bosmacs 2010-12-10 19:00:54

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;

@RichVel 2013-07-17 06:07:00

This has the great advantage that you know exactly what is being executed by the remote script - no problems with quoting. If you need dynamic commands, you can use a shell script with a subshell, still piping into the ssh, i.e. ( echo $mycmd $myvar ; ...) | ssh myhost - as with the cat usage, you know exactly what is going into the ssh command stream. And of course the subshell in the script can be multi-line for readability - see linuxjournal.com/content/bash-sub-shells

@elaRosca 2014-02-21 08:18:25

Can you do this with arguments in the commands-to-execute-remotely.sh?

@Patrick Bassut 2014-03-26 21:59:42

I think this is far more useful than paultomblin solution. Since the script can be redirected to any ssh server without having to open the file. Also it's far more readable.

@Olivier Dulac 2016-03-08 14:18:12

yes, but using echo or a here document (see top answer) : use: $localvar to interpret a locally-defined variable, \$remotevar to interpret remotely a remotely-defined variable, \$(something with optionnal args) to get the output of something executed on the remote server. An exemple that you can ssh through 1 (or, like shown here, multiple ssh commands) : echo " for remotedir in /*/${localprefix}* ; do cd \"\$remotedir\" && echo \"I am now in \$(pwd) on the remote server \$(hostname) \" ; done " | ssh [email protected] ssh [email protected] ssh [email protected] bash

@flow2k 2018-02-27 19:26:46

Hmm...how is this different from using input redirection, i.e. ssh blah_server < commands-to-execute-remotely.sh?

Related Questions

Sponsored Content

21 Answered Questions

[SOLVED] Best way to use multiple SSH private keys on one client

  • 2010-03-10 18:40:58
  • Justin
  • 467711 View
  • 907 Score
  • 21 Answer
  • Tags:   ssh ssh-keys openssh

8 Answered Questions

[SOLVED] What does set -e mean in a bash script?

  • 2013-10-27 19:06:32
  • AndreaNobili
  • 422066 View
  • 772 Score
  • 8 Answer
  • Tags:   linux bash shell sh

9 Answered Questions

[SOLVED] Assigning default values to shell variables with a single command in bash

  • 2010-01-06 14:29:31
  • Edward Q. Bridges
  • 345188 View
  • 744 Score
  • 9 Answer
  • Tags:   bash shell

14 Answered Questions

[SOLVED] How do I set a variable to the output of a command in Bash?

20 Answered Questions

[SOLVED] bash/fish command to print absolute path to a file

  • 2010-10-12 13:12:09
  • dhardy
  • 336204 View
  • 478 Score
  • 20 Answer
  • Tags:   bash shell path

29 Answered Questions

[SOLVED] How to specify the private SSH-key to use when executing shell command on Git?

  • 2010-12-30 19:42:01
  • Christoffer
  • 1161745 View
  • 1211 Score
  • 29 Answer
  • Tags:   git bash shell ssh

37 Answered Questions

[SOLVED] How do I parse command line arguments in Bash?

17 Answered Questions

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

14 Answered Questions

[SOLVED] Find and Replace Inside a Text File from a Bash Command

5 Answered Questions

[SOLVED] What is the preferred Bash shebang?

  • 2012-04-29 21:37:00
  • Kurtosis
  • 472772 View
  • 1171 Score
  • 5 Answer
  • Tags:   bash shebang

Sponsored Content