Get My IP

Using Existing Tools

Efficient programming requires planning at both the design stage and the implementation stage. You can only improve an inefficient design so much. When prototyping with bash, work with existing tools. Typical implementation tradeoff is space against time. Using the best tools frequently improves both. Take advantage of their greatest speed and greatest facility.

To outline some preliminary techniques, examine a seemingly trivial issue: getting the local IP address. Several query methods, the old ifconfig(8), the new ip(8), and nmcli(1), give IPs, but their different output requires different extraction methods.

Output With the Old

Querying the interface configuration (ifconfig) gives lots of network information and interface facts:

$ ifconfig
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 10.138.2.4  netmask 255.255.255.255  broadcast 10.255.255.255
        inet6 aaaa::bbb:cccc:dddd:eeee  prefixlen 64  scopeid 0x20<link>
        ether 01:23:45:ab:cd:ef  txqueuelen 1000  (Ethernet)
        RX packets 16  bytes 1072 (1.0 KiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 10  bytes 776 (776.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

 lo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536
        inet 127.0.0.1  netmask 255.0.0.0
        inet6 ::1  prefixlen 128  scopeid 0x10<host>
        loop  txqueuelen 1  (Local Loopback)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 0  bytes 0 (0.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

This first system has a regular Ethernet, eth0, using the IP (IPv4) address 10.138.2.4 and the standard loopback, lo, network device. It uses the typical local 10-net, 10.x.y.z, addressing.

Here’s another system with more interfaces:

$ ifconfig
enp3s0f0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 192.168.254.1  netmask 255.255.255.0  broadcast 192.168.254.255
        inet6 ffff::eee:dddd:cccc:bbbb  prefixlen 64  scopeid 0x20<link>
        ether 00:1a:2b:3c:4e:5f  txqueuelen 1000  (Ethernet)
        RX packets 440913  bytes 129332910 (123.3 MiB)
        RX errors 0  dropped 12  overruns 0  frame 0
        TX packets 269164  bytes 19509829 (18.6 MiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
        device interrupt 38

 lo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536
        inet 127.0.0.1  netmask 255.0.0.0
        inet6 ::1  prefixlen 128  scopeid 0x10<host>
        loop  txqueuelen 1000  (Local Loopback)
        RX packets 68  bytes 12090 (11.8 KiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 68  bytes 12090 (11.8 KiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

 virbr0: flags=4099<UP,BROADCAST,MULTICAST>  mtu 1500
        inet 192.168.122.1  netmask 255.255.255.0  broadcast 192.168.122.255
        ether 01:23:45:54:32:10  txqueuelen 1000  (Ethernet)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 0  bytes 0 (0.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

 wlp4s0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 192.168.254.2  netmask 255.255.255.0  broadcast 192.168.254.255
        inet6 abcd::bcde:cde:defa:efab  prefixlen 64  scopeid 0x20<link>
        ether ff:ee:dd:cc:bb:aa  txqueuelen 1000  (Ethernet)
        RX packets 27  bytes 3395 (3.3 KiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 42  bytes 8024 (7.8 KiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

On this second system are:

  • The regular Ethernet, enp3s0f0
  • The loopback, lo
  • A virtual network interface, virbr0
  • A wireless interface, wlp4s0

These interface use names, such as en for Ethernet on a wire and wl for wireless, and use common local net addresses, 192.168.x.y, assigned by a router with DHCP. Virtual bridge, virbr, interfaces use preset addresses for network address translation (NAT) to relay network packets.

Note: See virsh(1) to play around with virbr0. Use man -k dhclient to begin reading about DHCP.

Notice that each of these entries has an “inet” entry. Which one is mine when I send packets to the network? The first system is easy. There’s only one. The loopback doesn’t go out from the local system,so only eth0‘s address can go out. The second one is not so simple. Ignoring virbr0, packets could go out enp3s0f0 or wlp4s0.

What’s the Route?

The older style route(8) table, route -n, shows a metric that decides which interface is best to use. The lower the metric number, the faster the network. For example, the second system’s routing table looks like this:

$ route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         192.168.254.254 0.0.0.0         UG    100    0        0 enp3s0f0
0.0.0.0         192.168.254.254 0.0.0.0         UG    600    0        0 wlp4s0
192.168.122.0   0.0.0.0         255.255.255.0   U     0      0        0 virbr0
192.168.254.0   0.0.0.0         255.255.255.0   U     100    0        0 enp3s0f0
192.168.254.0   0.0.0.0         255.255.255.0   U     600    0        0 wlp4s0

Specific destination addresses list specific routes. Look at the gateway entries — the ones with the “G” flag. Both the wired network (en) and the wireless network (wl) are gateways for all arbitrary destinations (0.0.0.0) that aren’t listed in the local networks in the Destination column. The wired net has metric 100, so the system will favor it over the wireless with metric 600.

For programming purposes, to get the gateway interface name with the fastest metric, use egrep to isolate only the default, 0.0.0.0, destinations.

$ route -n | egrep '^0.0.0.0'

Using the regular expression character, carat (^), at the beginning restricts the match to the beginning of each line. Because there could be more than one interface handling the default destination, as in this example, sort(1) them numerically by the metric in field 5:

$ route -n | egrep '^0.0.0.0' | sort -k 5n

The first line sort outputs will show the fastest network. Isolate it:

$ route -n | egrep '^0.0.0.0' | sort -k 5n | head -1

Now extract the interface name in the last field. There are two ways to isolate the last field: awk(1) and cut(1).

Using awk may seem wasteful at first. Some might say you’re loading a whole string processing language to get one field from one line. Why not use the tinier cut program?

Don’t underestimate awk.

Awk parses fields separated by any combination of spaces and tabs. Cut parses by tabs only. Because the routing table is aligned with spaces, something has to reduce the spaces between columns from many to one.You can do that by piping the output through tr(1) or you can put the pipeline into a subshell and pass its output to echo, which reduces multiple spaces into one each.

Which method is fastest? Test the awk technique:

$ time for n in {1..1000}
do
  route -n | egrep '^0.0.0.0' | sort -k 5n | head -1 |
  awk '{print $NF}' >/dev/null
done

Loop 1000 times through the route pipeline using awk. In awk, NF is the internal variable name for the Number of Fields in the current input line. Putting a dollar sign ($) in front of it turns the count into a field number and produces the field’s value. Thus, if NF is 8, $NF is equivalent to $8, which is the eighth field value. That prints the interface field name from the route table entry. Because the concern is timing, toss that name out to the /dev/null bit bucket.

Using the awk method, how fast does it go? Here’s that one’s sample time:

real    0m3.067s
user    0m3.133s
sys     0m5.686s

Each pipeline run takes an average of about 3 milliseconds. Now test the cut technique. First attempt tries using echo on the subshell output:

$ time for n in {1..1000}
do
  echo $(route -n | egrep '^0.0.0.0' | sort -k 5n | head -1) |
  cut -d' ' -f8 >/dev/null
done

The same route pipeline runs in a subshell giving the route table output line to echo. After echo reduces the column spacing to one space each, pipe it to cut to extract the eighth field. Again, cast it out to /dev/null just to get a time:

real    0m6.185s
user    0m4.018s
sys     0m8.425s

Not so good. This took twice as long! But, what about the other variation? Instead of using echo, squeeze the spaces with tr:

$ time for n in {1..1000}
do
  route -n | egrep '^0.0.0.0' | sort -k 5n | head -1 |
  tr -s ' ' | cut -d' ' -f8 >/dev/null
done

This eliminates the extra subshell echo required. But is it fast enough?

real    0m2.971s
user    0m3.118s
sys     0m5.914s

Slightly faster than awk, averaging 2.97 ms. Very nice.

The problem with searching for the interface name in ifconfig is that it is not on the same line as the IP address associated with it. It’s on one of the indented lines after the interface name.

Awk can come to the rescue again. Searching in awk, using the same regular expressions as egrep, can find the line with the name then read the next line to get the IP:

$ ifconfig | awk '/^eth0:/ {getline; print}'
    inet 10.138.2.4  netmask 255.255.255.255  broadcast 10.255.255.255 

$ ifconfig | awk '/^eth0:/ {getline; print $2}'
10.138.2.4

How long does this take?

$ time for n in {1..1000}
do
  ifconfig | awk '/^eth0:/ {getline; print $2}' >/dev/null
done 

real    0m5.541s
user    0m1.360s
sys     0m4.070s

Awk takes over 5 milliseconds to handle the data.

Substitute the route pipeline for the name between the caret (^) and the colon (:). The easiest way to do that is to close the apostrophe after the caret, use a subshell for the route pipeline, then open the apostrophe again before the colon to give the subshell’s output to awk:

$ ifconfig | awk '/^'$(
  route -n | egrep "^0.0.0.0" | sort -k 5n | head -1 |
  tr -s " " | cut -d" " -f8
)':/ {getline; print $2}'
10.138.2.4

So, how fast is this? Insert the time prefix in front of a loop for the previous command line:

$ time for n in {1..1000}
do
  ifconfig |
  awk '/^'$(
    route -n | egrep "^0.0.0.0" | sort -k 5n | head -1 |
    tr -s " " | cut -d" " -f8
  )':/ {getline; print $2}' >/dev/null
done 

real    0m23.538s
user    0m5.051s
sys     0m17.982s

Assign this result to a variable in your script:

my_ip="$(
  ifconfig | awk '/^'$(
    route -n | egrep "^0.0.0.0" | sort -k 5n | head -1 |
    tr -s " " | cut -d" " -f8
  )':/ {getline; print $2}'
)"

The Fork In The Route

The newer style for a route table uses ip route. See ip-route(8). Here’s the full route table shown on a Qubes system. First, route -n output for comparison:

$ route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         10.137.2.1      0.0.0.0         UG    0      0        0 eth0
10.137.2.1      0.0.0.0         255.255.255.255 UH    0      0        0 eth0

Now, the newer style for the same system:

$ ip route show table all
default via 10.137.2.1 dev eth0
10.137.2.1 dev eth0  scope link
local 10.138.2.4 dev eth0  table local  proto kernel  scope host  src 10.138.2.4
broadcast 10.255.255.255 dev eth0  table local  proto kernel  scope link  src 10.138.2.4
broadcast 127.0.0.0 dev lo  table local  proto kernel  scope link  src 127.0.0.1
local 127.0.0.0/8 dev lo  table local  proto kernel  scope host  src 127.0.0.1
local 127.0.0.1 dev lo  table local  proto kernel  scope host  src 127.0.0.1
broadcast 127.255.255.255 dev lo  table local  proto kernel  scope link  src 127.0.0.1
aaaa::/64 dev eth0  proto kernel  metric 256  pref medium
unreachable default dev lo  proto kernel  metric 4294967295  error -101 pref medium
local ::1 dev lo  table local  proto none  metric 0  pref medium
local aaaa::bbb:cccc:dddd:eeee dev lo  table local  proto none  metric 0  pref medium
ff00::/8 dev eth0  table local  metric 256  pref medium
unreachable default dev lo  proto kernel  metric 4294967295  error -101 pref medium

Several duplications exist showing IPv4 vs IPv6 and other variations. Keep it simple. Just pay attention to the default:

$ ip route show default
default via 10.137.2.1 dev eth0
10.137.2.1 dev eth0  scope link 

The default route goes through eth0. Use the -4 option selects IPv4 to restrict it further:

$ ip -4 route show default
default via 10.137.2.1 dev eth0 

Because there’s only one default route, no metric is shown. Looking at the other system using both wired and wireless connections:

$ ip -4 route show default
default via 192.168.254.254 dev enp3s0f0 proto static metric 100
default via 192.168.254.254 dev wlp4s0 proto static metric 600 

Both devices appear so metrics show. The device name is in the fifth field, preceded by dev in the fourth.

With more than one line there will be a metric. Sort lines numerically by the ninth field, take out sort‘s first line and use its fifth field to get the interface name.

Lacking a ninth field will not affect sort. It ignores the request and moves on. For example, on the system with only one interface:

$ ip -4 route show default | sort -k 9n | head -1 | cut -d' ' -f5
eth0

For the system with multiple interfaces:

$ ip -4 route show default | sort -k 9n | head -1 | cut -d' ' -f5
enp3s0f0

You could plug this pipeline in to the ifconfig | awk pipeline in place of the route pipeline:

$ time for n in {1..1000}
do
  ifconfig | awk '/^'$(
    ip -4 route show default | sort -k 9n | head -1 | cut -d' ' -f5
  )':/ {getline; print $2}' >/dev/null
done 

real    0m17.667s
user    0m3.338s
sys     0m14.004s

Much faster, averaging under 18 ms.

Using the ip address command — see ip-address(8) gives similar info as ifconfig.

$ ip address
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether 00:16:3e:5e:6c:01 brd ff:ff:ff:ff:ff:ff
    inet 10.138.2.4/32 brd 10.255.255.255 scope global eth0
      valid_lft forever preferred_lft forever
    inet6 fe80::216:3eff:fe5e:6c18/64 scope link
      valid_lft forever preferred_lft forever

Know the interface name? Narrow the output:

$ ip address show dev eth0
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether 01:23:45:ab:cd:ef brd ff:ff:ff:ff:ff:ff
    inet 10.138.2.4/32 brd 10.255.255.255 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 aaaa::bbb:cccc:dddd:eeee/64 scope link
       valid_lft forever preferred_lft forever

Given you only want the IPv4 format:

$ ip -4 address show dev eth0
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    inet 10.138.2.4/32 brd 10.255.255.255 scope global eth0
       valid_lft forever preferred_lft forever

While the format is different, the address appears on an indented line below the line with the interface name, as it does with ifconfig. The technique to extract it isn’t significantly different.

$ ip -4 address show dev eth0 | awk '/eth0:/ {getline; print $2}'
10.138.2.4/32

The /32 is CIDR notation for host routing. The number after the slash means that number of the first bits is the network portion. Remaining bits are the host portion. This eliminates a separate network mask. For this purpose remove the /32 notation.

$ ip -4 address show dev eth0 | awk '/eth0:/ {getline; print $2}' | cut -d/ -f1
10.138.2.4

Removing using cut is more direct than awk. Awk would need a call to its split() function, then access the resulting array.

While this does seem easier, it requires knowing the interface name ahead of time. The pipeline uses that name twice. Instead, let thesystem identify the interface name. Store it in a variable:

$ my_ip=$(ip -4 route show default | sort -k 9n | head -1 | cut -d' ' -f5) 

$ my_ip=$(ip -4 address show dev ${my_ip} | awk '/'$e':/ {getline; print $2}' | cut -d/ -f1) 

$ echo $my_ip
10.138.2.4

The first stores the interface name from the default routing in the my_ip variable. The second uses it to get the address of that interface name and overwrites the my_ip variable. But, using this two-pipeline process doesn’t seem very helpful. How fast is this compared with the ifconfig method?

$ time for n in {1..1000}
do
  my_ip=$(ip -4 route show default | sort -k 9n | head -1 | cut -d' ' -f5)
  my_ip=$(ip -4 address show dev ${my_ip} |
    awk '/'$my_ip':/ {getline; print $2}' | cut -d/ -f1)
done 

real    0m21.672s
user    0m4.133s
sys     0m17.165s

Faster than the ifconfig | route -n technique, but not as fast as the ifconfig | ip route.

Managing With Net Manager

Using nmcli(1) — the NetManager Command Line Interface — instead of ifconfig makes a lot more information available in many formats. Not all systems use nmcli. Check whether your system uses it with:

$ nmcli general
STATE      CONNECTIVITY  WIFI-HW  WIFI     WWAN-HW  WWAN
connected  full          enabled  enabled  enabled  enabled 

Otherwise, you’ll get the following:

$ nmcli general
Error: NetworkManager is not running.

Using nmcli without arguments gives all the current interfaces:

$ nmcli
virbr0: connected to virbr0
        bridge, 01:23:45:54:32:20, sw, mtu 1500
        inet4 192.168.122.1/24 

enp3s0f0: connected to p4p1
        "JMicron Technology JMC250 PCI Express Gigabit Ethernet Controller"
        ethernet (jme), 00:1a:2b:3c:4e:5f, hw, mtu 1500
        ip4 default
        inet4 192.168.254.1/24
        inet6 ffff::eee:dddd:cccc:bbbb/64 wlp4s0: connected to myssid
        "Intel Centrino Ultimate-N 6300 (3x3 AGN)"
        wifi (iwlwifi), ff:ee:dd:cc:bb:aa, hw
        inet4 192.168.254.2/24
        inet6 abcd::bcde:cde:defa:efab/64 lo: unmanaged
        loopback (unknown), 00:00:00:00:00:00, sw, mtu 65536 

virbr0-nic: unmanaged
        tun, 01:23:45:54:32:10, sw, mtu 1500 

Use "nmcli device show" to get complete information about known devices and
"nmcli connection show" to get an overview on active connection profiles. 

Consult nmcli(1) and nmcli-examples(5) manual pages for complete usage details.

Names appear at the beginning of each interface’s first line followed by a colon. Indented on subsequent lines are inet4 and inet6 addresses using CIDR notation. This leads to a simple awk script:

$ nmcli | awk '/^[^\t]*:/ {name = $1} /inet4/ {print name $2}'
virbr0:192.168.122.1/24
enp3s0f0:192.168.254.1/24
wlp4s0:192.168.254.2/24

Note: A tab (\t) appears between the second carat (^) and the close bracket (]).

In awk, slashes define a regular expression pattern for the action that follows. Actions are surrounded by braces. The first regular expression matches, at the beginning of a line, any characters that are not tabs up to a colon. It matches the interface names but not indented lines. Store the found name. Then, on the inet4 line, output that stored name and the second field of the inet4 line.

The result is easily parsed. Add a pipe to tr that replaces any colon or slash with a space:

$ nmcli | awk '/^[^\t]*:/ {name = $1} /inet4/ {print name $2}' | tr ':/' '  '
virbr0 192.168.122.1 24
enp3s0f0 192.168.254.1 24
wlp4s0 192.168.254.2 24

With space-separated lines, it’s easy to treat the names and IPs as fields.

However, nmcli has lots more information available.

$ nmcli conn show --active
NAME     UUID                                  TYPE             DEVICE
myssid   c030b2cf-d29e-436d-9720-c6657f37b218  802-11-wireless  wlp4s0
p4p1     9090f1ac-8bbb-4aa2-8be1-fe0c7e4ceef8  802-3-ethernet   enp3s0f0
virbr0   d9a45dbb-d6ce-47e7-bc13-5af099f5519a  bridge           virbr0   

Using the --active option eliminates available devices defined but currently unused. Column 1 shows the name assigned to the device by the system. Column 4 has the interface name for each device. You could isolate column 4, but nmcli already has a way to do that: the field option, -f:

$ nmcli -f device conn show --active
DEVICE
wlp4s0
enp3s0f0
virbr0   

Armed with interface names you could get information about each device, but only one at a time:

$ nmcli device show enp3s0f0
GENERAL.DEVICE:                         enp3s0f0
GENERAL.TYPE:                           ethernet
GENERAL.HWADDR:                         00:1A:2B:3C:4E:5F
GENERAL.MTU:                            1500
GENERAL.STATE:                          100 (connected)
GENERAL.CONNECTION:                     p4p1
GENERAL.CON-PATH:                       /org/freedesktop/NetworkManager/ActiveConnection/0
WIRED-PROPERTIES.CARRIER:               on
IP4.ADDRESS[1]:                         192.168.254.1/24
IP4.GATEWAY:                            192.168.254.254
IP4.DNS[1]:                             192.168.254.254
IP4.DNS[2]:                             74.40.74.41
IP4.DOMAIN[1]:                          Home
IP6.ADDRESS[1]:                         ffff::eee:dddd:cccc:bbbb/64
IP6.GATEWAY:                            

Parsing this is also easy. Awk or even echo can get rid of extra spacing, but nmcli has an option for that, too:

$ nmcli -t device show enp3s0f0
GENERAL.DEVICE:enp3s0f0
GENERAL.TYPE:ethernet
GENERAL.HWADDR:00:90:F5:C2:91:84
GENERAL.MTU:1500
GENERAL.STATE:100 (connected)
GENERAL.CONNECTION:p4p1
GENERAL.CON-PATH:/org/freedesktop/NetworkManager/ActiveConnection/0
WIRED-PROPERTIES.CARRIER:on
IP4.ADDRESS[1]:192.168.254.1/24
IP4.GATEWAY:192.168.254.254
IP4.DNS[1]:192.168.254.254
IP4.DNS[2]:74.40.74.41
IP4.DOMAIN[1]:Home
IP6.ADDRESS[1]:fe80::290:f5ff:fec2:9184/64
IP6.GATEWAY:

The terse option, -t, eliminates the more human readable spacing, leaving only the colon separator.

What remains? Isolating the IP address.Whether IP4 or IP6 is important, you could isolate it with awk or grep, but why not use nmcli‘s field option:

$ nmcli -f ip4.address device show enp3s0f0
IP4.ADDRESS[1]:                         192.168.254.1/24

There’s that spacing again. Make it terse:

$ nmcli -t -f ip4.address device show enp3s0f0
IP4.ADDRESS[1]:192.168.254.1/24

Isolate the colon and slash as before and the fields come available. In fact, if it weren’t for the period between the IP4 and the ADDRESS[1] this notation could become a variable assignment. Changing the period (.) to an underscore (_) and the colon (:) to an equals (=) turns the syntax into a bash variable assignment ready to run.

But, tr won’t work to make the character changes. Using tr turns every period to an underscore. That fouls the IP’s dotted-octet notation. Instead, use sed(1). Its substitution method only replaces the first match unless expressly told to replace all of them, globally.

$ nmcli -t -f ip4.address device show enp3s0f0 |
sed -e 's/\./_/' -e 's/:/=/'
IP4_ADDRESS[1]=192.168.254.1/24

Sed can take multiple expressions, each using the -e option. Escape the period, which is a regular expression that matches any single character, by preceding it with a backslash (\). Finally, tr gets rid of the all caps.

$ nmcli -t -f ip4.address device show enp3s0f0 |
sed -e 's/\./_/' -e 's/:/=/' |
tr '[:upper:]' '[:lower:]'
ip4_address[1]=192.168.254.1/24

Combining all the interface names with this technique allows generating an assignment list:

$ for name in $(nmcli -f device conn show --active | tail -n +2)
do
  echo ${name}_$(
    nmcli -t -f ip4.address device show ${name} |
    sed -e 's/\./_/' -e 's/:/=/' |
    tr '[:upper:]' '[:lower:]'
  )
done
wlp4s0_ip4_address[1]=192.168.254.2/24
enp3s0f0_ip4_address[1]=192.168.254.1/24
virbr0_ip4_address[1]=192.168.122.1/24

Ready for array assignment. Just replace echo with eval. Access the arrays using the following notation:

$ echo ${wlp4s0_ip4_address[1]}
192.168.254.2/24 

$ echo ${enp3s0f0_ip4_address[1]}
192.168.254.1/24 

$ echo ${virbr0_ip4_address[1]}
192.168.122.1/24

The problem with this notation: you must know the interface names generated by nmcli conn show ahead of time. Not particularly convenient, nor would such knowledge make for a portable program. To solve this problem, instead of an index number use the interface name as the array index.

Does an interface have more than one IP address? Could there be a [1] and a [2] for the same interface on IP4? To handle that, combine the interface name with the number:

$ for name in $(nmcli -f device conn show --active | tail -n +2)
do
  echo $(
    nmcli -t -f ip4.address device show ${name} |
    sed -e 's/\./_/' -e 's/:/=/' -e 's/\[/\['${name}_'/' |
    tr '[:upper:]' '[:lower:]'
  )
done
ip4_address[wlp4s0_1]=192.168.254.2/24
ip4_address[enp3s0f0_1]=192.168.254.1/24
ip4_address[virbr0_1]=192.168.122.1/24

Adding an extra expression (-e) to the sed command substitutes the opening bracket with the same bracket followed by the interface name. Add an underscore to separate the name from the number.

This technique uses the assocative array notation, where instead of an index number as a subscript, use a name. Using names associates one kind of data with another kind, in this case, the interface name associates with the IP4 address. To create such variables, define the variable as associative so bash won’t assume it’s a regular indexed variable. Then, replace echo with eval to set the variables.

declare -A ip4_address 

for name in $(nmcli -f device conn show --active | tail -n +2)
do
  eval $(
    nmcli -t -f ip4.address device show ${name} |
    sed -e 's/\./_/' -e 's/:/=/' -e 's/\[/\['${name}_'/' |
    tr '[:upper:]' '[:lower:]'
  )
done

You now have an array of variables, named ip4_address, indexed with interface names. Your program discovers the interfaces and their addresses by querying the array:

$ echo ${#ip4_address[*]}
3 

$ echo ${!ip4_address[*]}
virbr0_1 enp3s0f0_1 wlp4s0_1 

$ echo ${ip4_address[*]}
192.168.122.1/24 192.168.254.1/24 192.168.254.2/24

The first example shows the quantity of IP4 addresses stored in thearray. The second gives space-separated interface names. The third shows the CIDR IPs for each of those interfaces. Knowing the variable’s name gives you all the access you need. Use the name nmcli uses as the address type, ip4_address or ip6_address, modified with underscore instead of dot and lowercase instead of all caps.

$ for iface in ${!ip4_address[*]}
do
  echo ${iface} is ${ip4_address[${iface}]}
done
virbr0_1 is 192.168.122.1/24
enp3s0f0_1 is 192.168.254.1/24
wlp4s0_1 is 192.168.254.2/24

Now, your program can use any of these IPs by their names.

 

Leave a Comment