Was benötigt POSIX sed für "1d; 1,2d", wenn ein Adressbereich von einer bereits gelöschten Zeile ausgeht?

11

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 sedsowie BSD sed. Nicht einverstanden sind (zumindest) GNU und Busybox sedsowie 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 dzum 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 sedzwei Eingabezeilen aund b. Das Programm macht zwei Dinge:

  1. Löscht die erste Zeile mit 1d. Der dBefehl wird

    Löschen Sie den Musterraum und starten Sie den nächsten Zyklus. und

  2. 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,2in Zeile 1 nie erreicht wird (weil der bereits zum nächsten Zyklus / zur nächsten Zeile gesprungen ist) und daher die Bereichseinbeziehung nie beginnt, solange aer gelöscht wurde. Die konformen Unixe sedvon macOS und Solaris 10 erzeugen diese Ausgabe, ebenso wie das Nicht-POSIX sedin Solaris und BSD sedim 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 dund 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?

Michael Homer
quelle
Schreiben Sie als vorübergehende Problemumgehung in eine temporäre Datei und verarbeiten Sie sie unter edUmgehung von POSIX sed?
D. Ben Knoble

Antworten:

9

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:

Date: Fri, 16 Mar 2012 17:09:42 +0000
From: Geoff Clare <gwc-7882/[email protected]>
To: austin-group-l-7882/[email protected]
Newsgroups: gmane.comp.standards.posix.austin.general
Subject: Re: Strange addressing issue in sed

Stephane Chazelas <[email protected]> wrote, on 16 Mar 2012:
>
> 2012-03-16 15:44:35 +0000, Geoff Clare:
> > I've been alerted to an odd behaviour of sed on certified UNIX
> > systems that doesn't seem to match the requirements of the
> > standard.  It concerns an interaction between the 'n' command
> > and address matching.
> > 
> > According to the standard, this command:
> > 
> > printf 'A\nB\nC\nD\n' | sed '1,3s/A/B/;1,3n;1,3s/B/C/'
> > 
> > should produce the output:
> > 
> > B
> > C
> > C
> > D
> > 
> > GNU sed does produce this, but certified UNIX systems produce this:
> > 
> > B
> > B
> > C
> > D
> > 
> > However, if I change the 1,3s/B/C/ to 2,3s/B/C/ then they produce
> > the expected output (tested on Solaris and HP-UX).
> > 
> > Is this just an obscure bug from common ancestor code, or is there
> > some legitimate reason why this address change alters the behaviour?
> [...]
> 
> I suppose the idea is that for the second 1,3cmd, line "1" has
> not been seen, so the 1,3 range is not entered.

Ah yes, now it makes sense, and it looks like the standard does
require this slightly strange behaviour, given how the processing
of the "two addresses" case is specified:

    An editing command with two addresses shall select the inclusive
    range from the first pattern space that matches the first address
    through the next pattern space that matches the second.  (If the
    second address is a number less than or equal to the line number
    first selected, only one line shall be selected.) Starting at the
    first line following the selected range, sed shall look again for
    the first address. Thereafter, the process shall be repeated.

It's specified this way because the addresses can be BREs, but if
the same matching process is applied to the line numbers (even though
they can only match at most once), then the 1,3 range on that last
command is never entered.

-- 
Geoff Clare <g.clare-7882/[email protected]>
The Open Group, Apex Plaza, Forbury Road, Reading, RG1 1AX, England

Und hier ist der relevante Teil der restlichen Nachricht (von mir), die Geoff zitiert hat:

I suppose the idea is that for the second 1,3cmd, line "1" has
not been seen, so the 1,3 range is not entered.

Same idea as in

printf '%s\n' A B C | sed -n '1d;1,2p'

whose behavior differ in traditional (heirloom toolchest at
least) and GNU.

It's unclear to me whether POSIX wants one behavior or the
other.

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'mit seq 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 sedImplementierung 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:

seq 5 | sed -n '3d;1,3p'

Die Zeilen 1, 2, 4 und 5 sollten ausgegeben werden, da diesmal die Endadresse vom 1,3pFernkampfbefehl 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 sedgibt 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:

$ seq 5 | sed -n '2d;2,/3/p'
3
4
5

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 mit busybox sedin:

$ seq 10 | busybox sed -n '2,7d; 2,3p'
8

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!

Stéphane Chazelas
quelle
1
Es hört sich also so an, als ob das Problem immer noch ungelöst ist - ich stimme Ihrer Argumentation zu, warum es passiert, aber auch, dass unklar ist, ob dies gewünscht wurde - obwohl es auch so klingt, als ob Geoff von dem zitierten Text überzeugt war, dass die UNIX ™ -Implementierungen waren richtig. Lesen Sie das auch?
Michael Homer
1
@MichaelHomer, die Idee ist, dass (laut Geoff) POSIX klar ist , dass das GNU-Verhalten nicht konform ist. Und es ist wahr, dass es weniger konsistent ist (vergleiche seq 10 | sed -n '1d;1,2p'mit seq 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.
Stéphane Chazelas
2
Da die POSIX-Definition keine Aussage darüber macht, dass Adressen "gesehen" werden müssen, um einen Adressbereich zu starten oder zu beenden, folgt IMO die GNU-Implementierung strenger dem POSIX-Wortlaut (überraschend für GNU!). Dies ist auch für die meisten mir bekannten Fälle in der Praxis erwünscht. Aber wie Sie betonen, müsste es konsistent sein. Und das Überprüfen jeder Zeile auf Bereichsmuster, auch nachdem dies dnicht 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!
Philippos
@Philippos, in diesem 1d;1,2pSkript wird der 1,2pBefehl 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'
Stéphane Chazelas
2
@Isaac, das ist der Kern des Problems. In der POSIX-Sprache 1und /1/sind beide Adressen, 1ist die Adresse, wenn die Zeilennummer 1 ist, /1/ist die Adresse, wenn der Musterraum enthält 1, 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.
Stéphane Chazelas