sed: wczytuje cały plik do obszaru wzorców bez niepowodzenia w przypadku wprowadzania jednowierszowego

9

Czytanie całego pliku w przestrzeni wzorów jest przydatne do zastępowania nowych linii & c. i istnieje wiele przypadków zalecających następujące działania:

sed ':a;N;$!ba; [commands...]'

Jednak nie powiedzie się, jeśli dane wejściowe zawierają tylko jedną linię.

Na przykład, przy wprowadzaniu dwóch linii, każda linia podlega komendzie podstawienia:

$ echo $'abc\ncat' | sed ':a;N;$!ba; s/a/xxx/g'
xxxbc
cxxxt

Ale przy wprowadzaniu pojedynczego wiersza nie jest wykonywane podstawienie:

$ echo 'abc' | sed ':a;N;$!ba; s/a/xxx/g'
abc

Jak napisać sedpolecenie, aby odczytać wszystkie dane wejściowe jednocześnie i nie mieć tego problemu?

dicktyr
źródło
Zredagowałem twoje pytanie, aby zawierało rzeczywiste pytanie. Jeśli chcesz, możesz poczekać na inne odpowiedzi, ale ostatecznie oznaczyć najlepszą odpowiedź jako zaakceptowaną (patrz przycisk potoku po lewej stronie odpowiedzi, tuż poniżej przycisków strzałek w górę w dół).
John1024
@ John1024 Dzięki, dobrze mieć przykład. Znalezienie tego rodzaju rzeczy przypomina mi, że „wszystko jest źle”, ale cieszę się, że niektórzy z nas się nie poddają. :}
dicktyr
2
Jest trzecia opcja! Użyj sed -zopcji GNU . Jeśli twój plik nie ma wartości null, będzie czytał do końca pliku! Znalezione z tego: stackoverflow.com/a/30049447/582917
CMCDragonkai

Odpowiedzi:

13

Istnieje wiele powodów, dla których wczytywanie całego pliku do obszaru wzorców może się nie powieść. Problem logiczny w pytaniu dotyczącym ostatniego wiersza jest powszechny. Jest to związane z sedcyklem linii - kiedy nie ma już linii i sednapotyka EOF, to przez nie przechodzi - przerywa przetwarzanie. I tak, jeśli jesteś na ostatniej linii i instruujesz, sedaby zdobyć kolejną, to zatrzyma się na tym miejscu i nie będzie więcej robić.

To powiedziawszy, jeśli naprawdę musisz wczytać cały plik do przestrzeni wzorców, prawdopodobnie i tak warto rozważyć inne narzędzie. Faktem jest, że sedjest to tytułowy edytor strumieniowy - jest przeznaczony do pracy z linią - lub logicznym blokiem danych - na raz.

Istnieje wiele podobnych narzędzi, które są lepiej wyposażone do obsługi pełnych bloków plików. edi exna przykład mogą zrobić wiele z tego, co sedmożna zrobić i przy podobnej składni - i wiele więcej poza tym - ale zamiast działać tylko na strumieniu wejściowym podczas przekształcania go na dane wyjściowe sed, zachowują również tymczasowe pliki kopii zapasowych w systemie plików . W razie potrzeby ich praca jest buforowana na dysk i nie poddają się gwałtownie na końcu pliku (i zwykle rzadziej implodują pod obciążeniem bufora) . Ponadto oferują wiele przydatnych funkcji, których sednie ma - w rodzaju, które po prostu nie mają sensu w kontekście strumienia - takich jak znaczniki linii, cofanie, nazwane bufory, łączenie i inne.

sedpodstawową siłą jest zdolność do przetwarzania danych natychmiast po ich odczytaniu - szybko, wydajnie i strumieniowo. Kiedy wycierasz plik, wyrzucasz go i masz tendencję do napotkania trudności z marginesami, takich jak problem z ostatnią linią, o którym wspomniałeś, przepełnienia bufora i beznadziejna wydajność - ponieważ analizowane dane wydłużają czas przetwarzania wyrażeń regularnych podczas wyliczania dopasowań rośnie wykładniczo .

