Bash - 多维数组和从输出中提取变量

Bash - 多维数组和从输出中提取变量

我想做一些简单的事情,但我不确定如何在这里实现我的目标。

w我正在尝试提取控制台上的命令给出的 USER、TTY 和 FROM 值。在 bash 中,我尝试获取此输出并将这些值放入多维数组(或只是带有空格分隔符的数组)中。

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

我已经找到了在单个数组中逐行读取值的方法,但是我似乎无法将 USERS 数组值移出 while 循环的范围。它打印值 1,2,3,4,然后在循环后打印 0。我读到的每个例子都很好地使用了范围之外的变量,但我似乎不能。

答案1

您的主要问题是管道中的最后一个命令在子 shell 中运行,就像管道中的所有其他命令一样。大多数 shell 都是这种情况。 ATT ksh 和 zsh 是例外:它们在父 shell 中运行管道的最后一个命令。

从 bash 4.2 开始,您可以通过设置来告诉 bash 表现得像 ksh 和 zshlastpipe 选项

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

或者,您可以使用流程替代而不是管道,以便read命令在主 shell 进程中运行。

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

或者,您可以使用可移植的方法,该方法适用于没有进程替换或 ksh/zsh 行为的 shell,例如 Bourne、dash 和 pdksh。 (对于数组,您仍然需要 (pd)ksh、bash 或 zsh。)在管道内运行需要来自管道的数据的所有内容。

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

答案2

shopt -s lastpipe可以使用管道的最后一个命令进入当前的 shell 环境。这解决了你的问题。我想这个功能并不总是在 bash 中存在,所以如果你想要广泛兼容的代码,请避免使用它。

兼容的替代方案:

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"

答案3

Bash 数组是一维的。如果您想为每一行保存有序的单独值,一种解决方案是使用关联数组。一个粗略的例子:

还要小心那些大写变量名称,因为它们可能与环境变量冲突。

#!/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

答案4

对于 bash 数组中的存储,使用空格以外的分隔符通常更简单。

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

然后您可以在打印时将其拆分,例如:

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

相关内容