Regex i Sed / Perl: Dopasuj słowo, które NIE JEST poprzedzone innym słowem

11

Chciałbym użyć sedlub perlzastąpić wszystkie wystąpienia słowa, które nie ma określonego słowa przed nim.

Na przykład mam plik tekstowy, który zawiera fabułę filmu i chcę zastąpić wszystkie wystąpienia nazwiska postaci ich imieniem, ale tylko wtedy, gdy ich imię nie pojawi się bezpośrednio przed imieniem.

Przykładowy tekst może wyglądać następująco:

John Smith and Jane Johnson talk about Smith's car.

Chcę, aby wyglądało to tak:

John Smith and Jane Johnson talk about John's car.

Gdybym tak zrobił sed 's/Smith/John/' file, miałbym:

John John and Jane Johnson talk about John's car.

Imię poprzedzające nazwisko zawsze będzie takie samo. Nie mam do czynienia z John Smithi Frank Smith. Potrzebuję tylko sposobu dopasowania Smith, który nie ma Johngo wcześniej.

jonescb
źródło
O którym serze mówisz?
Ignacio Vazquez-Abrams
GNU sed 4.2.1 w systemie Linux
jonescb

Odpowiedzi:

8

Byłoby łatwo w każdym języku, w którym wyrażenia regularne mogłyby wyglądać za sobą. Oczywiście Perl jest pierwszym na liście:

perl -pe 's/(?<!John\W)Smith/John/g' <<< "John Smith and Jane Johnson talk about Smith's car."

Słabym punktem jest posiadanie więcej niż jednego nie-słownego znaku między „John” i „Smith”. Niestety kwantyfikator jak +dla \Wpodniosłoby „zmiennej długości nie lookbehind realizowane” błąd.

człowiek w pracy
źródło
6

EDYTUJ … ponownie twój komentarz .. Oto nowy skrypt, który nie dotyczy (np.) Williama Smitha. Tymczasowo zaciemnia wzory, które zachowuje jako Smith (niezmieniony).

sed -r 's/\<(John) (Smith)\>/\1\x01x\2/g; 
        s/\<Smith\>/John/g;  s/\x01x/ /g'

Jeśli martwisz się o pana Pana ... to działa.

sed -r 's/\<(John|((M(r|rs|s))\.?)) (Smith)\>/\1\x01x\5/g
        s/\<Smith\>/John/g; s/\x01x/ /g'

Możesz zaspokoić Williama , dodając jego nazwisko do listy lub , np.
sed -r 's/\<(William|John|...


To jest oryginalny skrypt

sed -r 's/(^|[[:punct:]] |\<[a-z]+ )(Smith\>)/\1John/'
Peter.O
źródło
To działa, ale jedynym problemem, jaki znalazłem, było to, że jeśli słowo przed Smithem jest pisane wielką literą (np. Pojawia się po pierwszym słowie w zdaniu), to nie pasuje. Rozwiązanie perla przez manatwork nie ma tego problemu, nawet jeśli zawiedzie w innych sytuacjach. Na szczęście mój plik tekstowy nie ma tytułów takich jak Mr. lub osoby o tym samym nazwisku.
jonescb,
Tak, dziękuję ... Opublikowałem
poprawiony
1
 sed -r 's/([^John] )Smith/\1John/g;s/([^Jane] )Johnson/\1Jane/g'

() Przechwyci nie-imię przed LastName, więc zostaną zastąpione ponownie.

Edytować

@ manatwork, gilles

Masz rację. Co powiesz na

sed -r 's/(John Smith)/temp1/g;s/Smith/John/g;s/temp1/John Smith/g'

To wydaje się załatwić sprawę.

ata
źródło
To się nie powiedzie, jeśli przed imieniem nie ma innego słowa, na przykład „Smith i Jane Johnson rozmawiają o samochodzie Smitha”.
manatwork
2
[^John]dopasowuje jeden znak, który musi być jednym z J, o, hlub n. Wątpię, żeby to było to, co zamierzałeś. W wyrażeniach regularnych nie ma konstrukcji negacji (Perl ma (?!…)i (?<!…), ale jeśli uważasz to za negację, prawdopodobnie nie zrobi tego, czego oczekujesz).
Gilles 'SO - przestań być zły'
@Juaco: Twój Take-2 działa, ale jest podatny na nieoczekiwane dane. Użyłem podobnej metody (choć trochę niechętnie), ponieważ użycie sedjej bez powoduje rozdętą logikę sed ... temp1prawie zawsze będzie dobrze, ale! uważaj na ten autobus. Aby zminimalizować tę możliwość, uważam, że lepiej jest używać znaków, które (prawie) nigdy nie występują w plikach tekstowych łacińskich, np. Wartość szesnastkowa \ x01 \ x02, lub ich kombinacje, a może \ xe188b4 ustawienia regionalne UTF-8 (ሴ - ETHIOPIC SYLLABLE SEE) .. np. echo -e 'Z' |sed 's/./\xe1\x88\xb4/'=> gdy ustawieniem
narodowym