Regex & Sed / Perl: trova la parola che NON è preceduta da un'altra parola


11

Vorrei usare sedo perlsostituire tutte le occorrenze di una parola che non ha una determinata parola davanti.

Ad esempio, ho un file di testo che contiene una trama di un film e voglio sostituire tutte le occorrenze del cognome di un personaggio con il loro nome, ma solo se il loro nome non viene immediatamente prima del loro cognome.

Il testo di esempio potrebbe essere simile al seguente:

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

Voglio che assomigli a questo:

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

Se lo facessi sed 's/Smith/John/' file, allora avrei:

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

Il nome che precede il cognome sarà sempre lo stesso. Non ho a che fare con John Smithe Frank Smith. Ho solo bisogno di un modo per abbinare Smithche non lo abbia Johnpreceduto.


Di quale sed stai parlando?
Ignacio Vazquez-Abrams,

GNU sed 4.2.1 su Linux
jonescb,

Risposte:


8

Sarebbe facile con qualsiasi lingua in cui le espressioni regolari siano in grado di guardare indietro. Certo, Perl è il primo sulla lista:

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

Il punto debole sta avendo più di un carattere non verbale tra "John" e "Smith". Sfortunatamente un quantificatore come +for \Wgenererebbe l'errore "Lookbehind di lunghezza variabile non implementato".


6

EDIT .. re il tuo commento .. Ecco una nuova sceneggiatura che non si preoccupa (ad es.) Di William Smith. Offusca temporaneamente i modelli che mantiene come Smith (invariato).

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

Se sei preoccupato per il signor Mr Mrs ... allora funziona.

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

Puoi soddisfare William aggiungendo il suo nome all'elenco o , ad es.
sed -r 's/\<(William|John|...


Questo è lo script originale

sed -r 's/(^|[[:punct:]] |\<[a-z]+ )(Smith\>)/\1John/'

Funziona, ma l'unico problema che ho riscontrato è che se la parola prima di Smith è scritta in maiuscolo (ad esempio, viene dopo la prima parola in una frase), allora non corrisponde. La soluzione perl di manatwork non ha questo problema, anche se fallirebbe in altre situazioni. Fortunatamente, il mio file di testo non ha titoli come Mr. o persone con lo stesso cognome.
jonescb,

Sì, grazie ... Ho pubblicato una sceneggiatura scritta ...
Peter.O,

1
 sed -r 's/([^John] )Smith/\1John/g;s/([^Jane] )Johnson/\1Jane/g'

Il () acquisirà il non-Firstname prima di un LastName, in modo che vengano rimandati nella sostituzione.

modificare

@ Manatwork, gilles

Hai ragione. Che ne dite di

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

Questo sembra fare il trucco.


Questo fallirà se non ci sono altre parole prima del nome, ad esempio "Smith e Jane Johnson parlano della macchina di Smith".
arte

1
[^John]partite di un carattere che deve essere uno dei J, o, ho n. Dubito che questo sia quello che volevi. Non esiste un costrutto di negazione nelle espressioni regolari (Perl ha (?!…)e (?<!…), ma se la consideri come una negazione, probabilmente non farà ciò che ti aspetti).
Gilles 'SO- smetti di essere malvagio' il

@Juaco: il tuo take-2 funziona, ma è sensibile a dati imprevisti. Ho usato un metodo simile (anche se un po 'a malincuore) perché l'uso sedsenza di esso rende logica la sedoria gonfia ... temp1quasi sempre andrà bene, ma! attenzione per quel bus. Per mitigare questa possibilità, credo che sia meglio usare caratteri che (quasi) non si verificano mai nei file di testo in caratteri latini, ad es. Valore esadecimale \ x01 \ x02, o combinazioni di essi, o forse \ xe188b4 UTF-8 locale (ሴ - ETIOPIC SYLLABLE SEE) .. es. echo -e 'Z' |sed 's/./\xe1\x88\xb4/'=> quando la locale è UTF-8 ..
Peter.O
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.