Bash string variable outputting incorrectly

Hello All,

I am learning BASH scripting and I would appreciate any help with a small problem I am having...

I am writing a script that builds a simple hosts file for DNS reasons related to a piece of software called netdb by parsing another application's config files for IP's and their hostnames.

The script works correctly for all hosts except a relatively small group. What ends up happening is the string my script finds for a hostname ends up overwriting everything on the line that it is currently located on. I've narrowed my script down to only parse the config file that produces these problem strings after my script runs.

The erroneous output is as follows:

Number of addresses found is: 2
Number of DNS names found is: 2
Sync Check Success!
-----------------------------------------------------------

DEBUG: VARIABLE (IP) IS:  192.168.xxx.xxx
DEBUG: VARIABLE (host) IS:  Alderaan
-----------------------------------------------------------

DEBUG: VARIABLE (IP) IS:  192.168.xxx.xxx
DEBUG: VARIABLE (host) IS:  Bespin

Bespin .xxx.xxx
Alderaan .xxx.xxx

Number of addresses successfully converted is: 2

When the output should be:

Number of addresses found is: 2
Number of DNS names found is: 2
Sync Check Success!
-----------------------------------------------------------

DEBUG: VARIABLE (IP) IS:  192.168.xxx.xxx
DEBUG: VARIABLE (host) IS:  Alderaan
-----------------------------------------------------------

DEBUG: VARIABLE (IP) IS:  192.168.xxx.xxx
DEBUG: VARIABLE (host) IS:  Bespin

Bespin 192.168.xxx.xxx
Alderaan 192.168.xxx.xxx

Number of addresses successfully converted is: 2

here is my script code

#!/bin/sh

export IFS="
"
TotalAdrFound=`grep -E "address" -h xxx.cfg |cut -d ";" -f1|wc -l`
TotalDNSFound=`grep "A longer name" -h xxx.cfg|wc -l`

#Use grep to parase .cfg file and grab the ip number for each host
args=`grep "address" -h xxx.cfg| awk '{print $2;}'`


#Remove old list of DNS names.grep "address" -h *.cfg| cut -d "s" -f3 | cut -d ";" -f1 |
rm /tmp/addrs.netdb

#Use grep to parase .cfg file and grab the host name and output to addrs.netdb
sudo grep "A longer name" -h xxx.cfg| cut -d ";" -f1 |awk '{print $2;}' >> /tmp/addrs.netdb

TotalAdrSuccess=0

echo "Number of addresses found is: "$TotalAdrFound
echo "Number of DNS names found is: "$TotalDNSFound

#Check to see if every host has an  IP address.
if  [ "$TotalAdrFOUND"="$TotalDNSFound" ] ;
then
echo "Sync Check Success!"
else
echo "Sync Check FAILLED! There has been some type of error.  The Total Number " \
echo "     of Addresses found does not match the total number of DNS host names found."
fi

for IP in $args ;do
#Increment the line number for the sed command below
Line=$((Line+1))

#grab the host name for the IP
host=`sed "$Line!d" /tmp/addrs.netdb`

TotalAdrSuccess=$((TotalAdrSuccess+1))

#Some debug code for a select group of hosts.
echo "-----------------------------------------------------------\n"
echo "DEBUG: VARIABLE (IP) IS: " $IP 
echo "DEBUG: VARIABLE (host) IS: " $host

output="$IP $host\n$output"
done
echo "$output"
echo "Number of addresses sucessfully converted is: "$TotalAdrSuccess

Like I said above this works with all my other cfg files but this one. This cfg file is in the same format as all the other.

There are a few problematic lines in your script:

if  [ "$TotalAdrFOUND"="$TotalDNSFound" ] ;

i doubt that this works. correct should be (notice the spaces):

if  [ "$TotalAdrFOUND" = "$TotalDNSFound" ] ;

If i read the script source correctly the variables in question are both integers. You could also use integer comparation then (see man test for a complete description of available options)

if  [ $TotalAdrFOUND -eq $TotalDNSFound ] ;
Line=$((Line+1))

I am not sure if this works or not, but in any case this will definitely be correct (again, notice the spaces, they are necessary):

(( Line += 1 ))

A further tip: do NOT USE BACKTICKS! Backticks are an ancient device, which has some (quite intricate) shortcomings and are supported only for backwards compatibility. You use something like:

variable=`command`

and want the output of "command" to become the variables content. Use

variable="$(command)"

for this purpose, which has none of the shortcomings and all of the features of the above syntax.