Nawiasem mówiąc, jeśli chodzi o ten ostatni punkt: chociaż rozumiem, że przykładowy s/a/A/gprzypadek jest prawdopodobnie naiwnym przykładem i prawdopodobnie nie jest rzeczywistym skryptem, który chcesz zebrać w danych wejściowych, być może warto poświęcić chwilę na zapoznanie się z y///. Jeśli często globalnie zastępujesz jedną postać inną, ymoże to być bardzo przydatne. Jest to transformacja w przeciwieństwie do podstawienia i jest znacznie szybsza, ponieważ nie oznacza wyrażenia regularnego. Ten ostatni punkt może również być przydatny przy próbie zachowania i powtórzenia pustych //adresów, ponieważ nie wpływa na nie, ale może być przez nie zmieniony. W każdym razie y/a/A/jest to prostszy sposób na osiągnięcie tego samego - i możliwe są także wymiany:y/aA/Aa/ które zamieniają wszystkie wielkie / małe litery jak na linii dla siebie.

Należy również pamiętać, że opisywane zachowanie tak naprawdę nie jest tym, co powinno się wydarzyć.

Z GNU info sedw sekcji WSPÓLNIE ZGŁASZANE BŁĘDY :

  • N polecenie w ostatnim wierszu

    • Większość wersji sedkończy pracę bez drukowania niczego, gdy Npolecenie jest wydawane w ostatnim wierszu pliku. GNU seddrukuje przestrzeń wzorca przed wyjściem, chyba że oczywiście określono -nprzełącznik poleceń. Ten wybór jest zgodny z projektem.

    • Na przykład zachowanie sed N foo barzależy od tego, czy foo ma parzystą czy nieparzystą liczbę linii. Lub, podczas pisania skryptu do odczytu kilku kolejnych wierszy po dopasowaniu wzorca, tradycyjne implementacje sedzmusiłyby cię do napisania czegoś podobnego /foo/{ $!N; $!N; $!N; $!N; $!N; $!N; $!N; $!N; $!N }zamiast po prostu /foo/{ N;N;N;N;N;N;N;N;N; }.

    • W każdym razie najprostszym obejściem jest użycie $d;Nw skryptach opartych na tradycyjnym zachowaniu lub ustawienie POSIXLY_CORRECTzmiennej na niepustą wartość.

POSIXLY_CORRECTZmienna jest mowa bo POSIX określa, że jeśli sednapotka EOF podczas próby Npowinno wyjść bez wyjścia, ale w wersji GNU świadomie zrywa z normą w tym przypadku. Zauważ też, że nawet jeśli zachowanie jest uzasadnione powyżej założenia, że ​​przypadek błędu dotyczy edycji strumieniowej - a nie umieszczania całego pliku w pamięci.

W standardowych definiuje N„S zachowanie sposób:

  • N

    • Dołącz następny wiersz danych wejściowych, pomniejszając \nkońcową ewlinię, do przestrzeni wzoru, używając osadzonej \newline, aby oddzielić dołączony materiał od materiału oryginalnego. Zauważ, że bieżący numer linii zmienia się.

    • Jeśli nie jest dostępny następny wiersz danych wejściowych, Nczasownik polecenia rozgałęzia się do końca skryptu i kończy pracę bez rozpoczynania nowego cyklu lub kopiowania przestrzeni wzorców na standardowe wyjście.

W tej notatce pokazano kilka innych GNU-izmów - w szczególności użycie :etykiety, branch i {nawiasów kontekstowych funkcji }. Zasadniczo każde sedpolecenie, które akceptuje dowolny parametr, rozumiane jest jako ograniczenie w \newline w skrypcie. Więc polecenia ...

:arbitrary_label_name; ...
b to_arbitrary_label_name; ...
//{ do arbitrary list of commands } ...

... bardzo prawdopodobne jest nieprawidłowe działanie w zależności od sedimplementacji, która je czyta. Przenośne powinny być napisane:

