So, you want to parse the multi-line output of a shell command… this proved a little more troublesome than I was expecting…

As an example, the output of ‘who’ is:
administrator console  Dec  4 08:27
administrator ttys000  Dec  4 10:01
administrator ttys001  Dec  4 10:47

Or maybe you want to do something with resource intensive programs:
ps -e -o pid= -o user= -o pcpu= -o pmem= -o comm= | sort -n -k3,4 -r | head -5

Method 1: ‘mapfile’ (or ‘readarray’)

The first method I used was ‘mapfile’ (‘readarray’ also worked). I also use ‘sed’ to remove any consecutive spaces (so I could use ‘cut’ with a space as the delimiter).

#!/bin/bash
echo ""
echo "Output of 'who':"
who

# get output from 'who', put each line into array
mapfile -t Instances < <(who | sed 's/\( \)*/\1/g')
#readarray -t Instances < <(who | sed 's/\( \)*/\1/g')

echo ""
echo -e "${#Instances[@]} entries found!"
for Instance in "${Instances[@]}"; do
#    echo -e "\t$Instance"

    #username
    username=`(echo -e "$Instance") | cut -d' ' -f1-1`
    #terminal port
    port=`(echo -e "$Instance") | cut -d' ' -f2-2`
    #date
    date=`(echo -e "$Instance") | cut -d' ' -f3-3`

    echo "username = $username, port = $port, date = $date"
done

Method 2: ‘while IFS= read -r

The problem with ‘mapfile’/’readarray’ is that they don’t work in some shells (such as Mac’s default). Here is an alternate option:

#!/bin/ksh
Instances2=`who | sed 's/\( \)*/\1/g'`

printf %s "$Instances2" | while IFS= read -r Instance
do
   echo "$line"
done

Method 2a:

The server I tested the script on was using the Korn (ksh) shell. It didn’t give any errors, but wasn’t outputting anything either. This slight variation fixed it (replaced ‘printf’ with ‘echo’):

#!/bin/ksh
Instances2=`who | sed 's/\( \)*/\1/g'`

echo "$Instances2" | while IFS= read -r Instance; do
  echo "$Instance"
done

Advertisements