BASH: Die Verwendung von awk zum Filtern eindeutiger Zeilen führt zu einem Array mit der Länge 0

BASH: Die Verwendung von awk zum Filtern eindeutiger Zeilen führt zu einem Array mit der Länge 0

Hinweis: Vielen Dank an Jeff Schaller und Steeldriver. Aber da keiner von beiden eine Antwort gepostet hat, bin ich mir nicht sicher, wie ich es als gelöst markieren soll. Ich habe jetzt ein besseres Verständnis von Pipes/Subshells. Ich bin ziemlich sicher, dass ich das einmal wusste, aber es ist lange her, seit ich etwas Komplexes in Bash ausprobiert habe.

Sowohl das Zuweisen des gefilterten Ergebnisses von awk zu einer Variablen als auchProzesssubstitutionhat bei mir funktioniert. Mein endgültiger Code zum Lesen unsortierter eindeutiger Zeilen aus stdin:

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

Mehr lesen aufProzesssubstitutionfür diejenigen, die diese Frage finden und nach einer Lösung für ein ähnliches Problem suchen.

URSPRÜNGLICHE FRAGE:

Ich habe die Site durchsucht, kann aber keine Antwort auf mein Problem finden.

Ich erstelle ein Array aus stdin und muss nach eindeutigen Zeilen filtern. Dazu verwende ich, awk '!x[$0]++'was meines Wissens eine Abkürzung für ist:

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

Der Filter funktioniert wie gewünscht, das Problem besteht jedoch darin, dass das resultierende Array aus der while readSchleife leer ist.

Zum Beispiel ( $listals Ersatz für 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

erzeugt:

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

Aber Filtern $listmit 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

erzeugt:

array length = 0

Aber die Ausgabe awk '!x[$0]++' <<< "$list"scheint in Ordnung zu sein:

red apple
yellow banana
purple grape
orange orange

Ich habe versucht, jede Zeile in der while readSchleife zu untersuchen:

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

und es scheint in Ordnung zu sein:

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

Was übersehe ich hier?

Falls es wichtig ist, ich verwende Bash 3.2.57:

GNU Bash, Version 3.2.57(1)-Release (x86_64-apple-darwin15) Copyright (C) 2007 Free Software Foundation, Inc.

Antwort1

awk '!x[$0]++' <<< "$Liste" |während lesen -r Zeile; machen
    Anordnung[Anzahl++]=$Zeile
Erledigt

Derarray(kursiv) ist in diesem Fall ein Teil dersubshell(deutlich).

Das $lineund $arrayhat einen Wertwährenddie Unterschale ist sozusagen lebendig.

Sobald die Subshell beendet ist, d. h. stirbt, wird die übergeordnete (Spawner-)Umgebung wiederhergestellt. Dies beinhaltet die Löschung aller in der Subshell festgelegten Variablen.

In diesem Fall:

  • $arrayENTFERNT,
  • $lineENTFERNT.

Versuche dies:

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"

Erträge:

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

}

Oder gemäß Handbuch.

Wir können beginnen mitRohrleitungen

[…] Jeder Befehl in einer Pipeline wird einzeln ausgeführt.Unterschale(sehenBefehlsausführungsumgebung). […]

Und dasBefehlsausführungsumgebungerweitert das Abenteuer wie folgt:

[…] Ein in diesemseparate Umgebung kann nichtdie Ausführungsumgebung der Shell beeinflussen.

Befehlsersetzung, mit Klammern gruppierte Befehle und asynchrone Befehle werden in einer Subshell-Umgebung aufgerufen, die ein Duplikat der Shell-Umgebung ist, mit der Ausnahme, dass von der Shell abgefangene Traps auf die Werte zurückgesetzt werden, die die Shell beim Aufruf von ihrem übergeordneten Element geerbt hat. Integrierte Befehle, die als Teil einer Pipeline aufgerufen werden, werden ebenfalls in einer Subshell-Umgebung ausgeführt.An der Subshell-Umgebung vorgenommene Änderungen können sich nicht auf die Ausführungsumgebung der Shell auswirken.[…]

Es kann nicht beeinflussen: daher kann es nicht gesetzt werden.

Wir können jedoch umlenken und etwas in die folgende Richtung tun:

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

Antwort2

Einige Lösungen für Ihr Problemohne die Schleife

# 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

verwandte Informationen