...;:arbitrary_label_name
...;b to_arbitrary_label_name
//{ do arbitrary list of commands
}

To samo odnosi się do r, w, t, a, i, i c (i ewentualnie kilka bardziej, że jestem zapominając w tej chwili) . W prawie każdym przypadku można je również napisać:

sed -e :arbitrary_label_name -e b\ to_arbitary_label_name -e \
    "//{ do arbitrary list of commands" -e \}

... gdzie nowa -einstrukcja \nxecution oznacza separator ewline. Więc tam, gdzie infotekst GNU sugeruje, że tradycyjne sedwdrożenie zmusiłoby cię do zrobienia :

/foo/{ $!N; $!N; $!N; $!N; $!N; $!N; $!N; $!N; $!N }

... raczej powinno być ...

/foo/{ $!N; $!N; $!N; $!N; $!N; $!N; $!N; $!N; $!N
}

... oczywiście to też nie jest prawda. Pisanie w ten sposób scenariusza jest trochę głupie. Istnieją znacznie prostsze sposoby robienia tego samego, na przykład:

printf %s\\n foo . . . . . . |
sed -ne 'H;/foo/h;x;//s/\n/&/3p;tnd
         //!g;x;$!d;:nd' -e 'l;$a\' \
     -e 'this is the last line' 

... który drukuje:

foo
.
.
.
foo\n.\n.\n.$
.$
this is the last line

... ponieważ tpolecenie est - podobnie jak większość sedpoleceń - zależy od cyklu linii w celu odświeżenia rejestru zwrotnego i tutaj cykl linii może wykonać większość pracy. Jest to kolejny kompromis, którego dokonujesz, gdy kopiujesz plik - cykl linii nigdy się nie odświeża i tak wiele testów zachowuje się nienormalnie.

Powyższe polecenie nie ryzykuje przekroczenia zakresu danych wejściowych, ponieważ wykonuje tylko kilka prostych testów, aby zweryfikować, co czyta podczas czytania. W przypadku Hstarego wszystkie wiersze są dodawane do miejsca wstrzymania, ale jeśli linia jest zgodna /foo/, zastępuje hstare miejsce. Bufory są następnie xzmieniane, a s///próba uwarunkowania warunkowego jest podejmowana, jeśli zawartość bufora jest zgodna z //ostatnim adresowanym wzorcem. Innymi słowy, //s/\n/&/3ppróbuje zastąpić trzeci znak nowej linii w przestrzeni wstrzymania i wydrukować wyniki, jeśli przestrzeń wstrzymania jest obecnie zgodna /foo/. Jeśli to się tpowiedzie, skrypt rozgałęzia się na etykietę not delete - co powoduje, lże skrypt kończy pracę.

W przypadku, gdy oba /foo/i trzeci nowej linii, nie mogą być dopasowane razem w przestrzeni utrzymywania jednak następnie //!gzastąpi bufor Jeżeli /foo/nie jest dopasowany, lub, jeśli jest dopasowany, to zastąpić buforem jeśli \newline nie jest dopasowany (w miejsce /foo/z sama) . Ten mały, subtelny test zapobiega niepotrzebnemu zapełnianiu się bufora przez długie odcinki „nie” /foo/i zapewnia, że ​​proces pozostanie bezproblemowy, ponieważ dane wejściowe się nie nakładają. W przypadku braku /foo/lub //s/\n/&/3pawarii bufory są ponownie zamieniane, a każda linia oprócz ostatniej jest tam usuwana.

Ta ostatnia - ostatnia linia $!d- jest prostym pokazem, w jaki sposób sedmożna wykonać skrypt odgórny, aby łatwo obsługiwać wiele spraw. Kiedy twoją ogólną metodą jest wycinanie niechcianych przypadków, zaczynając od najbardziej ogólnych i pracując w kierunku najbardziej specyficznych, wówczas przypadki brzegowe można łatwiej obsłużyć, ponieważ mogą one po prostu spaść do końca skryptu z innymi poszukiwanymi danymi i kiedy to wszystko otula cię tylko danymi, których potrzebujesz. Konieczność pobrania takich przypadków brzegowych z zamkniętej pętli może być jednak znacznie trudniejsza.

