Czego wymaga POSIX sed dla `1d; 1,2d`, gdzie zakres adresów zaczyna się od już usuniętej linii?

11

W komentarzach do tego pytania pojawił się przypadek, w którym różne implementacje sed nie zgadzały się z dość prostym programem, a my (lub przynajmniej ja) nie byliśmy w stanie ustalić, czego właściwie wymaga specyfikacja.

Problemem jest zachowanie zakresu rozpoczynającego się od usuniętej linii:

1d;1,2d

Czy wiersz 2 powinien zostać usunięty, mimo że początek zakresu został usunięty przed osiągnięciem tego polecenia? Moje początkowe oczekiwania były „nie” zgodne z BSD sed, podczas gdy GNU sed mówi „tak”, a sprawdzenie tekstu specyfikacji nie rozwiązuje całkowicie tej kwestii.

Spełniają moje oczekiwania (przynajmniej) macOS i Solaris sedoraz BSD sed. Nie zgadzają się (przynajmniej) GNU i Busybox sedi wiele osób tutaj. Pierwsze dwa są certyfikowane przez SUS, podczas gdy pozostałe są prawdopodobnie bardziej rozpowszechnione. Które zachowanie jest prawidłowe?


Tekst specyfikacji dla dwóch zakresów adresów mówi:

Narzędzie sed zastosuje następnie kolejno wszystkie polecenia, których adresy wybierają tę przestrzeń wzorca, aż polecenie rozpocznie następny cykl lub zakończy działanie.

i

Polecenie edycyjne z dwoma adresami wybiera zakres obejmujący od pierwszej przestrzeni wzorów pasującej do pierwszego adresu do następnej przestrzeni wzorów pasującej do drugiego. [...] Począwszy od pierwszego wiersza następującego po wybranym zakresie, sed ponownie szuka pierwszego adresu. Następnie proces należy powtórzyć.

Prawdopodobnie linia 2 znajduje się w „obejmującym zakresie od pierwszej przestrzeni wzorów, która pasuje do pierwszego adresu, do następnej przestrzeni wzorów, która pasuje do drugiej”, niezależnie od tego, czy punkt początkowy został usunięty. Z drugiej strony spodziewałem się, że pierwszy dprzejdzie do następnego cyklu i nie dam szansy na rozpoczęcie zakresu. Implementacje z certyfikatem UNIX ™ działają zgodnie z oczekiwaniami, ale potencjalnie nie spełniają wymagań specyfikacji.

Poniżej przedstawiono kilka przykładowych eksperymentów, ale kluczowe pytanie brzmi: co należy sed zrobić, gdy zakres zaczyna się od usuniętej linii?


Eksperymenty i przykłady

Uproszczoną demonstracją problemu jest to, że drukuje dodatkowe kopie wierszy zamiast ich usuwania:

printf 'a\nb\n' | sed -e '1d;1,2p'

Zapewnia to seddwa wiersze wprowadzania aoraz b. Program robi dwie rzeczy:

  1. Usuwa pierwszą linię za pomocą 1d. dKomenda będzie

    Usuń przestrzeń wzorów i rozpocznij następny cykl. i

  2. Wybierz zakres linii od 1 do 2 i jawnie je wydrukuje, oprócz automatycznego drukowania, który otrzymuje każda linia. Linia zawarta w zakresie powinna zatem pojawić się dwukrotnie.

Oczekiwałem, że to się wydrukuje

b

tylko, gdy zakres nie ma zastosowania, ponieważ 1,2nigdy nie jest osiągany podczas linii 1 (ponieważ już dprzeskoczył do następnego cyklu / linii), a więc włączenie zakresu nigdy się nie rozpoczyna, a azostało usunięte. Zgodny Unix systemów sedmacOS i Solaris 10 generuje takie dane wyjściowe, podobnie jak non-POSIX sedw Solarisie i BSD sedw ogóle.

GNU sed natomiast drukuje

b
b

wskazując, że ma interpretować zakres. Dzieje się tak zarówno w trybie POSIX, jak i nie. Sed Busybox ma takie samo zachowanie (ale nie zawsze identyczne zachowanie, więc nie wydaje się, aby było to wynikiem wspólnego kodu).

Dalsze eksperymenty z

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'

