
Ich möchte die Anzahl der Zeilen in einer Pipe zählen und die Pipe dann je nach Ergebnis fortsetzen.
Ich habe es versucht
x=$(printf 'faa\nbor\nbaz\n' \
| tee /dev/stderr | wc -l) 2>&1 \
| if [[ $x -ge 2 ]]; then
grep a
else
grep b
fi
Aber es filtert überhaupt nicht (weder nach "a" noch nach "b"). Das war ziemlich unerwartet, da zumindest diese wie erwartet funktionieren:
printf 'faa\nbor\nbaz\n' | if true; then grep a; else grep b; fi
printf 'faa\nbor\nbaz\n' | if false; then grep a; else grep b; fi
Es scheint, dass ich den stderr nicht innerhalb der Befehlssubstitution umleiten kann, da dies auch nicht funktioniert (in Bash). Es werden alle drei Zeilen gedruckt:
x=$(printf 'faa\nbor\nbaz\n' | tee /dev/stderr | wc -l) 2>&1 | grep a
In zsh werden nur zwei Zeilen gedruckt.
Aber in beiden Shells wird die Variable x nicht nach der Pipeline gesetzt und nicht einmal während der zweiten Hälfte der Pipeline.
Was kann ich tun, um die Zeilen in einer Pipeline zu zählen und dann abhängig von dieser Zahl zu handeln? Ich möchte temporäre Dateien vermeiden.
Antwort1
Dieser Kommentarist wahr:
Jeder Teil einer Pipeline wird unabhängig von den anderen Teilen derselben Pipeline gestartet. Dies bedeutet, dass es
$x
nicht in der Mitte der Pipeline verfügbar sein kann, wenn es in einer der anderen Phasen festgelegt ist.
Das heißt nicht, dass Sie nichts tun können. Eine Pipeline kann als Hauptdatenkanal betrachtet werden, Prozesse können jedoch auch über Nebenkanäle kommunizieren: Dateien, benannte FIFOs oder was auch immer (obwohl Sie manchmal besonders vorsichtig sein müssen und sie nicht blockieren lassen dürfen).
Sie möchten die Anzahl der Zeilen zählen und den gesamten Datenstrom später bedingt verarbeiten. Das bedeutet, dass Sie bis zum Ende des Datenstroms gelangen und erst dann den gesamten Datenstrom weitergeben müssen. Sie müssen also den gesamten Datenstrom irgendwie speichern. Eine temporäre Datei scheint ein vernünftiger Ansatz zu sein. Sie sollten Ihre Pipe in mindestens zwei Teile aufteilen. Der erste Teil sollte die Daten in einer Datei speichern; dann sollten die Zeilen gezählt werden (diese Aufgabe gehört meiner Meinung nach möglicherweise zum ersten Teil); dann sollte der letzte Teil die Nummer abrufen, die Datei lesen, um die Daten von Anfang an zu erhalten, und entsprechend handeln.
Wenn Sie temporäre Dateien wirklich vermeiden möchten, sollte ein Teil Ihrer Pipeline in etwa so funktionieren sponge
. Um Nebenkanäle zu vermeiden, sollte die Anzahl der Zeilen als allererste Zeile der Ausgabe übergeben werden und der verbleibende Teil der Pipeline sollte dieses Protokoll verstehen.
Betrachten Sie diesen Befehl:
sed '$ {=; H; g; p;}; H; d'
Es sammelt Zeilen in einem Haltebereich. Wenn mindestens eine Zeile vorhanden ist, wird nach dem Empfang der letzten Zeile sed
die Anzahl der Zeilen gedruckt, gefolgt von einer leeren Zeile und der eigentlichen Eingabe.
Die leere Zeile ist unnötig, ergibt sich aber „natürlich“ aus diesem einfachen Code. Anstatt zu versuchen, sie in zu vermeiden sed
, würde ich sie einfach später in der Pipe behandeln (z. B. mit sed '2 d'
).
Anwendungsbeispiel:
#!/bin/sh
sed '$ {=; H; g; p;}; H; d' | sed '2 d' | {
if ! IFS= read -r nlines; then
echo "0 lines. Nothing to do." >&2
else
echo "$nlines lines. Processing accordingly." >&2
if [ "$nlines" -ge 2 ]; then
grep a
else
grep b
fi
fi
}
Anmerkungen:
IFS= read -r
ist übertrieben, da die erste Zeile gut definiert ist und nur eine Zahl enthält (oder nicht existiert).- Ich habe verwendet
/bin/sh
. Der Code läuft auch in Bash. Sie können nicht davon ausgehen,
sed
dass es in der Lage ist, eine beliebige Datenmenge zu speichern.POSIX-Spezifikationsagt:Die Muster- und Haltebereiche müssen jeweils mindestens 8192 Bytes aufnehmen können.
Es kann also sein, dass die Grenze nur 8192 Byte beträgt. Andererseits kann ich mir vorstellen, dass eine temporäre Datei problemlos 1 TB Daten fasst. Vermeiden Sie temporäre Dateien vielleicht nicht um jeden Preis.
Der Titel lautet „Zählen Sie die Anzahl der Zeilen“, aber Ihr Beispiel versucht zu entscheiden, ob die Zahl 2 oder mehr ist (im Allgemeinen N oder mehr). Diese Probleme sind nicht gleichwertig. Nach der 2. (N-ten) Eingabezeile kennen Sie die Antwort auf das letztere Problem, gleichmäßige Zeilen werden unendlich erscheinen. Der obige Code kann keine unendliche Eingabe verarbeiten. Lassen Sie uns das bis zu einem gewissen Grad beheben.
sed '
7~1 {p; d}
6 {H; g; i \
6+
p; d}
$ {=; H; g; p}
6! {H; d}
'
Dieser Befehl verhält sich wie die vorherige Lösung, außer dass er bei der 6. Zeile annimmt (druckt), dass die Anzahl der Zeilen ist 6+
. Dann werden die bereits angezeigten Zeilen gedruckt und die folgenden Zeilen (falls vorhanden) werden gedruckt, sobald sie erscheinen ( cat
-ähnliches Verhalten).
Anwendungsbeispiel:
#!/bin/sh
threshold=6
sed "
$((threshold+1))~1 {p; d}
$threshold {H; g; i \
$threshold+
p; d}
$ {=; H; g; p}
${threshold}! {H; d}
" | sed '2 d' | {
if ! IFS= read -r nlines; then
echo "0 lines. Nothing to do." >&2
else
echo "$nlines lines. Processing accordingly." >&2
if [ "$nlines" = "$threshold+" ]; then
grep a
else
grep b
fi
fi
}
Anmerkungen:
- „Bis zu einem gewissen Grad“ behoben, da die Einschränkung
sed
(wie auch immer die Einschränkung in Ihrem Fall lautet) weiterhin gilt. Jetzt werden jedochsed
maximal$threshold
Zeilen verarbeitet; wenn die$threshold
Anzahl niedrig genug ist, sollte es in Ordnung sein. - Der Beispielcode führt nur Tests dagegen durch,
$threshold+
aber das Protokoll ermöglicht Ihnen, zwischen den Zeilen 0, 1, 2, …, Schwellenwert minus eins und Schwellenwert oder mehr zu unterscheiden.
Ich bin nicht sehr bewandert darin sed
. Wenn mein sed
Code vereinfacht werden kann, hinterlassen Sie mir bitte einen Hinweis in einem Kommentar.
Antwort2
Basierend auf der Diskussion und dem Sed-Code von Kamil habe ich diese Awk-Lösung gefunden:
awk -v th="$threshold" '
function print_lines() { for (i in lines) print lines[i] }
NR < th { lines[NR] = $0 }
NR > th { print }
NR == th { print th; print_lines(); print }
END { if (NR < th) { print NR; print_lines(); } }' \
| if read nlines; then
if [ "$nlines" -eq "$threshold" ]; then
grep a
else
grep b
fi
fi