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

主な問題は、パイプラインの最後のコマンドがパイプラインの他のすべてのコマンドと同様にサブシェルで実行されることです。これはほとんどの殻の場合です。 ATT ksh と zsh は例外です。それらは親シェルでパイプラインの最後のコマンドを実行します。

Bash 4.2以降の設定では、bashにkshやzshのように動作するように指示できます。lastpipe オプション

#!/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コマンドがデフォルトのシェルプロセスで実行されるようにします。

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

あるいは、Bourne、dash、pdkshなど、プロセスの置き換えやksh / zshの動作なしでシェルで動作する移植可能なアプローチを使用できます。 (配列の場合はまだ(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パイプラインの最後のコマンドを使用して、現在のシェル環境に入ることができます。これで問題が解決します。私の考えでは、この機能は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配列は1次元配列です。各行に対して順序付けられた別々の値を格納するには、連想配列を使用することが1つの解決策です。おおよその例:

また、大文字の変数名は環境変数と競合する可能性がありますので注意してください。

#!/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[@]//:/ }"

関連情報