BASH: el uso de awk para filtrar líneas únicas da como resultado una matriz de 0 longitud

BASH: el uso de awk para filtrar líneas únicas da como resultado una matriz de 0 longitud

Nota: Gracias a Jeff Schaller y Steeldriver. Pero como ninguno de los dos publicó como respuesta, no estoy seguro de cómo marcarlo como resuelto. Ahora entiendo mejor las tuberías/subcapas. Estoy bastante seguro de que alguna vez supe esto, pero ha pasado mucho tiempo desde que probé algo complejo en bash.

Tanto asignando el resultado filtrado de awk a una variable comosustitución de procesostrabajó para mi. Mi código final para leer líneas únicas sin clasificar de stdin:

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

Más lecturas ensustitución de procesospara aquellos que encuentran esta pregunta y buscan una solución a un problema similar.

PREGUNTA ORIGINAL:

Busqué en el sitio, pero no encuentro una respuesta a mi problema.

Estoy creando una matriz a partir de la entrada estándar y necesito filtrar líneas únicas. Para hacer esto, estoy usando awk '!x[$0]++'lo que he leído es una abreviatura de:

awk 'BEGIN { while (getline s) { if (!seen[s]) print s; seen[s]=1 } }'.

El filtro funciona como se desea, pero el problema es que la matriz resultante del while readbucle está vacía.

Por ejemplo (usando $listcomo sustituto de 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

produce:

array length = 5
red apple
yellow banana
purple grape
orange orange
yellow banana

Pero filtrando $listcon 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

produce:

array length = 0

Pero el resultado de awk '!x[$0]++' <<< "$list"parece estar bien:

red apple
yellow banana
purple grape
orange orange

Intenté examinar cada línea del while readbucle:

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

y parece bien:

line[0] = red apple
line[1] = yellow banana
line[2] = purple grape
line[3] = orange orange

¿Que me estoy perdiendo aqui?

Por si es importante, estoy usando bash 3.2.57:

GNU bash, versión 3.2.57(1) (x86_64-apple-darwin15) Copyright (C) 2007 Free Software Foundation, Inc.

Respuesta1

awk '!x[$0]++' <<< "$lista" |mientras lee -r línea; hacer
    formación[cuenta++]=$línea
hecho

Elarray(itálico) en este caso es parte delsubshell(atrevido).

El $liney $arraytiene un valor.mientras quela subcapa está viva, por así decirlo.

Una vez que finaliza la subcapa, también conocida como muere, se restaura el entorno principal (generador). Esto incluye la eliminación de cualquier variable establecida en el subnivel.

En este caso:

  • $arrayremoto,
  • $lineremoto.

Prueba esto:

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"

Rendimientos:

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

}

O según el manual.

Podemos empezar conTuberías

[…] Cada comando en una canalización se ejecuta por su cuentasubcapa(verEntorno de ejecución de comandos). […]

Y elEntorno de ejecución de comandosamplía la aventura de la siguiente manera:

[…] Un comando invocado en esteambiente separado no puedoafectar el entorno de ejecución del shell.

La sustitución de comandos, los comandos agrupados con paréntesis y los comandos asincrónicos se invocan en un entorno de subshell que es un duplicado del entorno de shell, excepto que las trampas detectadas por el shell se restablecen a los valores que el shell heredó de su padre en el momento de la invocación. Los comandos integrados que se invocan como parte de una canalización también se ejecutan en un entorno de subshell.Los cambios realizados en el entorno del subshell no pueden afectar el entorno de ejecución del shell.[…]

No puede afectar: ​​por lo tanto no puede establecer.

Sin embargo, podemos redirigir y hacer algo en la dirección 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

Respuesta2

Algunas soluciones a tu problemasin el bucle

# 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

información relacionada