I have a simple script that executes commands on remote host using ssh and a here-document.

I was trying to do it with a one-liner at command line but I couldn't figure out how to do the here-document outside of a script. Is it possible? I spent some time googling and reading documentation about here-documents, but it wasn't coming to me.

This script works fine - I needed the here-document section in order to run awk remotely via ssh but I usually like to do one-liners for simple stuff:

# it looks up all my hosts with 'db' in the name
# then gets the PID of any rsyncs running as user 'research'
# and pumps them into xargs

getHosts=(`curl --silent "http://assetts.lab/all_hosts" | grep -v ^# | awk -F" " '{print$1}'|grep db`)
for BOX in ${getHosts[@]};do
    echo "$BOX: "
    ssh -T sshUser@$BOX <<"EOF"
ps -ef | egrep "rsync|iasync" | awk -F" " '{if ($1 ~ "research") print $2}'|sudo xargs -i ps -fp '{}'
#ps -ef | egrep "rsync|iasync" | awk -F" " '{if ($1 ~ "research") print $2}'|sudo xargs -i kill '{}'

I'm probably missing something simple... Anyway thanks for any suggestions :)

  • 1
    Your current code is simple and readable. You already have 2 kinds of quotes in the remote command: using some more condensed form will make your code "clever", and that's usually contrary to maintainable – glenn jackman Oct 8 '14 at 1:14

The EOF bash feature is basically for use as you do in your example, ie in order to send multiple lines of text to another command.

If you want this as a one-liner, the EOF has to go.

In order to execute a command on a remote host using ssh, all you need to do is:

ssh host command

For example:

ssh host "ls -alh"

will run the command on host and return the output on the console.

Your example code could be rewritten in a one line if you take note of how you use the quotation marks, something like this:

ssh host "ps -ef | egrep 'rsync|iasync' | awk -F' ' '{if (\$1 ~ \"research\") print \$2}'"

This double-quoted string puts the arguments to individual commands in single-quotes. When more double-quotes are needed (the "research" pattern), they have to be escaped with a \, and so do the $ characters.

  • the awk doesn't actually work on the remote host when used this way - this is why I used heredocs to begin with #works: $ ssh luser@box "ps -ef | egrep 'rsync|iasync'" luser 7457 7454 0 11:44 ? 00:00:00 bash -c ps -ef | egrep 'rsync|iasync' luser 7476 7457 0 11:44 ? 00:00:00 egrep rsync|iasync # no worky ` $ssh luser@box "ps -ef | egrep 'rsync|iasync' | awk -F" " '{if ($1 ~ "research") print $2}'" Usage: awk [POSIX or GNU style options] -f progfile [--] file ... ` – j-marr Oct 16 '14 at 15:45
  • ^^ sorry I don't yet know how to format in comments :( ^^ – j-marr Oct 16 '14 at 15:52
  • using a heredoc sends the doc content to stdin, this is different to adding an argument to ssh. The one-liner equivalent to a heredoc would be a herestring, i.e. using a triple chevron: ssh host <<<''commands" – MNB Nov 22 '20 at 23:29

I don't really see a need to do anything about the use of a here-document, but I do see that you can simplify other things in your script quite a bit (which in turn makes the here-document obsolete).

First of all, the pipeline to populate your getHosts array may be simplified (there is seldom a reason to pipe grep into awk), and the whole array can be removed:

curl -s 'http://assetts.lab/all_hosts' |
awk -F ' ' '!/^#/ && $1 ~ /db/ { print $1 }' |
while IFS= read -r remote; do
   # rest of code

Here I have combined your grep+awk+grep command into a single awk command. I've also let the awk command feed the hostnames directly into the loop instead of storing them in an intermediate variable.

Inside the loop, it looks as if you're wanting to get information about a rsync or iasync process belonging to a research user.

This is best done with pgrep:

pgrep -l -U research 'rsync|iasync'

To signal these processes, use pkill in place of pgrep and drop the -l option.

The full script the becomes:


curl -s 'http://assetts.lab/all_hosts' |
awk -F ' ' '!/^#/ && $1 ~ /db/ { print $1 }' |
while IFS= read -r remote; do
   printf 'Processing host "%s"\n' "$remote"
   ssh "$remote" 'pgrep -l -U research "rsync|iasync"'

Or, using xargs:


curl -s 'http://assetts.lab/all_hosts' |
awk -F ' ' '!/^#/ && $1 ~ /db/ { print $1 }' |
xargs -I {} sh -c '
   printf "Processing host \"%s\"\n" "$1"
   ssh "$1" "pgrep -l -U research \"rsync|iasync\""' sh {}

If your xargs supports it, this could be made to run several sh -c processes at once with -P n, where n is the number of concurrent sh -c command that xargs should keep running.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.