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 的执行环境。[…]

它不会影响:因此它无法设置。

然而,我们可以重定向并朝着以下方向做一些事情:

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

相关内容