Zamień ciąg zawierający znaki nowego wiersza

10

Z bashpowłoką w pliku z wierszami podobnymi do poniższych

first "line"
<second>line and so on

Chciałbym, aby zastąpić jeden lub więcej wystąpień "line"\n<second>z other charactersi uzyskanie za każdym razem:

first other characters line and so on

Więc muszę zastąpić ciąg zarówno znakami specjalnymi, jak "i <znakiem nowej linii.

Po przeszukaniu innych odpowiedzi stwierdziłem, że sedmożna zaakceptować znaki nowej linii po prawej stronie polecenia (czyli other charactersciąg znaków), ale nie po lewej.

Czy istnieje sposób (prostszy niż ten ), aby uzyskać ten wynik za pomocą sedlub grep?

BowPark
źródło
pracujesz z komputerem Mac? \noświadczenie ewline zrobić dlatego pytam. ludzie rzadko pytają, czy mogą zrobić s//\n/tak, jak ty z GNU sed, chociaż większość innych sedodrzuca tę ucieczkę po prawej stronie. mimo to funkcja \nucieczki będzie działać po lewej stronie w dowolnym POSIX sed- ie i można je przenośnie tłumaczyć tak, y/c/\n/jakby miało to ten sam efekt, co s/c/\n/gnie zawsze jest tak przydatne.
mikeserv

Odpowiedzi:

3

Trzy różne sedpolecenia:

sed '$!N;s/"[^"]*"\n<[^>]*>/other characters /;P;D'

sed -e :n -e '$!N;s/"[^"]*"\n<[^>]*>/other characters /;tn'

sed -e :n -e '$!N;/"$/{$!bn' -e '};s/"[^"]*"\n<[^>]*>/other characters /g'

Wszystkie trzy opierają się na podstawowym s///poleceniu dotyczącym upodstytucji:

s/"[^"]*"\n<[^>]*>/other characters /

Wszyscy oni również starają się zachować ostrożność w obsłudze ostatniego wiersza, ponieważ sedzwykle różnią się wydajnością w przypadku krawędzi. To jest znaczenie $!adresu odpowiadającego każdej linii, która !nie jest $ostatnia.

Wszyscy oni również używają Npolecenia ext, aby dołączyć następny wiersz wejściowy do przestrzeni wzorów po \nznaku ewline. Każdy, kto sedprzez jakiś czas \ngrał, nauczy się polegać na postaci ewline - ponieważ jedynym sposobem na zdobycie takiego jest wyraźne umieszczenie go tam.

Wszystkie trzy podejmują próbę odczytania jak najmniejszej ilości danych wejściowych przed podjęciem działania - seddziałają tak szybko, jak to możliwe i nie muszą czytać całego pliku wejściowego przed wykonaniem tej czynności.

Chociaż robią wszystko N, wszystkie trzy różnią się metodami rekurencji.

Pierwsze polecenie

Pierwsze polecenie wykorzystuje bardzo prostą N;P;Dpętlę. Te trzy polecenia są wbudowane w dowolny kompatybilny z POSIX sedi ładnie się uzupełniają.

  • N- jak już wspomniano, dołącza Nlinię wejściową ext do przestrzeni wzorów po wstawionym \nograniczniku ewline.
  • P- jak p; to Prints wzorzec-przestrzeń - ale tylko do pierwszego występującego \ncharakteru ewline. I tak, biorąc pod uwagę następujące dane wejściowe / polecenia:

    • printf %s\\n one two | sed '$!N;P;d'
  • sed Prints tylko jeden . Jednak z ...

  • D- jak d; to Deletes wzorzec-przestrzeń i zaczyna kolejną linię cyklu. W przeciwieństwie do d , Dusuwa tylko do pierwszego występującego \newline w przestrzeni wzorca. Jeśli po \nznaku ewline w przestrzeni wzorcowej znajduje się więcej przestrzeni , sedrozpoczyna się następny cykl linii od tego, co pozostało. Jeżeli dw poprzednim przykładzie zostały zastąpione z D, na przykład, sedby Prukuj zarówno jeden i dwa .

To polecenie jest powtarzane tylko dla wierszy, które nie pasują do s///instrukcji ubstitution. Ponieważ s///ubstitution usuwa \ndodaną ewline N, nigdy nie pozostaje nic po sed Dusunięciu przestrzeni wzorców.

Testy można wykonać w celu zastosowania Pi / lub Dwybiórczo, ale są też inne polecenia, które lepiej pasują do tej strategii. Ponieważ rekurencji jest realizowany obsłużyć kolejne linie, które pasują tylko część reguły zastępczej, kolejne sekwencje linii pasujących oba końce na s///ubstitution nie działają dobrze .:

Biorąc pod uwagę ten wkład:

