Store All Lines from While Loop in Single Variable in Shell

shelltext processingvariable

As far as I know, while loops in shell are executed in a sub-shell, so they cannot modify variables outside the loop.

I'm writing a shell script and I want to store all the internal IPs of my machine into a single variable and so process this variable with a for loop to filter them one by one with iptables.

I could write this piece of code:

ip route show default | awk '{ print $5 }' | while read line; do
  ip address show dev ${line} scope global | awk '/inet / {sub(/\/.*/, "", $2); print $2}' | while read line; do
    echo "${line} "
  done
done

Output:

10.17.0.49 
192.168.1.4

My question is:

How can I store all these lines emitted by a while loop into a single variable (as while loop variables are volatile)?

Best Answer

To print all the global scope local addresses of the interfaces involved in default routes, I'd use the JSON format which can be processed programmatically in a more reliable way:

perl -MJSON -le '
  $default_routes = decode_json(qx(ip -j route show default));
  for (@$default_routes) {$devs{$_->{dev}} = 1}
  $addresses = decode_json(qx(ip -j address show));
  for (@$addresses) {
    if ($devs{$_->{ifname}}) {
      for (@{$_->{addr_info}}) {
        print $_->{local} if $_->{scope} eq "global";
      }
    }
  }'

Or the same using jq:

ip -j address show |
  jq -r --argjson devs "$(
      ip -j route show default|jq 'map({"key":.dev})|from_entries'
    )" '.[]|select(.ifname|in($devs)).addr_info[]|
      select(.scope == "global").local'

(it needs a relatively recent version of iproute2 though for JSON output support).

To get it (or more generally every line of the output of some command) into a bash array, use:

readarray -t array < <(
  that-command above
)

If the aim is to get the source IP address that packets going out on the default route would get, see for instance my answer to How to get my own IP address and save it to a variable in a shell script?.

Related Question