For loop in bash - Direct output to two files

Hello all,

i have a code in which when doing a for loop, i need to direct the output to two files, one just a single output, the other to always append (historical reasons).

So far i managed to do the following, which is working, but am still considering it as "dirty".

INTERFACE_NAME=`$SNMP -v2c -c $COMMUNITY $HOST ifDescr | awk '{print$4}'`
for int in $INTERFACE_NAME
do
inOct1=`grep "$int " $HOMEDIR/cron/tmp/$(basename $0 .sh)1 | awk '{print$4}' | tr -d "incoming="`
inOct2=`grep "$int " $HOMEDIR/cron/tmp/$(basename $0 .sh)2 | awk '{print$4}' | tr -d "incoming="`

outOct1=`grep "$int " $HOMEDIR/cron/tmp/$(basename $0 .sh)1 | awk '{print$5}' | tr -d "outgoing="`
outOct2=`grep "$int " $HOMEDIR/cron/tmp/$(basename $0 .sh)2 | awk '{print$5}' | tr -d "outgoing="`

speed=`grep "$int " $HOMEDIR/cron/tmp/$(basename $0 .sh)1 | awk '{print$3}' | tr -d "speed="`
status=`grep "$int " $HOMEDIR/cron/tmp/$(basename $0 .sh)1 | awk '{print$1}' | tr -d "state=" | tr -d "([0-9])"`

#Diff the polls, divided by the interval
if [ $outOct2 -lt 0 ] || [ $outOct1 -lt 0 ] || [ $inOct2 -lt 0 ] || [ $inOct1 -lt 0 ]; then
    echo "Negative numbers not allowed...exiting"
    exit 1
fi

inUsage=`echo $(((inOct2 - inOct1) / TIME_INTERVAL))`
outUsage=`echo $(((outOct2 - outOct1) / TIME_INTERVAL))`

#Convert to bits
inUsage=`echo $((inUsage * 8))`
outUsage=`echo $((outUsage * 8))`

#get usage in percantage
perinUsage=`echo "scale=4;((($inUsage/$speed) * 100))" | bc`
perinUsage=`printf '%.3f\n' $perinUsage`
peroutUsage=`echo $(((outUsage/speed) * 100)) | bc`
peroutUsage=`printf '%.3f\n' $peroutUsage`

echo $DATE "|" $int "|" $speed "|" $status "|" $inUsage "|" $perinUsage "|" $outUsage "|" $peroutUsage
done | tee $OUTPUTDIR/$(basename $0 .sh) | tee -a  $HISTDIR/${DATESTAMP}.$(basename $0 .sh)

#Remove LOCK
rm -f ${LOCKFILE}

As you can see this is the interesting part:

echo $DATE "|" $int "|" $speed "|" $status "|" $inUsage "|" $perinUsage "|" $outUsage "|" $peroutUsage
done | tee $OUTPUTDIR/$(basename $0 .sh) | tee -a  $HISTDIR/${DATESTAMP}.$(basename $0 .sh)

where i need to output to two files.

Is there a way to simplify this by making the for loop output to two files and making the output to not show in the screen?

Thanks in advance for your comments

while true
do
         echo "Destination one" >&5
         echo "Destination two" >&6
         echo "Destination one again" >&5
         break
 done 5>destone 6>&1 | less

5 and 6 are arbitrary numbers higher than 2, as 0-2 are reserved for stdin/stdout/stderr.

1 Like

If this one echo statement is the only output I would go indeed for

output=`echo $DATE "|" $int "|" $speed "|" $status "|" $inUsage "|" $perinUsage "|" $outUsage "|" $peroutUsage`
echo "$output" >&5
echo "$output" >&6
done 5>$OUTPUTDIR/$(basename $0 .sh) 6>>$HISTDIR/${DATESTAMP}.$(basename $0 .sh)

Otherwise you need to keep one tee (a dup() can only be done by tee)

done | tee $OUTPUTDIR/$(basename $0 .sh) >>$HISTDIR/${DATESTAMP}.$(basename $0 .sh)
1 Like

Your TIME_INTERVAL variable is not expanded by the shell, so your results may not be the desired ones.
And, I'm not sure I understand why you overwrite the output file again and again in the loop, so only the last one will persist and be available / workable upon.
Instead of running 27 external commands for every single interface, you might want to consider doing it ALL in one awk script like

FNM=${0##*/}
FNM=${FNM%.sh}
$SNMP -v2c -c $COMMUNITY $HOST ifDescr |
awk -vX=2 -vDT="$DATE" -vTI="$TIME_INTERVAL" -vOF="$OUTPUTDIR/$FNM" -vHF="$HISTDIR/${DATESTAMP}.$FNM" '
FNR == NR       {INT[$4]++
                 next
                }
FNR == 1        {FC++
                }

$X in INT       {
                 sub (/incoming=/, ""; $4)
                 sub (/outgoing=/, ""; $5)
                 if (($4 < 0) || ($5 < 0))      {print "Negative numbers not allowed...exiting"
                                                 exit 1
                                                }
                 inOct [$X,FC] = $4
                 outOct[$X,FC] = $5

                 sub (/speed=/, ""; $3)
                 if (!speed[$X]) speed[$X] = $3

                 sub (/state=/, ""; $1)
                 sub /\([0-9]\)/, "", $1)
                 if (!state[$X]) state[$X] = $1
                }
END             {for (i in INT) {inUsage  = (inOct [i,2] - inOct [i,1]) / TI * 8
                                 outUsage = (outOct[i,2] - outOct[i,1]) / TI * 8
                                 TMP = sprintf ("%s|%s|%.f|%s|%.0f|%.3f|%.0f|%.3f\n", DT, i, speed, state, inUsage, inUsage/speed*100, outUsage, outUsage/speed*100)
                                 print TMP  >  OF
                                 print TMP  >> HF
                                 close (OF)
                                 close (HF)
                                }
                }
                 
' - $HOMEDIR/cron/tmp/${FNM}1 $HOMEDIR/cron/tmp/${FNM}2

Define X to be the column the interface name is in (here assumed to be 2). Unfortunately, this is just a vague proposal as there are no data nor data structures known to allow for a decent testing.

1 Like

Hi,

@Rudi C. I need to try to that to get the script much "cleaner" as am still not yet happy with the code.
In the meantime i have also tried with the arbitrary echos and all worked fine (both Corona's and MadeInGermany's)
Thanks all for the contribution guys.