first "line"
<second>"line"
<second>"line"
<second>line and so on

... drukuje ...

first other characters "line"
<second>other characters line and so on

Poradzi sobie jednak

first "line"
second "line"
<second>line

...w porządku.

Drugie polecenie

To polecenie jest bardzo podobne do trzeciego. Obaj stosują etykietę :branch / test (jak pokazano również w odpowiedzi Joesepha R. tutaj ) i powracają do niej pod pewnymi warunkami.

  • -e :n -e- przenośne sedskrypty ograniczają :definicję etykiety za pomocą \newline lub nowej wbudowanej -einstrukcji xecution .
    • :n- definiuje etykietę o nazwie n. To może być zwrócone w dowolnym momencie albo bnalbo tn.
  • tn- tkomenda est powraca do określonej etykiety (lub, jeśli nie została podana, kończy działanie skryptu dla bieżącego cyklu linii), jeśli jakakolwiek s///ubstitution od czasu zdefiniowania etykiety lub ostatniego wywołania tests zakończyła się powodzeniem.

W tym poleceniu następuje rekurencja dla pasujących linii. Jeśli z sedpowodzeniem zastąpi wzorzec innymi znakami , sedpowraca do :netykiety i próbuje ponownie. Jeśli s///nie zostanie wykonana sedumstitution, automatycznie drukuje się przestrzeń wzorcowa i rozpoczyna się następny cykl linii.

Zwykle lepiej radzi sobie z kolejnymi sekwencjami. Tam, gdzie ostatni zawiódł, wyświetla się:

first other characters other characters other characters line and so on

Trzecie polecenie

Jak wspomniano, logika tutaj jest bardzo podobna do ostatniej, ale test jest bardziej wyraźny.

  • /"$/bn- to jest sedtest. Ponieważ bpolecenie ranch jest funkcją tego adresu, sedbędzie brancho tylko :npo \ndodaniu ewline, a przestrzeń wzorców nadal kończy się "podwójnym cudzysłowem.

Jest tak mało zrobione pomiędzy Ni bjak to możliwe - w ten sposób sedmożna bardzo szybko zebrać dokładnie tyle danych, ile jest to konieczne, aby upewnić się, że poniższy wiersz nie pasuje do twojej reguły. W s///różni się tutaj tym, że zatrudnia ubstitution gskroniowe flagi - i tak to zrobi wszelkie niezbędne zamienniki naraz. Biorąc pod uwagę identyczne dane wejściowe, polecenie wypisuje identycznie do ostatniego.

mikeserv
źródło
Przepraszam za trywialne pytanie, ale jakie jest znaczenie DATAi jak odbierasz wprowadzanie tekstu?
BowPark
@BowPark - W tym przykładzie <<\DATA\ntext input\nDATA\njest upieczony, ale jest to tylko tekst przekazany sedprzez powłokę w dokumencie tutaj . Działa to tak samo jak sed 'script' filenamelub process that writes to stdout | sed 'script'. To pomaga?
mikeserv
Tak, dziękuję! Dlaczego bez Dkażdej zmodyfikowanej linii jest podwójna? ( sed
Użyłeś
1
@BowPark - pomijasz dublowanie, Dponieważ w Dprzeciwnym razie Dusuwa z wyjścia to, co teraz widzisz podwójnie. Właśnie dokonałem edycji - i mogę ją rozwinąć wkrótce.
mikeserv
1
@BowPark - ok, zaktualizowałem go i podałem opcje. Teraz może być trochę łatwiej przeczytać / zrozumieć. Ja również wyraźnie to Dpowiedziałem.
mikeserv
7

Cóż, mogę wymyślić kilka prostych sposobów, ale żaden z nich nie obejmuje grep(który i tak nie zastępuje) ani sed.

  1. Perl

    Aby wymienić każde wystąpienie "line"\n<second>z other characters, użytkowania:

    $ perl -00pe 's/"line"\n<second>/other characters /g' file
    first other characters line and so on
    

    Lub, aby traktować wiele następujących po sobie wystąpień "line"\n<second>jako jednego i zastąpić je wszystkie jednym other characters, użyj:

    perl -00pe 's/(?:"line"\n<second>)+/other characters /g' file
    

    Przykład:

    $ cat file
    first "line"
    <second>"line"
    <second>"line"
    <second>line and so on
    $ perl -00pe 's/(?:"line"\n<second>)+/other characters /g' file
    first other characters line and so on
    

    -00Powoduje Perl odczytać pliku w trybie „ust”, co oznacza, że „linie” są zdefiniowane przez \n\nzamiast \nistocie każdy akapit jest traktowana jako linia. Podstawienie pasuje zatem do nowej linii.

  2. awk

    $  awk -v RS="\n\n" -v ORS="" '{
          sub(/"line"\n<second>/,"other characters ", $0)
          print;
        }' file 
    first other characters line and so on
    

    Ten sam podstawowy pomysł, ustawiamy separator rekordów ( RS), aby \n\nzamazał cały plik, a następnie wyjściowy separator rekordów na nic (w przeciwnym razie drukowany jest dodatkowy znak nowej linii), a następnie używamy sub()funkcji, aby dokonać zamiany.

