BASH:使用 awk 過濾唯一行會導致 0 長度數組

BASH:使用 awk 過濾唯一行會導致 0 長度數組

附註:感謝 Jeff Schaller 和 Steeldriver。但由於兩者都沒有作為答案發布,我不確定如何標記為已解決。我現在對管道/子殼有了更好的理解。我很確定我曾經知道這一點,但是我已經很久沒有在 bash 中嘗試過任何複雜的東西了。

兩者都將 awk 的過濾結果分配給變數並流程替代為我工作。我從以下位置讀取未排序的唯一行的最終程式碼stdin

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

更多閱讀流程替代對於那些發現這個問題正在尋找類似問題解決方案的人。

原問題:

我搜尋了該網站,但找不到我的問題的答案。

我正在從標準輸入建立一個數組,需要過濾唯一的行。為此,我使用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

$list用 awk 過濾:

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)-release (x86_64-apple-darwin15) 版權所有 (C) 2007 Free Software Foundation, Inc.

答案1

awk '!x[$0]++' <<< "$list" |同時讀取 -r 行;做
    大批[計數++]=$行
完畢

array斜體)在這種情況下是一部分subshell大膽的)。

$line$array一個值同時可以這麼說,子殼是活的。

一旦子 shell 完成(即死亡),父級(生成器)環境就會恢復。這包括刪除子 shell 中設定的任何變數。

在這種情況下:

  • $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] {

}

或按照手冊。

我們可以從管道

[...]管道中的每個命令都在其自己的中執行子外殼(看命令執行環境)。 […]

還有命令執行環境冒險擴展如下:

[...] 在此呼叫的命令獨立的環境 不能影響shell的執行環境。

命令替換、用括號分組的命令和非同步命令在子 shell 環境中調用,該子 shell 環境是 shell 環境的副本,只不過 shell 捕獲的陷阱會重置為 shell 在調用時從其父 shell 繼承的值。作為管道一部分呼叫的內建指令也在子 shell 環境中執行。對子 shell 環境所做的變更不會影響 shell 的執行環境。[…]

它不會影響:因此它無法設定。

然而,我們可以重定向並朝著以下方向做些事情:

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

相關內容