1

I am writing a script that connects to multiple servers which uses 3 different users.

#!/bin/ksh

server1=("abc1" "abc2") server2=("abc3" "abc4") server3=("abc5" "abc6")

for i in "${server1[@]}" do ssh sam@$i "some command" done

for i in "${server2[@]}" do ssh danny@$i "some command" done

for i in "${server3[@]}" do ssh robert@$i "some command" done

Is there a way to combine all three for loops` into just one. Actually I want just want to write just one ssh for multiple connections.

I have tried putting all servers in 1 array and users in anotherlike

servers=("abc1" "abc2" "abc3" "abc4" "abc5" "abc6")

users=("sam" "Danny" "Robert")

for i in ${servers[@]} do ssh ${users[@]}@$i "some command" done

But this way the script puts each user with each server, which ends up locking the service accounts…

What I want is some script that loops all the servers and users and only match user Sam with array server1 user danny with array server2 and user robert with array server3 only. is there a better way to match just the right user with right server in a clean and better way…

muru
  • 72,889
  • Your tags say Bash, but your hashbang says Ksh. Which shell are you actually using? – Toby Speight Jan 22 '24 at 15:47
  • @TobySpeighti am using ksh.. – NecroCoder Jan 22 '24 at 15:54
  • 1
    hmmm. do you need to run these sequentially? In the order specified? Is the structure of the array controlling the actions fixed? Do the user names need to be hard coded? What is the relationship between usernames and hosts? Is there a reason the usernames are not defined in your ssh_config? – symcbean Jan 22 '24 at 16:38
  • The ssh sessions need to be run one after the other but can be in any order. Usernames doesn’t have to be hardcoded and usernames are the service accounts for the servers… – NecroCoder Jan 22 '24 at 16:51

2 Answers2

3

Assuming Bash, as the tags say (not ksh as the shebang says), we can use a nameref to use the corresponding array variable given the integer identifier:

users=(. userxyz user123 user221)
for n in 1 2 3
do
    declare -n server=server$n
    for i in "${server[@]}"
    do ssh "${users[$n]}@$i" "some command"
    done
done

With a suitable echo, we get the same output as original script:

ssh userxyz@abc1 some command
ssh userxyz@abc2 some command
ssh user123@abc3 some command
ssh user123@abc4 some command
ssh user221@abc5 some command
ssh user221@abc6 some command

To adapt this for Korn shell, you would need to use the nameref built-in command instead of declare -n.


A simpler approach may be just to consider the username/hostname together:

servers=("userxyz@abc1" "userxyz@abc2"
         "user123@abc3" "user123@abc4"
         "user221@abc5" "user221@abc6")

for i in "${servers[@]}" do ssh "$i" "some command" done

Toby Speight
  • 8,678
  • Would the script try to use each user with each server if I run the script… that would cause the account to get locked… – NecroCoder Jan 22 '24 at 16:25
  • 1
    Replace ssh with a suitable echo to see what it will do (don't you normally do that anyway before trusting someone else's code?). – Toby Speight Jan 22 '24 at 16:27
  • 1
    ksh93(+) has it's own nameref feature, but it doesn't work like the bash version. See https://unix.stackexchange.com/a/98863/6122 for an example. . Good luck to all. – shellter Jan 23 '24 at 05:26
  • Thanks @shellter. It's 30 years or so since I last used Korn shell, so I didn't know that. – Toby Speight Jan 23 '24 at 07:53
3

If user1 is only associated with server1a and server1b, user2 is associated with server2a, server2b and server2c etc., then

#!/bin/sh

set --
user1 server1a server1b --
user2 server2a server2b server2c --
user3 server3a --

while [ "$#" -ne 0 ]; do user=$1; shift

until [ "$1" = -- ]; do
    ssh "$user@$1" 'some command'
    shift
done

# Shift off that "--" delimiter
shift

done

This uses the positional parameters to store the user names and the corresponding list of servers. Since it's a single list of strings, we delimit the sections of the list containing user names and server names with -- so that we easily can see when to expect either a new user name or the end of the list.

The outer loop loops over the sections of the list and assigns the user name to the variable user, while the inner loop loops over the server names and calls ssh until the string -- is found.

The script is written for /bin/sh and would therefore also work for both ksh and bash.

Given the code above, the following ssh commands would get executed:

ssh user1@server1a 'some command'
ssh user1@server1b 'some command'
ssh user2@server2a 'some command'
ssh user2@server2b 'some command'
ssh user2@server2c 'some command'
ssh user3@server3a 'some command'

If you remove the initial set command, you would be able to call the script from the command line like this, for the same effect:

$ ./script user1 server1a server1b -- user2 server2a server2b server2c -- user3 server3a --

With an extra check (|| [ "$#" -eq 0 ]) in the inner loop, you could leave off that last -- on the command line.


If what you want to do is to apply the DRY principle and only call ssh from a single point in your code, then lift that bit out into a function. Here's your code, modified to do that:

#!/bin/ksh

server1=("abc1" "abc2") server2=("abc3" "abc4") server3=("abc5" "abc6")

process () { ssh "$1@$2" 'some command' }

for server in "${server1[@]}"; do process sam "$server" done

for server in "${server2[@]}"; do process danny "$server" done

for server in "${server3[@]}"; do process robert "$server" done

Kusalananda
  • 333,661