terdon
źródło
2
@mikeserv? Który? Po drugie, OP powiedział, że chcą „zastąpić jedno lub więcej wystąpień”, więc zjedzenie akapitu może być tym, czego oczekują.
terdon
bardzo dobry punkt. Wydaje mi się, że za każdym razem bardziej się skupiałem i uzyskiwałem , ale nie jest jasne, czy powinna to być jedna zamiana na wystąpienie, czy jedna zamiana na sekwencję wystąpień ... @BowPark?
mikeserv
Potrzebny jest jeden zamiennik na wystąpienie.
BowPark,
@BowPark OK, wtedy pierwsze podejście perla lub awk powinny działać. Czy nie dają pożądanej wydajności?
terdon
Działa, dziękuję, ale trzecia linia awkpowinna być print;}' file. Muszę unikać Perla i najlepiej go używać sed, w każdym razie zasugerowałeś dobre alternatywy.
BowPark
6

przeczytaj cały plik i dokonaj globalnej zamiany:

sed -n 'H; ${x; s/"line"\n<second>/other characters /g; p}' <<END
first "line"
<second> line followed by "line"
<second> and last
END
first other characters  line followed by other characters  and last
Glenn Jackman
źródło
Tak. Działa, ale co, jeśli mam wiele wystąpień?
BowPark
Racja. Naprawiono
glenn jackman
1
przepraszam, że znów wybrałem nitpick, ale ${cmds}jest specyficzne dla GNU - większość innych sedbędzie wymagać \newline lub -eprzerwy między pi }. Możesz całkowicie pominąć nawiasy klamrowe - i przenośnie - a nawet uniknąć wstawienia dodatkowego \nznaku ewline w pierwszej linii, takiego jak:sed 'H;1h;$!d;x;s/"line"\n<second>/other characters /g'
mikeserv
Testowałem to i wydaje się, że nie jest przenośne. Drukuje dodatkowy nowy wiersz na początku danych wyjściowych, ale wynik jest prawidłowy w GNU.
BowPark
Aby usunąć wiodący nowy wiersz: sed -n '1{h;n};H; ${x; s/"line"\n<second>/other characters /g; p}'- staje się to jednak niemożliwe do utrzymania.
glenn jackman
3

Oto wariant odpowiedzi Glenna, który zadziała, jeśli wystąpi wiele kolejnych wystąpień (działa sedtylko z GNU ):

sed ':x /"line"/N;s/"line"\n<second>/other characters/;/"line"/bx' your_file

To :xtylko etykieta dla rozgałęzień. Zasadniczo działa to tak, że sprawdza wiersz po podstawieniu, a jeśli nadal jest zgodny "line", rozgałęzia się z powrotem do :xetykiety (to bxrobi) i dodaje kolejną linię do bufora i rozpoczyna przetwarzanie.

Joseph R.
źródło
@mikeserv Prosimy o sprecyzowanie, co masz na myśli. To zadziałało dla mnie.
Joseph R.
@mikeserv Przepraszam, naprawdę nie wiem o czym mówisz. Skopiowałem powyższą linię kodu z powrotem do terminala i działała poprawnie.
Joseph R.
1
wycofane - najwyraźniej działa to w GNU, sedktóry przenosi obsługę etykiet nie-POSIX wystarczająco daleko, aby zaakceptować spację jako ogranicznik deklaracji etykiety. Należy jednak pamiętać, że każda inna sedzawiedzie tam - i zawiedzie N. GNU sedłamie wytyczne POSIX, aby wydrukować przestrzeń wzorcową przed wyjściem Nz ostatniego wiersza, ale POSIX wyjaśnia, że ​​jeśli Npolecenie zostanie odczytane w ostatnim wierszu, nic nie powinno zostać wydrukowane.
mikeserv
Jeśli edytujesz wpis w celu określenia GNU, cofnę mój głos i usunę te komentarze. Warto również dowiedzieć się o vkomendzie GNU, która się psuje, sedale w GNU w wersji 4 i nowszej nie ma żadnej opcji.
mikeserv
1
w takim przypadku będę oferować jeszcze jedno - można to zrobić przenośnie jak: sed -e :x -e '/"line"/{$!N' -e '};s/"line"\n<second>/other characters/;/"line"/bx'.
mikeserv