stwierdza, że ​​traktuje zakres zaczynający się od usuniętej linii, tak jakby zaczynał się od następnego wiersza. Jest to widoczne, ponieważ /c/nie pasuje do końca zakresu. Użycie /b/do uruchomienia zakresu nie zachowuje się tak samo jak 2.


Pierwszym działającym przykładem, którego użyłem, był

printf '%s\n' a b c d e | sed -e '1{/a/d;};1,//d'

jako sposób na usunięcie wszystkich linii aż do pierwszego /a/dopasowania, nawet jeśli jest to w pierwszym wierszu (do czego używałby GNU sed 0,/a/d- była to próba wykonania tego zgodna z POSIX).

Zasugerowano, że zamiast tego powinno się usunąć do drugiego dopasowania, /a/jeśli pierwsza linia pasuje (lub cały plik, jeśli nie ma drugiego dopasowania), co wydaje się prawdopodobne - ale znowu, robi to tylko GNU sed. Zarówno sed MacOS, jak i Solaris produkują sed

b
c
d
e

w tym celu, jak się spodziewałem (GNU sed tworzy puste wyjście po usunięciu nieskończonego zakresu; Busybox sed drukuje tylko di e, co jest oczywiście błędne bez względu na wszystko). Zasadniczo zakładam, że ich pozytywny wynik testów zgodności z certyfikatem oznacza, że ​​ich zachowanie jest prawidłowe, ale wystarczająca liczba osób zasugerowała, że ​​nie jestem pewien, tekst specyfikacji nie jest całkowicie przekonujący, a zestaw testów nie może być doskonale kompleksowy.

Oczywiście, pisanie tego kodu dzisiaj nie jest praktycznie przenośne, biorąc pod uwagę niespójność, ale teoretycznie powinien być wszędzie równoważny z jednym lub drugim znaczeniem. Myślę, że to błąd, ale nie wiem, przeciwko którym implementacjom należy to zgłosić. Obecnie uważam, że zachowanie GNU i Busybox sed jest niezgodne ze specyfikacją, ale mogę się mylić.

Czego wymaga tutaj POSIX?

Michael Homer
źródło
Aby tymczasowo obejść ten problem, napisz do pliku tymczasowego i przetworz go za pomocą POSIX ed, pomijając sedcałkowicie?
D. Ben Knoble,

Odpowiedzi:

9

