2

I have a bash array of hosts.

a_hosts=( "host1.q.d.n",
  "host2.q.d.n",
  "host3.q.d.n",
  ...
  "hostN.q.d.n"
)

I need to compare a specific file on each host with the same file on all of the other hosts.

diff host1.file host2.file
diff host1.file host3.file
...
diff host1.file hostN.file

diff host2.file host3.file ... diff host2.file hostN.file ...etc.

I have ideas based on this solution, but I keep backing myself into a corner when I try to loop through loopN-1 inside of a loop through loopN. I almost think I have to duplicate the array, and keep the two arrays synchronized. But, that's yet another loop.

Has anyone come up with an elegant solution to this kind of loop manipulation?


EDIT 1:

I'm experimenting with this.

# Create two loop arrays.
a_outer_loop=a_hosts
a_inner_loop=a_hosts

Iterate through outer loop.

for s_fqdn1 in ${a_outer_loop[@]} do

Pop the first item of the inner loop. (Index 0)

a_inner_loop=( ${a_inner_loop[@]:1:} )

Loop through the popped inner loop.

for s_fqdn2 in ${a_inner_loop[@]} do diff s_fqdn1.file s_fqdn2.file done done


EDIT 2:

Ack! Sorry, my fault for oversimplifying my example. If my list of hosts were really host1, host2, ..., hostN, this would be a much simpler problem. Sadly, I have several varieties of FQDNs to deal with, in several domains, so no solution as easy as host$i will work. Good news is: I think I've got something that will work.

dafydd
  • 1,458

3 Answers3

2

Loop using the index of the elements, and then offset the array using that index in the nested loop:

#! /bin/bash
a_hosts=( "host1.q.d.n"
  "host2.q.d.n"
  "host3.q.d.n"
  ...
  "hostN.q.d.n"
)

for i in "${!a_hosts[@]}" do host1=${a_hosts[i]} for host2 in "${a_hosts[@]:i+1}" do echo "$host1" "$host2" done done

  • ${!a_hosts[@]} - the indexes of all elements in the array
  • ${a_hosts[@]:i+1} - the array starting from offset i+1 (arithmetic expansion is performed on the array subscript).

Output with the example (without commas):

% bash foo.sh
host1.q.d.n host2.q.d.n
host1.q.d.n host3.q.d.n
host1.q.d.n ...
host1.q.d.n hostN.q.d.n
host2.q.d.n host3.q.d.n
host2.q.d.n ...
host2.q.d.n hostN.q.d.n
host3.q.d.n ...
host3.q.d.n hostN.q.d.n
... hostN.q.d.n
muru
  • 72,889
2

Muru and dafydd already gave you the elegant approaches. Here's the brute force one:

#! /bin/bash
a_hosts=( "host1.q.d.n"
  "host2.q.d.n"
  "host3.q.d.n"
  "hostN.q.d.n"
)

for((i=0;i<${#a_hosts[@]};i++)); do for((k=$i+1;k<${#a_hosts[@]};k++)); do diff <(ssh "${a_hosts[$i]}" cat /path/to/file)
<(ssh "${a_hosts[$k]}" cat /path/to/file) done done

terdon
  • 242,166
1

This works in Bash 4. The best part is that it's entirely agnostic to the contents of the source array.

Set your source array.

a_fqdns=( "h1.q.d.n" "h5.r.d.n" "i21.s.d.n" ... )

For the outer loop, pop the final element, as the corresponding inner loop would be empty for that element.

a_outer_loop=( ${a_fqdns[@]} )
unset a_outer_loop[${#a_fqdns[@]}-1]

a_inner_loop=( ${a_fqdns[@]} )

for s_fqdn1 in ${a_outer_loop[@]} do echo ${s_fqdn1}

Pop the first element (index 0) from the inner loop. This will result in the inner loop shrinking by one element (index 0) for each iteration of the outer loop. Note that a_outer_loop[i] has the same value as a_inner_loop[0] for each iteration of a_outer_loop.

Also, unset 'a_inner_loop[0]' won't work here. unset doesn't pop index 0, it just changes the value of index 0 to an empty string.

  a_inner_loop=( "${a_inner_loop[@]:1}" )
  for s_fqdn2 in ${a_inner_loop[@]}
  do
    echo "- ${s_fqdn2}"
  done
done
dafydd
  • 1,458