注:感谢 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