Run three commands in multiple Linux servers

OS : RHEL 8.5
bash shell

I want to run the following three commands in multiple RHEL servers

cp /app/version/pkms7c/config.prm /nfs_server83/backups/config_backup.prm_$servername
mv /app/version/pkms7c/config.prm /shared_fs2/app_configs/pkms
ln -s /shared_fs2/app_configs/pkms/config.prm /app/version/pkms7c/config.prm

In a jump server (another RHEL server), I have a file named hostlist like below with lots of server names

m48vlx167.domain.net
m48vlx125.domain.net
m43vlx216.domain.net
m52vlx238.domain.net
.
.
m56vlx178.domain.net

And I created a for loop like below to run it.

When I run the script, it seems to be running fine. Do you see any flaw in this ? Any improvements/suggestions ?

for host in `cat hostlist` 
do 
echo -e "Currently running commands on $host ....\n"
ssh -n "johndoe@$host" "
cp /app/version/pkms7c/config.prm /nfs_server83/backups/config_backup.prm_$host;
mv /app/version/pkms7c/config.prm /shared_fs2/app_configs/pkms;
ln -s /shared_fs2/app_configs/pkms/config.prm /app/version/pkms7c/config.prm"
done

It's fine.
I have a cosmetical suggestion.
Since empty lines do not harm, and indented commands work, I would visualize the structure:

  ssh -n "johndoe@$host" "
    cp /app/version/pkms7c/config.prm /nfs_server83/backups/config_backup.prm_$host
    mv /app/version/pkms7c/config.prm /shared_fs2/app_configs/pkms
    ln -s /shared_fs2/app_configs/pkms/config.prm /app/version/pkms7c/config.prm
  "

If you use multi-line then the separator is a newline and you don't need an extra semicolon.

The for host in `cat hostlist` splits at "whitespace" (space,tab,newline, which is by default in "$IFS"), and is subject to filename generation (expansion of wildcards). Fine if you trust it.
If you have one host per line, then you can use a rock-solid

while read -r host junk <&3
do
  ...
done 3< hostlist

The read by default reads one line and ignores a leading space. The purpose of the junk variable is to also ignore a trailing space.

3 Likes

Thank You MadeInGermany.

One more question:
Once I ssh into a server, If I want to perform a conditional check (like an IF clause) for the existence of /app/version/pkms7c directory, only then execute below cp, mv and ln commands. Would that be possible in the below construct ?

I think I will have to make it a script ie. a script containing IF... statement plus cp, mv and ln commands. Right ?

while read -r host junk <&3
do
  ssh -n "johndoe@$host" "
    cp /app/version/pkms7c/config.prm /nfs_server83/backups/config_backup.prm_$host;
    mv /app/version/pkms7c/config.prm /shared_fs2/app_configs/pkms;
    ln -s /shared_fs2/app_configs/pkms/config.prm /app/version/pkms7c/config.prm
  "
done 3< hostlist

If you know the login shell at the remote is bash then you can do bash syntax. Also, the remote script is in " " quotes to allow local substitutions like $host.
The following works:

  ssh -n "johndoe@$host" "
    if [ -d /app/version/pkms7c ]
    then
      cp /app/version/pkms7c/config.prm /nfs_server83/backups/config_backup.prm_$host
      mv /app/version/pkms7c/config.prm /shared_fs2/app_configs/pkms
      ln -s /shared_fs2/app_configs/pkms/config.prm /app/version/pkms7c/config.prm
    fi
  "

But if you want to do more things, like having a remote $dir, then you must go for the safe method:


while read -r host junk <&3 
do 
  echo "Currently running commands on $host ...."
  ssh "johndoe@$host" /bin/bash -s "$host" << "_EORS_"
#!/bin/bash
# Positional parameters (passed to /bin/bash)
host=$1
# Other variables
dir=/app/version/pkms7c
if [ -d "$dir" ]
then
  cp "$dir"/config.prm /nfs_server83/backups/config_backup.prm_$host
  mv "$dir"/config.prm /shared_fs2/app_configs/pkms
  ln -s /shared_fs2/app_configs/pkms/config.prm /app/version/pkms7c/config.prm
fi
_EORS_
# EndOfRemoteScript
done 3< hostlist

The remote script is in a here document. Of course a separate file would work as well.
It is passed through the ssh to the remote /bin/bash (no ssh -n that would prevent this).
bash -s means the script comes from stdin not the first argument. Instead the first argument becomes $1 (the 1st positional parameter).

EDIT: must be a quoted here document: "_EORS_"

2 Likes

Consider using ansible.

You will find it much easier then scripts in loops with easier error handling as stack does it for you on basic level :slight_smile:
There is also a lot of done code in the wild as ansible is defacto standard to manage many types of devices.

Regards
Peasant.

1 Like