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 read
Schleife leer ist.
Zum Beispiel ( $list
als 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 $list
mit 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 read
Schleife 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 $line
und $array
hat 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:
ENTFERNT,$array
ENTFERNT.$line
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