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 sed
oraz BSD sed
. Nie zgadzają się (przynajmniej) GNU i Busybox sed
i 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 d
przejdzie 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 sed
dwa wiersze wprowadzania a
oraz b
. Program robi dwie rzeczy:
Usuwa pierwszą linię za pomocą
1d
.d
Komenda będzieUsuń przestrzeń wzorów i rozpocznij następny cykl. i
- 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,2
nigdy nie jest osiągany podczas linii 1 (ponieważ już d
przeskoczył do następnego cyklu / linii), a więc włączenie zakresu nigdy się nie rozpoczyna, a a
zostało usunięte. Zgodny Unix systemów sed
macOS i Solaris 10 generuje takie dane wyjściowe, podobnie jak non-POSIX sed
w Solarisie i BSD sed
w 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 d
i 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?
ed
, pomijającsed
całkowicie?Odpowiedzi:
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:
A oto odpowiednia część reszty wiadomości (przeze mnie), którą zacytował Geoff:
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'
zseq 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ą
sed
implementację 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:
wiersze 1, 2, 4 i 5 powinny zostać wyprowadzone, ponieważ tym razem jest to adres końcowy, którego
1,3p
komenda 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
sed
zwraca 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:
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 zbusybox sed
: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!
źródło
seq 10 | sed -n '1d;1,2p'
zseq 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.d
to 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!1d;1,2p
skrypcie1,2p
polecenie 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'
1
i/1/
są to oba adresy,1
jest to adres, gdy numer wiersza wynosi 1,/1/
jest to adres, gdy przestrzeń wzorcowa zawiera1
, 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.