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
?Antworten:
Dies wurde im März 2012 auf der Mailingliste der Austin-Gruppe erwähnt. Hier ist die letzte Nachricht dazu (von Geoff Clare von der Austin Group (der Stelle, die POSIX unterhält), die auch das Thema überhaupt angesprochen hat). Hier von der gmane NNTP-Schnittstelle kopiert:
Und hier ist der relevante Teil der restlichen Nachricht (von mir), die Geoff zitiert hat:
Daher ist (laut Geoff) POSIX klar, dass das GNU-Verhalten nicht konform ist.
Und es ist wahr, dass es weniger konsistent ist (vergleiche
seq 10 | sed -n '1d;1,2p'
mitseq 10 | sed -n '1d;/^1$/,2p'
), auch wenn es für Leute, die nicht wissen, wie Bereiche verarbeitet werden, möglicherweise weniger überraschend ist (selbst Geoff fand das konforme Verhalten anfangs "seltsam" ).Niemand machte sich die Mühe, es den GNU-Leuten als Fehler zu melden. Ich bin mir nicht sicher, ob ich es als Fehler qualifizieren würde. Die wahrscheinlich beste Option wäre, die POSIX-Spezifikation zu aktualisieren, damit beide Verhaltensweisen deutlich machen, dass man sich auch nicht darauf verlassen kann.
Bearbeiten . Ich habe mir jetzt die ursprüngliche
sed
Implementierung in Unix V7 aus den späten 70ern angesehen, und es sieht so aus, als ob das Verhalten für numerische Adressen dort nicht beabsichtigt oder zumindest nicht vollständig durchdacht war.Mit Geoffs Lesung der Spezifikation (und meiner ursprünglichen Interpretation, warum es passiert), umgekehrt in:
Die Zeilen 1, 2, 4 und 5 sollten ausgegeben werden, da diesmal die Endadresse vom
1,3p
Fernkampfbefehl wie in nie angetroffen wirdseq 5 | sed -n '3d;/1/,/3/p'
Dies ist jedoch weder in der ursprünglichen Implementierung noch in einer anderen von mir versuchten Implementierung der Fall (Busybox
sed
gibt die Zeilen 1, 2 und 4 zurück, die eher wie ein Fehler aussehen).Wenn Sie sich den UNIX v7-Code ansehen, wird geprüft , ob die aktuelle Zeilennummer größer als die (numerische) Endadresse ist, und dann außerhalb des Bereichs. Die Tatsache, dass dies für die Startadresse nicht der Fall ist, sieht eher nach einem Versehen als nach einem absichtlichen Entwurf aus.
Dies bedeutet, dass es derzeit keine Implementierung gibt, die dieser Interpretation der POSIX-Spezifikation in dieser Hinsicht tatsächlich entspricht.
Ein weiteres verwirrendes Verhalten bei der GNU-Implementierung ist:
Da Zeile 2 übersprungen wurde, wird das
2,/3/
in Zeile 3 eingegeben (die erste Zeile mit der Nummer> = 2). Aber wie es die Linie ist , die aus uns geben Sie den Bereich, ist es nicht für die geprüfte Ende Adresse. Es wird schlimmer mitbusybox sed
in:Da die Zeilen 2 bis 7 gelöscht wurden, ist Zeile 8 die erste, die> = 2 ist, sodass dann der Bereich 2,3 eingegeben wird!
quelle
seq 10 | sed -n '1d;1,2p'
mitseq 10 | sed -n '1d;/^1$/,2p'
), selbst wenn potenziell weniger überraschende Menschen nicht erkennen würden, wie Bereiche verarbeitet werden. Niemand machte sich die Mühe, es den GNU-Leuten als Fehler zu melden. Ich bin mir nicht sicher, ob ich es als Fehler qualifizieren würde. Die wahrscheinlich beste Option wäre, die POSIX-Spezifikation zu aktualisieren, damit beide Verhaltensweisen klar machen, dass man sich auch nicht darauf verlassen kann.d
nicht nur ein Leistungsproblem ist, führt zu weiteren Implementierungsproblemen, da "unsichtbare" Muster, die für Bereiche benötigt werden, keine Auswirkungen auf weitere leere Muster haben dürfen ... ein Durcheinander!1d;1,2p
Skript wird der1,2p
Befehl nicht in der ersten Zeile ausgeführt, sodass die erste Adresse nicht mit einem Musterbereich übereinstimmt. Dies ist eine Möglichkeit, diesen Text zu interpretieren. In jedem Fall sollte es offensichtlich sein, dass die Auswertung der Adressen zum Zeitpunkt der Ausführung des Befehls erfolgen sollte. Wie insed 's/./x/g; /xxx/,/xxx/d'
1
und/1/
sind beide Adressen,1
ist die Adresse, wenn die Zeilennummer 1 ist,/1/
ist die Adresse, wenn der Musterraum enthält1
, ist die Frage, ob beide Adresstypen gleich behandelt werden sollten oder ob Zeilennummernbereiche berücksichtigt werden sollten " im absoluten "unabhängig davon, ob sie übereinstimmten. Siehe auch meine letzte Bearbeitung für mehr historischen Kontext.