One last tip: before using *any* variable, declare it at the beginning of your script. This offers the opportunity to document the variables contents, which helps greatly in maintaining your scripts, as well as to make sure all necessary variables are initialized with sensible values:

#!/bin/bash

# this is a sample script sketch

typeset    variable=""                # this is used for ....
typeset -i var_int=0                  # counter for loop cycling through something
typeset -i var_bool=0                 # processing flag, 0=process, 1=do not p.

<rest of your code>

I hope this helps.

bakunin

Thanks for the help. Your pointers certainly have helped made my code run better.

But the real problem I am having is with this part of code

#Some debug code for a select group of hosts.
echo "-----------------------------------------------------------\n"
echo "DEBUG: VARIABLE (IP) IS: " $IP 
echo "DEBUG: VARIABLE (host) IS: " $host

output="$IP $host\n$output"
done
echo "$output"

Instead of outputting to the terminal on the echo command what is intended I get the following:


Hostname.10.2

when it should be:

Hostname 192.168.10.2

I have over 600 addresses and host names and it only does this weird output for 8 of them. I want to say its an overflow but I can't convince my self that is what is happening.

Thanks again!

Well, probably they have helped some parts of your script run at all. In the meantime i have found some other syntactical errors and probable logic errors:

Sorry to say that, but more likely is the script produces the expected result out of pure chance with many hosts and isn't so lucky on the others.

First, have a look at these three lines:

TotalAdrFound=`grep -E "address" -h xxx.cfg |cut -d ";" -f1|wc -l`
TotalDNSFound=`grep "A longer name" -h xxx.cfg|wc -l`
<...>
if  [ "$TotalAdrFOUND"="$TotalDNSFound" ] ;

Variable names are case sensitive, therefore "$TotalAdrFOUND" will always be empty because it is never used nor given any value before. I suppose this to be a typing error, but this check (whatever it is for) can never have worked in the expected way.

Now for the probable cause of your visible problems:

for IP in $args ;do
#Increment the line number for the sed command below
Line=$((Line+1))

#grab the host name for the IP
host=`sed "$Line!d" /tmp/addrs.netdb`
<...>
done

To be honest this is by far the ....ahem.... most creatively phrased piece of code i ever saw. I needed several passes to even understand what it does.

If i get you correctly you have one or several IP addresses in $args. Now you want to take them one at a time in $IP and get the corresponding host names from a list stored in a file, reading it one by one, yes?

First, do not use a normal variable to store tabular data. This is what arrays are for. Now bash treats all variables in a pipeline local to this pipeline, so the following looks a little more clumsy than in ksh, but anyway:

(( index=1 ))
while read line ; do
     IP[$index]=$line
     (( index += 1 ))
done <<-EOF
     $(awk '{ /address/ {print $2};}' xxx.cfg)
EOF

This replaces the "$args" variable with an array with IP values. Test if they are correctly stored with the following:

(( index=1 ))
while [ $index -le ${#IP[*]} ] ; do
     echo IP[$index] is ${IP[$index]}
     (( index += 1 ))
done

Do the same with the host names. You just replace "address" with "A longer name" in the awk statement and replace the array name (like, say "DNS"). The logic stays the same.

Instead of this:

TotalAdrFound=`grep -E "address" -h xxx.cfg |cut -d ";" -f1|wc -l`
TotalDNSFound=`grep "A longer name" -h xxx.cfg|wc -l`
<...>
#Check to see if every host has an  IP address.
if  [ "$TotalAdrFOUND"="$TotalDNSFound" ] ;
then
<...>

you just use:

if  [ ${#IP[*]} -eq ${#DNS[*]} ] ; then
<...>

The construct "${#arrayname[*]}" is the number of elements stored in the array.

Now, instead of writing the DNS names into a temporary file first, then reading them back from there again, you simply do the following:

(( index=1 ))
while [ $index -le ${#IP[*]} ] ; do
     echo "-----------------------------------------------------------\n"
     echo "DEBUG: VARIABLE (IP) IS: " ${IP[$index]} 
     echo "DEBUG: VARIABLE (host) IS: " ${DNS[$index]}

     # or, alternatively, how you do your output:
     # echo "{IP[$index]}\t${DNS[$index]}"

     (( index += 1 ))
done

I hope this helps.

bakunin

.

Haha well I'll take that as a compliment ;).

Anyways thanks for the help and putting up with my attempt at learning bash. Seems I have a ways to go still.