I oto ostatnia rzecz, którą muszę powiedzieć: jeśli naprawdę musisz pobrać cały plik, możesz znieść nieco mniej pracy, polegając na cyklu linii, aby to zrobić za Ciebie. Zazwyczaj należy użyć Next i next dla uprzedzona - ponieważ postęp naprzód cyklu linii. Zamiast redundantnie implementować zamkniętą pętlę w pętli - ponieważ i tak sedcykl linii jest po prostu zwykłą pętlą odczytu - jeśli Twoim celem jest tylko gromadzenie danych wejściowych bez rozróżnienia, prawdopodobnie łatwiej jest zrobić:

sed 'H;1h;$!d;x;...'

... który zbierze cały plik lub spróbuje.


uwaga dodatkowa Ni zachowanie w ostatniej linii ...

chociaż nie mam dostępnych narzędzi do przetestowania, weź pod uwagę, że Npodczas czytania i edycji w miejscu zachowuje się inaczej, jeśli edytowany plik jest plikiem skryptu do następnego odczytu.

mikeserv
źródło
1
Stawianie bezwarunkowego na Hpierwszym miejscu jest piękne.
2015 r. O
@mikeserv Dziękujemy za Twój wkład. Widzę potencjalną korzyść z utrzymania cyklu linii, ale jak to działa mniej?
dicktyr
@ dicktyr dobrze, składnia wymaga pewnych skrótów, :a;$!{N;ba}jak wspomniałem powyżej - łatwiej jest używać standardowej formy na dłuższą metę, gdy próbujesz uruchomić wyrażenia regularne na nieznanych systemach. Ale tak naprawdę nie to miałem na myśli: wdrażasz zamkniętą pętlę - nie możesz tak łatwo dostać się do środka, kiedy chcesz, zamiast tego, rozgałęziając - przycinając niechciane dane - i pozwalając na cykl. To jak odgórna rzecz - wszystko, co sedrobi, jest bezpośrednim wynikiem tego, co właśnie zrobiono. Może widzisz to inaczej - ale jeśli spróbujesz, skrypt może okazać się łatwiejszy.
mikeserv
11

Nie udaje się, ponieważ Npolecenie pojawia się przed dopasowaniem wzorca $!(nie ostatni wiersz) i sed kończy pracę przed wykonaniem jakiejkolwiek pracy:

N.

Dodaj nowy wiersz do obszaru wzorów, a następnie dołącz następny wiersz danych wejściowych do obszaru wzorów. Jeśli nie ma więcej danych wejściowych, sed kończy pracę bez przetwarzania żadnych poleceń .

Można to łatwo naprawić, aby działało również z wprowadzaniem jednowierszowym (a nawet by było bardziej jasne w każdym przypadku), po prostu grupując polecenia Ni bpo wzorcu:

sed ':a;$!{N;ba}; [commands...]'

Działa w następujący sposób:

  1. :a utwórz etykietę o nazwie „a”
  2. $! jeśli nie ostatnia linia, to
  3. Ndołącz następny wiersz do obszaru wzorów (lub wyjdź, jeśli nie ma następnego wiersza) i barozgałęź (przejdź do) etykietę „a”

Niestety nie jest przenośny (ponieważ opiera się na rozszerzeniach GNU), ale następująca alternatywa (sugerowana przez @mikeserv) jest przenośna:

sed 'H;1h;$!d;x; [commands...]'
dicktyr
źródło
Opublikowałem to tutaj, ponieważ nie znalazłem informacji gdzie indziej i chciałem je udostępnić, aby inni mogli uniknąć problemów z rozpowszechnieniem :a;N;$!ba;.
dicktyr
Dzięki za opublikowanie! Pamiętaj, że zaakceptowanie własnej odpowiedzi jest również w porządku. Musisz tylko chwilę poczekać, aż system pozwoli ci to zrobić.
terdon