HOWTO: String splittings in Bash without using 'cut' and 'grep'

String operations on command line is a very frequent task. Even more frequently one needs to extract part of a string surrounded by particular ‘delimeters’ and assign it to a variable.

Consider this one-liner that returns MAC address of wireless interface by extracting the value from ‘ifconfig’ output:

$ ifconfig wlan0 | grep 'HWaddr' | cut -d' ' -f10
00:11:22:33:44:55

Conveniently, GNU Userland provides Linux users with a set a über-useful text-processing utilities,
cut and grep in particular. In the example above grep is used to isolate the line of ifconfig output containing ‘HWaddr’ string and cut to extract the value based on the count (10) of whitespace ’ ’ delimeters.

While this is a perfectly valid way, it involves two extraneous executions of cut and grep for both being separate programs. Shall this one-liner be run in a loop (eg for frequent value updates) it will induce unnecessary system load.

For instance, if we’d like to monitor the number of received bytes (RX) on the same wlan0 interface with a one second interval we could:

$ while rx=$(ifconfig wlan0 | grep 'RX bytes' | cut -d':' -f2 | cut -d' ' -f1); do
> echo $rx
> sleep 1
> done
4064924143
4064924156
4064924157
^C

But, as mentioned earlier this while-loop will be invoking 4 (!) four external commands (system calls) on every iteration, which on an embedded system can soon become problematic as complexity of your script is growing and more and more system calls are getting involved.

Luckily, Bash on its own provides all needed tools for string processing and lets us extract partial strings without the need for calling any external utilities:

$ while rx=$(ifconfig wlan0); do
> rx=${rx#*'RX bytes:'}
> echo ${rx/ *}
> sleep 1
> done
4064916178
4064916534
4064916756
^C

NOTE: Naturally, you can always read kernel statistics provided by the sysfs interface:

$ cat /sys/class/net/wlan0/statistics/rx_bytes

and to run this in a while-loop:

$ while read rx </sys/class/net/wlan0/statistics/rx_bytes; do
> echo $rx
> sleep 1
> done 
4066630396
4066631741
4066632037
^C

(read allows us to eliminate $(…) construct, avoiding spawning of a sub-shell)