BASH: Usar o awk para filtrar linhas exclusivas resulta em uma matriz de comprimento 0

BASH: Usar o awk para filtrar linhas exclusivas resulta em uma matriz de comprimento 0

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 readloop está vazia.

Por exemplo (usando $listcomo 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 $listcom 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 readloop:

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 $linee $arraytem 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:

  • $arrayremovido,
  • $lineremovido.

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

informação relacionada