9

I am trying to do something simple however I'm not sure how to achieve my goal here.

I am trying to extract the: USER, TTY and FROM values that are given by the w command on the console. In bash I am trying to take this output and get these values into a multidimensional array (or just an array with a space delimiter).

#!/bin/bash
w|awk '{if(NR > 2) print $1,$2,$3}' | while read line
do
     USERS+=("$line")
     echo ${#USERS[@]}
done
echo ${#USERS[@]}

I have found my way to the point of reading in the values by line in a single array however I cannot seem to get the USERS array value out of the scope of the while loop. It prints the values 1,2,3,4 and then 0 after the loop. Every example I read they use the variable outside the scope perfectly fine but I cannot seem to.

3
  • The right side of a pipeline runs in a subshell in bash, that's why it's not available after the loop.
    – jordanm
    Commented Jun 7, 2013 at 21:26
  • @jordanm so there is no way to extract the USERS array?
    – russ
    Commented Jun 7, 2013 at 21:51
  • Use process substitution. while read col1 col2 col3 _; do ...; done < <(w)
    – jordanm
    Commented Jun 7, 2013 at 21:53

4 Answers 4

5

Your main problem is that the last command in a pipeline runs in a subshell, like all other commands in the pipeline. This is the case in most shells. ATT ksh and zsh are exceptions: they run the last command of the pipeline in the parent shell.

Since bash 4.2, you can tell bash to behave like ksh and zsh by setting the lastpipe option.

#!/bin/bash
USERS=()
shopt -s lastpipe
w | awk '{if(NR > 2) print $1,$2,$3}' | while read line; do
  USERS+=("$line")
done
echo ${#USERS[@]}

Alternatively, you can use process substitution instead of a pipe, so that the read command runs in the main shell process.

#!/bin/bash
USERS=()
while read line; do
  USERS+=("$line")
done < <(w | awk '{if(NR > 2) print $1,$2,$3}')
echo ${#USERS[@]}

Alternatively, you can use the portable approach, which works in shells that don't have process susbtitution nor ksh/zsh behavior, such as Bourne, dash and pdksh. (You still need (pd)ksh, bash or zsh for arrays.) Run everything that requires the data from the pipeline inside the pipeline.

#!/bin/bash
USERS=()
shopt -s lastpipe
w | awk '{if(NR > 2) print $1,$2,$3}' | {
  while read line; do
    USERS+=("$line")
  done
  echo ${#USERS[@]}
}
4

With shopt -s lastpipe you can take the last command of a pipeline into the current shell environment. That solves your problem. I guess this feature has not always been in bash so avoid it if you want broadly compatible code.

The compatible alternative:

export_array="$(w | awk '{if(NR > 2) print $1,$2,$3}' | 
  { USERS=(); while read line; do
      USERS[]="$line"
    done
    declare -p USERS; } )"
eval "$export_array"
2
  • Did you mean USERS+=("$line") instead of USERS[]="$line"?
    – Runium
    Commented Jun 7, 2013 at 23:36
  • @Sukminder Of course not. I replaced that because AFAIR the += notation has been added in recent versions of bash. Both commands do the same. Commented Jun 7, 2013 at 23:40
4

Bash arrays are one dimensional. If you want to hold ordered separate values for each line one solution is to use associative arrays. A crude example:

Also be careful with those uppercase variable names as they can clash with environment variables.

#!/bin/bash

declare -i i=0 j=0
declare -A w

while read -r user tty from _;do
    ((++i > 2)) || continue
    w["$j.user"]="$user"
    w["$j.tty"]="$tty"
    w["$j.from"]="$from"
    ((++j))
done < <(w)

for ((i = 0; i < j; ++i)); do
    printf "entry %-2d {\n  %-5s: %s\n  %-5s: %s\n  %-5s: %s\n}\n" \
    "$i" \
    "user" "${w[$i.user]}" \
    "tty"  "${w[$i.tty]}" \
    "from" "${w[$i.from]}"
done
3

for storage in bash arrays, using a delimiter other than space is often simpler.

    readarray -s2 -t my_w_array < <(w | awk '{ print $1":"$2":"$3 }')

you can then split it when printing it, like:

    printf '%s\n' "${my_w_array[@]//:/ }"

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .