BASH: awk を使用して一意の行をフィルタリングすると、長さ 0 の配列が生成される

BASH: awk を使用して一意の行をフィルタリングすると、長さ 0 の配列が生成される

注: Jeff Schaller 氏と steeldriver 氏に感謝します。しかし、どちらも回答として投稿していないため、解決済みとしてマークする方法がわかりません。パイプ/サブシェルについて理解が深まりました。以前は知っていたはずですが、bash で複雑なことを試してから長い時間が経ちました。

awkからのフィルタリング結果を変数に代入し、プロセス置換私の場合はうまくいきました。ソートされていない一意の行を読み取るための最終的なコードは次のとおりですstdin

while read -r FILE
do
    ...
done < <(awk '!x[$0]++')

詳しくはこちらプロセス置換同様の問題の解決策を探してこの質問を見つけた人向け。

元の質問:

サイトを検索しましたが、問題に対する答えが見つかりません。

stdin から配列を構築していて、一意の行をフィルタリングする必要があります。これを行うには、awk '!x[$0]++'次の省略形を使用します。

awk 'BEGIN { while (getline s) { if (!seen[s]) print s; seen[s]=1 } }'

フィルターは期待どおりに動作しますが、ループからの結果の配列が空になるという問題がありますwhile read

たとえば($listのサロゲートとして使用stdin):

list=$'red apple\nyellow banana\npurple grape\norange orange\nyellow banana'
while read -r line; do
    array[count++]=$line
done <<< "$list"
echo "array length = ${#array[@]}"
counter=0
while [  $counter -lt ${#array[@]} ]; do
    echo ${array[counter++]}
done

生成:

array length = 5
red apple
yellow banana
purple grape
orange orange
yellow banana

しかし、$listawk でフィルタリングすると:

list=$'red apple\nyellow banana\npurple grape\norange orange\nyellow banana'
awk '!x[$0]++' <<< "$list" | while read -r line; do
    array[count++]=$line
done
echo "array length = ${#array[@]}"
counter=0
while [  $counter -lt ${#array[@]} ]; do
     echo ${array[counter++]}
done

生成:

array length = 0

しかし、出力はawk '!x[$0]++' <<< "$list"問題ないようです:

red apple
yellow banana
purple grape
orange orange

ループ内の各行を調べてみましたwhile read:

list=$'red apple\nyellow banana\npurple grape\norange orange\nyellow banana'
i=0
awk '!x[$0]++' <<< "$list" | while read -r line; do
    echo "line[$i] = $line"
    let i=i+1
done

そしてそれは問題ないように見えます:

line[0] = red apple
line[1] = yellow banana
line[2] = purple grape
line[3] = orange orange

ここで何が欠けているのでしょうか?

重要なこととして、私は bash 3.2.57 を使用しています。

GNU bash、バージョン 3.2.57(1) リリース (x86_64-apple-darwin15) Copyright (C) 2007 Free Software Foundation, Inc.

答え1

awk '!x[$0]++' <<< "$list" |-r行を読み取りながら、
    配列[count++]=$行
終わり

arrayイタリック)は、この場合、subshell大胆な)。

$lineには$array値があります一方いわば、サブシェルは生きているのです。

サブシェルが終了、つまり停止すると、親 (スポーナー) 環境が復元されます。これには、サブシェルで設定されたすべての変数の消去が含まれます。

この場合:

  • $array削除、
  • $line削除されました。

これを試して:

list=$'red apple\nyellow banana\npurple grape\norange orange\nyellow banana'
awk '!x[$0]++' <<< "$list" | while read -r line; do
    array[count++]=$line
    printf "array[%d] { %s\n" ${#array[@]} # array[num_of_elements] {
    printf "       %s\n" "${array[@]}"     # elements
    printf "}\n"                           # } end of array

done

printf "\n[ %s ]\n\n" "END OF SUBSHELL (PIPE)"

printf "array[%d] {\n" ${#array[@]}
printf "       %s\n" "${array[@]}"
printf "}\n"

収量:

array[1] {
       red apple
}
array[2] {
       red apple
       yellow banana
}
array[3] {
       red apple
       yellow banana
       purple grape
}
array[4] {
       red apple
       yellow banana
       purple grape
       orange orange
}

[ END OF SUBSHELL (PIPE) ]

array[0] {

}

またはマニュアルどおりに。

まずはパイプライン

[…]パイプライン内の各コマンドは、それぞれ独自の方法で実行されます。サブシェル(見るコマンド実行環境)。 […]

そしてそのコマンド実行環境冒険は次のように展開されます。

[…]この中で呼び出されるコマンド別の環境 できないシェルの実行環境に影響します。

コマンド置換、括弧でグループ化されたコマンド、および非同期コマンドは、シェル環境の複製であるサブシェル環境で呼び出されます。ただし、シェルによってキャッチされたトラップは、呼び出し時にシェルが親から継承した値にリセットされます。パイプラインの一部として呼び出される組み込みコマンドも、サブシェル環境で実行されます。サブシェル環境に加えられた変更は、シェルの実行環境に影響を与えることはできません。[…]

影響を与えることはできません。したがって設定できません。

ただし、次の方向に方向転換して何かを行うことができます。

list=$'red apple\nyellow banana\npurple grape\norange orange\nyellow banana'

while read -r line; do
    arr[count++]=$line
done <<<"$(awk '!x[$0]++' <<< "$list")"

echo "arr length = ${#arr[@]}"
count=0
while [[  $count -lt ${#arr[@]} ]]; do
    echo ${arr[count++]}
done

答え2

あなたの問題に対する解決策ループなし

# use bash's mapfile with process substitution 
mapfile -t arr < <( awk '!x[$0]++' <<<"$list" )

# use array assignment syntax (at least bash, ksh, zsh) 
# of a command-substituted value split at newline only
# and (if the data can contain globs) globbing disabled
set -f; IFS='\n' arr=( $( awk '!x[$0]++' <<<"$list" ) ); set +f

関連情報