Zostało to poruszone na liście mailingowej grupy Austin w marcu 2012 r. Oto ostatnia wiadomość na ten temat (autorstwa Geoffa Clare'a z Austin Group (organu, który utrzymuje POSIX), który jest także tym, który podniósł tę kwestię w pierwszej kolejności). Tutaj skopiowane z interfejsu gmane NNTP:

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

A oto odpowiednia część reszty wiadomości (przeze mnie), którą zacytował Geoff:

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.

Tak więc (według Geoffa) POSIX jest jasne, że zachowanie GNU jest niezgodne.

I to prawda, że ​​jest mniej spójny (porównaj seq 10 | sed -n '1d;1,2p'z seq 10 | sed -n '1d;/^1$/,2p'), nawet jeśli potencjalnie mniej zaskakuje ludzi, którzy nie zdają sobie sprawy z tego, jak przetwarzane są zakresy (nawet Geoff początkowo uznał zachowanie za „dziwne” ).

Nikt nie zawracał sobie głowy zgłaszaniem tego jako błędu do GNU. Nie jestem pewien, czy zakwalifikowałbym to jako błąd. Prawdopodobnie najlepszą opcją byłoby zaktualizowanie specyfikacji POSIX, aby umożliwić obu zachowaniom wyjaśnienie, że nie można polegać na żadnym z nich.

Edit . Spojrzałem teraz na oryginalną sedimplementację Unixa V7 z późnych lat 70. i wygląda na to, że takie zachowanie adresów adresowych nie było zamierzone lub przynajmniej nie zostało całkowicie przemyślane.

Z odczytaniem przez Geoffa specyfikacji (i moją oryginalną interpretacją tego, dlaczego tak się dzieje), odwrotnie, w:

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

wiersze 1, 2, 4 i 5 powinny zostać wyprowadzone, ponieważ tym razem jest to adres końcowy, którego 1,3pkomenda dystansowa nigdy nie napotyka , jak wseq 5 | sed -n '3d;/1/,/3/p'

Nie dzieje się tak jednak w oryginalnej implementacji, ani w żadnej innej implementacji, którą próbowałem (busybox sedzwraca linie 1, 2 i 4, które wyglądają bardziej jak błąd).

Jeśli spojrzysz na kod UNIX v7 , sprawdza on przypadek, w którym bieżący numer linii jest większy niż (numeryczny) adres końcowy, i wtedy wychodzi poza zakres. Fakt, że nie robi tego dla adresu początkowego, bardziej przypomina przeoczenie niż celowy projekt.

Oznacza to, że obecnie nie ma żadnej implementacji, która byłaby zgodna z tą interpretacją specyfikacji POSIX.

Innym mylącym zachowaniem związanym z implementacją GNU jest:

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

Ponieważ wiersz 2 został pominięty, wpisuje się go w 2,/3/wierszu 3 (pierwszy wiersz, którego liczba to> = 2). Ale ponieważ jest to linia, która skłoniła nas do wprowadzenia zakresu, nie jest sprawdzany pod kątem adresu końcowego . Gorzej z busybox sed:

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

Ponieważ linie 2 do 7, zostały usunięte, wiersz 8 jest pierwszą, która wynosi> = 2, więc zakres 2,3 jest wprowadzony po czym!

Stéphane Chazelas
źródło
1
Wygląda więc na to, że problem jest nadal nierozwiązany - zgadzam się z twoim uzasadnieniem, dlaczego tak się dzieje, ale także, że nie jest jasne, czy to było to, czego chciał - choć brzmi to również tak, jakby Geoff był przekonany cytowanym tekstem, że implementacje UNIX ™ były poprawne. Czy to także twoje czytanie?
Michael Homer,
1
@MichaelHomer, chodzi o to, że (według Geoffa) POSIX jest jasne, że zachowanie GNU jest niezgodne. I to prawda, że ​​jest mniej spójny (porównaj seq 10 | sed -n '1d;1,2p'z seq 10 | sed -n '1d;/^1$/,2p'), nawet jeśli potencjalnie mniej zaskakujący dla ludzi nie zda sobie sprawy z tego, jak przetwarzane są zakresy. Nikt nie zawracał sobie głowy zgłaszaniem tego jako błędu do GNU. Nie jestem pewien, czy zakwalifikowałbym go jako błąd, prawdopodobnie najlepszą opcją byłaby aktualizacja specyfikacji POSIX, aby umożliwić obu zachowaniom wyjaśnienie, że nie można polegać na żadnym z nich.
Stéphane Chazelas,
2
W rzeczywistości, ponieważ definicja POSIX nie zawiera stwierdzenia, że ​​adresy muszą być „widziane”, aby rozpocząć lub zakończyć zakres adresów, IMO implementacja GNU podąża bardziej ściśle za sformułowaniami POSIX (zaskakujące dla GNU!). Jest to również pożądane zachowanie w większości znanych mi przypadków rzeczywistych. Ale, jak zauważyłeś, musiałoby być spójne. A sprawdzanie każdej linii pod kątem wzorców zasięgu nawet po dto nie tylko problem z wydajnością, prowadzi to do dalszych problemów z implementacją, ponieważ „niewidoczne” wzorce potrzebne do zasięgów nie mogą mieć wpływu na kolejne puste wzorce… bałagan!
Philippos,
@Filippos, w tym 1d;1,2pskrypcie 1,2ppolecenie nie jest uruchamiane w pierwszym wierszu, więc do pierwszego adresu nie pasuje żadna przestrzeń wzorca , co jest jednym ze sposobów interpretacji tego tekstu. W każdym razie powinno być oczywiste, że ocena adresów powinna być dokonana w momencie uruchomienia polecenia. Jak wsed 's/./x/g; /xxx/,/xxx/d'
Stéphane Chazelas,
2
@Isaac, to sedno problemu. W języku POSIX 1i /1/są to oba adresy, 1jest to adres, gdy numer wiersza wynosi 1, /1/jest to adres, gdy przestrzeń wzorcowa zawiera 1, pytanie brzmi, czy oba typy adresów powinny być traktowane tak samo, czy też zakresy numerów wierszy powinny uwzględniać „ bezwzględnie ”niezależnie od tego, czy pasowały. Zobacz także moją ostatnią edycję, aby uzyskać więcej kontekstu historycznego.
Stéphane Chazelas,