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?