In den Kommentaren zu dieser Frage tauchte ein Fall auf, in dem verschiedene sed-Implementierungen in einem ziemlich einfachen Programm nicht übereinstimmten und wir (oder zumindest ich) nicht feststellen konnten, was die Spezifikation tatsächlich dafür erfordert.
Das Problem ist das Verhalten eines Bereichs, der an einer gelöschten Zeile beginnt:
1d;1,2d
Sollte Zeile 2 gelöscht werden , obwohl der Anfang des Bereichs entfernt wurde, bevor dieser Befehl erreicht wurde? Meine anfängliche Erwartung war "Nein" im Einklang mit BSD sed, während GNU sed "Ja" sagt und die Überprüfung des Spezifikationstextes die Angelegenheit nicht vollständig löst.
Entsprechend meiner Erwartung sind (zumindest) macOS und Solaris sed
sowie BSD sed
. Nicht einverstanden sind (zumindest) GNU und Busybox sed
sowie zahlreiche Personen hier. Die ersten beiden sind SUS-zertifiziert, während die anderen wahrscheinlich weiter verbreitet sind. Welches Verhalten ist richtig?
Der Spezifikationstext für Bereiche mit zwei Adressen lautet:
Das Dienstprogramm sed wendet dann nacheinander alle Befehle an, deren Adressen diesen Musterraum auswählen, bis ein Befehl den nächsten Zyklus startet oder beendet.
und
Ein Bearbeitungsbefehl mit zwei Adressen muss den Inklusivbereich vom ersten Musterraum, der mit der ersten Adresse übereinstimmt, bis zum nächsten Musterraum, der mit dem zweiten übereinstimmt, auswählen. [...] Ab der ersten Zeile nach dem ausgewählten Bereich sucht sed erneut nach der ersten Adresse. Danach ist der Vorgang zu wiederholen.
Argumentieren, Zeile 2 ist innerhalb „der einschließlichen Bereichs von dem ersten Musterraum, der die erste Adresse durch den nächsten Musterraum übereinstimmt, der die zweite übereinstimmt“, und zwar unabhängig davon , ob der Startpunkt gelöscht wurde. Andererseits erwartete ich, dass der erste d
zum nächsten Zyklus übergeht und dem Bereich keine Chance gibt, zu starten. Die UNIX ™ -zertifizierten Implementierungen erfüllen die Erwartungen, aber möglicherweise nicht die Anforderungen der Spezifikation.
Es folgen einige veranschaulichende Experimente, aber die Schlüsselfrage lautet: Was ist zu sed
tun, wenn ein Bereich in einer gelöschten Zeile beginnt?
Experimente und Beispiele
Eine vereinfachte Demonstration des Problems ist die folgende, bei der zusätzliche Kopien von Zeilen gedruckt werden, anstatt sie zu löschen:
printf 'a\nb\n' | sed -e '1d;1,2p'
Dies bietet sed
zwei Eingabezeilen a
und b
. Das Programm macht zwei Dinge:
Löscht die erste Zeile mit
1d
. Derd
Befehl wirdLöschen Sie den Musterraum und starten Sie den nächsten Zyklus. und
- Wählen Sie den Zeilenbereich von 1 bis 2 aus und drucken Sie diese zusätzlich zum automatischen Drucken jeder Zeile explizit aus. Eine im Bereich enthaltene Linie sollte daher zweimal erscheinen.
Meine Erwartung war, dass dies gedruckt werden sollte
b
Nur, wenn der Bereich nicht gilt, weil er 1,2
in Zeile 1 nie erreicht wird (weil d
er bereits zum nächsten Zyklus / zur nächsten Zeile gesprungen ist) und daher die Bereichseinbeziehung nie beginnt, solange a
er gelöscht wurde. Die konformen Unixe sed
von macOS und Solaris 10 erzeugen diese Ausgabe, ebenso wie das Nicht-POSIX sed
in Solaris und BSD sed
im Allgemeinen.
GNU sed hingegen druckt
b
b
darauf hinweist , dass es hat den Bereich interpretiert. Dies geschieht sowohl im POSIX-Modus als auch nicht. Busybox's sed hat das gleiche Verhalten (aber nicht immer das gleiche Verhalten, so dass es nicht das Ergebnis von gemeinsam genutztem Code zu sein scheint).
Weiteres Experimentieren mit
printf 'a\nb\nc\nd\ne\n' | sed -e '2d;2,/c/p'
printf 'a\nb\nc\nd\ne\n' | sed -e '2d;2,/d/p'
stellt fest, dass ein Bereich, der an einer gelöschten Zeile beginnt, so behandelt wird, als würde er an der folgenden Zeile beginnen. Dies ist sichtbar, da /c/
es nicht zum Beenden des Bereichs passt. Die Verwendung /b/
zum Starten des Bereichs verhält sich nicht wie 2
.
Das erste Arbeitsbeispiel, das ich verwendete, war
printf '%s\n' a b c d e | sed -e '1{/a/d;};1,//d'
um alle Zeilen bis zur ersten /a/
Übereinstimmung zu löschen , auch wenn sich diese in der ersten Zeile befindet (wofür GNU sed verwenden würde 0,/a/d
- dies war eine versuchte POSIX-kompatible Wiedergabe davon).
Es wurde vorgeschlagen, dies stattdessen bis zur zweiten Übereinstimmung zu löschen, /a/
wenn die erste Zeile übereinstimmt (oder die gesamte Datei, wenn keine zweite Übereinstimmung vorliegt), was plausibel erscheint - aber auch dies tut nur GNU sed. Sowohl macOS sed als auch Solaris sed produzieren
b
c
d
e
dafür, wie ich erwartet hatte (GNU sed erzeugt die leere Ausgabe beim Entfernen des nicht abgeschlossenen Bereichs; Busybox sed druckt nur d
und e
, was eindeutig falsch ist, egal was passiert ). Im Allgemeinen würde ich davon ausgehen, dass das Bestehen der Zertifizierungskonformitätstests bedeutet, dass ihr Verhalten korrekt ist, aber genug Leute haben etwas anderes vorgeschlagen, dass ich nicht sicher bin, der Spezifikationstext nicht vollständig überzeugend ist und die Testsuite nicht sein kann perfekt umfassend.
Natürlich ist es angesichts der Inkonsistenz praktisch nicht portabel, diesen Code heute zu schreiben, aber theoretisch sollte er überall mit der einen oder anderen Bedeutung gleichwertig sein. Ich denke, dies ist ein Fehler, aber ich weiß nicht, gegen welche Implementierung (en) ich ihn melden soll. Ich bin derzeit der Ansicht, dass das Verhalten von GNU und Busybox sed nicht mit der Spezifikation übereinstimmt, aber ich könnte mich darin irren.
Was benötigt POSIX hier?
ed
Umgehung von POSIXsed
?