Nota: Obrigado a Jeff Schaller e steeldriver. Mas como nenhum dos dois postou como resposta, não sei como marcar como resolvido. Agora tenho uma melhor compreensão de pipes/subshells. Tenho quase certeza de que já sabia disso, mas já faz muito tempo que não tentei algo complexo no bash.
Ambos atribuindo o resultado filtrado do awk a uma variável esubstituição de processofuncionou para mim. Meu código final para ler linhas exclusivas não classificadas de stdin
:
while read -r FILE
do
...
done < <(awk '!x[$0]++')
Mais leituras emsubstituição de processopara quem encontra esta dúvida procurando solução para um problema semelhante.
PERGUNTA ORIGINAL:
Pesquisei no site, mas não consigo encontrar uma resposta para o meu problema.
Estou construindo um array de stdin e preciso filtrar linhas exclusivas. Para fazer isso, estou usando awk '!x[$0]++'
o que li é uma abreviação de:
awk 'BEGIN { while (getline s) { if (!seen[s]) print s; seen[s]=1 } }'
.
O filtro funciona conforme desejado, mas o problema é que a matriz resultante do while read
loop está vazia.
Por exemplo (usando $list
como substituto para 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
produz:
array length = 5
red apple
yellow banana
purple grape
orange orange
yellow banana
Mas filtrando $list
com 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
produz:
array length = 0
Mas a saída de awk '!x[$0]++' <<< "$list"
parece boa:
red apple
yellow banana
purple grape
orange orange
Tentei examinar cada linha do while read
loop:
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
e parece bem:
line[0] = red apple
line[1] = yellow banana
line[2] = purple grape
line[3] = orange orange
O que estou perdendo aqui?
Caso seja importante, estou usando o bash 3.2.57:
GNU bash, versão 3.2.57(1)-release (x86_64-apple-darwin15) Copyright (C) 2007 Free Software Foundation, Inc.
Responder1
awk '!x[$0]++' <<< "$lista" |enquanto lê a linha -r; fazer variedade[contagem++]=$linha feito
Oarray
(itálico) neste caso faz parte dosubshell
(audacioso).
O $line
e $array
tem um valorenquantoo subshell está vivo, por assim dizer.
Depois que o subshell termina, também conhecido como morre, o ambiente pai (gerador) é restaurado. Isso inclui a eliminação de quaisquer variáveis definidas no subshell.
Nesse caso:
removido,$array
removido.$line
Experimente isto:
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"
Rendimentos:
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] {
}
Ou conforme manual.
Podemos começar comGasodutos
[…] Cada comando em um pipeline é executado em seu própriosubnível(verAmbiente de execução de comando). […]
E aAmbiente de execução de comandoexpande a aventura da seguinte forma:
[…] Um comando invocado nesteambiente separado não podeafetar o ambiente de execução do shell.
Substituição de comandos, comandos agrupados entre parênteses e comandos assíncronos são invocados em um ambiente subshell que é uma duplicata do ambiente shell, exceto que as armadilhas capturadas pelo shell são redefinidas para os valores que o shell herdou de seu pai na invocação. Os comandos internos invocados como parte de um pipeline também são executados em um ambiente subshell.As alterações feitas no ambiente do subshell não podem afetar o ambiente de execução do shell.[…]
Não pode afetar: portanto, não pode ser definido.
No entanto, podemos redirecionar e fazer algo no sentido de:
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
Responder2
Algumas soluções para o seu problemasem o laço
# 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