Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Bash: Looping over multiple input sources (nesting "while read" loops?)

Tags:

bash

I have a query.sh script which runs dig commands, performing a set of lookups from different DNS servers, which are currently given in a column in the single input file used (sites.txt).

My goal is to modify this script to use a different input file, dns_servers.txt, to find the DNS servers to iterate through for each query.

I'm not clear on where to start here. What do I need to do to be able to safely nest while read loops?


Current inputs:

query.sh

#!/bin/sh

while read line;
do
        set $line
        echo "SITE:" $1@$2
        /usr/sbin/dig +short -4 @$2 $1
        sleep 5
        exitStatus=$?
        echo "Exit Status: " $exitStatus
done < sites.txt

sites.txt

Current format has a hostname and a DNS server to use for lookups against that hostname.

www.google.com 8.8.4.4

The intent is for the column with the DNS server to be ignored, and the contents of dns_servers.txt to be used instead.


Desired Inputs

dns_servers.txt

10.1.1.1
12.104.1.232
...
like image 920
user2382290 Avatar asked Aug 03 '15 15:08

user2382290


People also ask

Can you nest for loops in bash?

Nested loop definition That is to say, it is a loop that exists inside an outer loop. When you integrate nested loops in bash scripting, you are trying to make a command run inside another command. This is what it means: a bash nested for loop statement runs a command within another loop.

What are the three types of loops supported by bash?

There are three basic loop constructs in Bash scripting, for loop, while loop , and until loop .

What is bash while loop?

The bash while loop is a control flow statement that allows code or commands to be executed repeatedly based on a given condition. For example, run echo command 5 times or read text file line by line or evaluate the options passed on the command line for a script.


1 Answers

Ignoring any additional column(s) in the sites.txt file, and iterating through the lines of dns_servers.txt, might look like the following:

#!/bin/sh
while read -r site _ <&3; do
  while read -r dns_server <&4; do
    dig +short -4 "@$dns_server" "$site"; exit=$?
    sleep 5
    echo "Exit status: $exit"
  done 4<dns_servers.txt
done 3<sites.txt

The key changes here:

  • Pass the list of fields you want to parse as arguments to read. The underscore passed as second positional argument to the first read is the variable name to which the second column of site.txt is now being saved.
  • Nest your loops, since you want to read from the inner loop for every pass of the outer loop.
  • Use a different file descriptor (here, 3 and 4) for outer and inner loops to keep them separated.

Incidentally, if you were targeting bash (#!/bin/bash) rather than POSIX sh (#!/bin/sh), I might do this differently. The below uses the bash 4 extension mapfile to read dns_servers.txt all at once:

#!/bin/bash
readarray -t dns_servers <dns_servers.txt
while read -r site _; do
  for from_ip in "${dns_servers[@]}"; do
    dig +short -4 "@$from_ip" "$site"; exit=$?
    sleep 5
    echo "Exit status: $exit"
  done
done <sites.txt

Here, we read dns_servers.txt only once, and reuse that list of values for each value read from sites.txt.

If you're using bash 3.x, mapfile can be replaced with a loop:

dns_servers=( )
while read -r; do dns_servers+=( "$REPLY" ); done <dns_servers.txt
like image 110
Charles Duffy Avatar answered Dec 10 '22 20:12

Charles Duffy