7

I'm writing a script to collect some various network statistics. What I'm trying to do is to produce some delta data from the netstat -i command.

I'm collecting the needed data with the following bash code:

declare -a array
n=0
netstat -i | tail -n +3 | while read LINE; do
        echo "Setting array[$n] to $LINE"
        array[$n]=$LINE
        echo "array now have ${#array[@]} entries"
        let n=$n+1
done
echo "array now have ${#array[@]} entries"

output from this command is:

Setting array[0] to eth0       1500 0   4946794      0      0 0       2522971      0      0      0 BMRU
array now have 1 entries
Setting array[1] to lo        16436 0     25059      0      0 0         25059      0      0      0 LRU
array now have 2 entries
Setting array[2] to vmnet1     1500 0         6      0      0 0          1126      0      0      0 BMRU
array now have 3 entries
Setting array[3] to vmnet8     1500 0       955      0      0 0          1054      0      0      0 BMRU
array now have 4 entries
Setting array[4] to wlan0      1500 0    613879      0      0 0        351194      0      0      0 BMU
array now have 5 entries
array now have 0 entries

As you can see, the array actually disappear after the while loop, and I do not understand why.

4 Answers 4

6

Any time you use a pipe you create an implicit subshell. When that subshell terminates, so do its variables. A quick fix for this is to not pipe stuff to read. You can accomplish the above using process substitution:

while read LINE; do
        echo "Setting array[$n] to $LINE"
        array[$n]=$LINE
        echo "array now have ${#array[@]} entries"
        let n=$n+1
done < <(netstat -i | tail -n +3)

A more POSIX compliant approach (read: more portable, less bashist) is to make everything happen in the subshell:

netstat -i | tail -n +3 | {
    declare -a array
    n=0
    while read LINE; do
        echo "Setting array[$n] to $LINE"
        array[$n]=$LINE
        echo "array now have ${#array[@]} entries"
        let n=$n+1
    done
    echo "array now have ${#array[@]} entries"
}

You can read the fine points of this (and more) at Greg Wooledge's wiki.

Sign up to request clarification or add additional context in comments.

3 Comments

Thanks, the first code works perfectly. But the array is lost outside the subshell. I suspect this is the intention, but does not work for me.
Very nice. I prefer the POSIX compliant approach because it provides the input data at the start, supporting chronological reading.
@Arthur you can provide the input data at the start either way. < <(netstat …) while read LINE; do …
2

If your only goal is to put the output of a command into an array (linewise), you'd better use the (sadly not very well-known) mapfile bash builtin, it's by far the most efficient (and the best suited for code golf, count how many character strokes I have compared to the other possibilities):

mapfile -t array < <(netstat -i | tail -n +3)

The other answers explain why your construct didn't work (pipe is in a subshell and all that).

help mapfile for all the details and possibilities of that command.

Comments

1

Ok, are you ready?

There is how to transform netstat -i | tail -n +3 in a bash Associative Array of array:

declare -A AANET
while read -a line ;do
    declare -a AI$line
    eval "AI$line=(${line[@]})"
    AANET[$line]=AI$line
  done < <(
    netstat -i |
       tail -n +3)

Than now:

echo ${!AANET[@]}
venet0 eth1 eth0 lo br0

echo ${AANET[eth0]}
AIeth0

And for sub-associative, we have to use eval:

eval echo \${${AANET[eth0]}[@]}
eth0 1500 0 17647 0 0 0 35426 0 0 0 BMPU

eval echo \${${AANET[eth0]}[1]}
1500

eval echo \${${AANET[eth0]}[3]}
17647

eval echo \${${AANET[eth0]}[7]}
35426

eval echo \${${AANET[eth0]}[@]:3:5}
17647 0 0 0 35426

An for assing a temporary variable:

eval currentBin=\${${AANET[eth0]}[3]} currentBout=\${${AANET[eth0]}[7]}
echo $currentBout 
35426
echo $currentBin 
17647

or even too:

eval "declare -a currentVals=(\${${AANET[eth0]}[@]:3:8})"
echo ${currentVals[0]}
17647
echo ${currentVals[4]}
35426
echo ${currentVals[@]}
17647 0 0 0 35426 0 0 0

Edit:

Ok, if it is possible without eval!

for aKey in ${!AANET[@]};do
    fields=(${AANET[$aKey]}{[1],[3],[7]});
    echo $aKey ${!fields} ${!fields[1]} ${!fields[2]}
  done |
    xargs printf "%-9s %12s %12s %12s\n" IFace MTU RX TX

IFace              MTU           RX           TX
venet0            1500            0            0
eth1              1500      6400292      6942577
eth0              1500        17647        35426
lo               16436           83           83

7 Comments

Too much eval for me :-(. Bash was never designed for that kind of stuff. If you really need these constructs, just use another language.
@gniourf_gniourf Are you comming from php? You could use eval in such case where you know which kind of data could be evalued. In this situation, command netstat -i won't return backtick or such cross-scripting echapments.
Nope, not coming from php at all. Just that I know what language is suited for what task (or at least, I believe I do).
Btw, you can achieve the same without any eval's (using indirect expansion), but what's the point anyways?
It's not really the purpose of this post to explain that but something around this (that will be safe regarding everything): array=(field0 field1); name="array[@]"; echo "${!name}"; keyfield0=0; namefield0="array[0]"; echo "${!namefield0}", then build up on this (with helper functions).
|
0

Don't pipe to a loop, bash substitutes variables first and then starts a subshell without access to your array.

Do it like this. Nice and simple.

array=()
for alias in `netstat -i | tail -n +3`; do
    array+=($alias)
done
echo ${array[@]}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.