Regex & Sed / Perl: corresponde à palavra que NÃO é precedida por outra palavra

11

Gostaria de usar sedou perlsubstituir todas as ocorrências de uma palavra que não tenha uma determinada palavra na frente.

Por exemplo, eu tenho um arquivo de texto que contém a trama de um filme e desejo substituir todas as ocorrências do sobrenome de um personagem pelo primeiro nome, mas apenas se o primeiro nome não aparecer imediatamente antes do sobrenome.

O texto de exemplo pode ficar assim:

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

Eu quero que fique assim:

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

Se eu apenas fizer sed 's/Smith/John/' file, então eu teria:

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

O primeiro nome que vem antes do sobrenome sempre será o mesmo. Eu não tenho que lidar com John Smithe Frank Smith. Eu só preciso de uma maneira de combinar Smithque não tem Johnprecedente.

jonescb
fonte
De qual sed você está falando?
Ignacio Vazquez-Abrams,
GNU sed 4.2.1 no Linux
jonescb 6/11/11

Respostas:

8

Seria fácil com qualquer idioma em que as expressões regulares sejam capazes de olhar para trás. Obviamente, Perl é o primeiro da lista:

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

O ponto fraco é ter mais de um caractere que não seja uma palavra entre "John" e "Smith". Infelizmente um quantificador como +para \Welevaria “comprimento variável não lookbehind implementado” erro.

homem a trabalhar
fonte
6

EDIT .. re seu comentário .. Aqui está um novo script que não se preocupa com (por exemplo) William Smith. Oculta temporariamente os padrões que mantém como Smith (inalterados).

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

Se você está preocupado com o Sr. Sra ... então isso funciona.

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

Você pode atender a William adicionando seu nome à lista ou , por exemplo.
sed -r 's/\<(William|John|...


Este é o script original

sed -r 's/(^|[[:punct:]] |\<[a-z]+ )(Smith\>)/\1John/'
Peter.O
fonte
Isso funciona, mas o único problema que encontrei foi que, se a palavra antes de Smith estiver em maiúscula (por exemplo, ela vem após a primeira palavra em uma frase), ela não corresponde. A solução perl por manatwork não tem esse problema, mesmo que falhe em outras situações. Felizmente, meu arquivo de texto não possui títulos como Sr. ou pessoas com o mesmo sobrenome.
21411 jonescb
Sim, obrigado ... Eu publiquei um script ammended ...
Peter.O
1
 sed -r 's/([^John] )Smith/\1John/g;s/([^Jane] )Johnson/\1Jane/g'

O () capturará o não nome antes de um sobrenome, para que eles sejam retrocedidos na substituição.

Editar

@ manatwork, gilles

Você está certo. E se

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

Isso parece fazer o truque.

ata
fonte
Isso falhará se não houver outra palavra antes do nome, por exemplo "Smith e Jane Johnson falam sobre o carro de Smith".
Manatwork
1
[^John]corresponde a um caráter que deve ser um dos J, o, hou n. Duvido que seja isso que você pretendia. Não há construto de negação em expressões regulares (Perl tem (?!…)e (?<!…), mas se você pensar nisso como uma negação, provavelmente não fará o que você espera).
Gilles 'SO- stop be evil'
@ Juaco: Seu take-2 funciona, mas é suscetível a dados inesperados. Eu usei um método semelhante (embora com certa relutância), porque usar sedsem ele faz com que a lógica sed inchada ... temp1quase sempre será boa, mas! cuidado com o ônibus. Para atenuar essa possibilidade, acredito que é melhor usar caracteres que (quase) nunca ocorrem em arquivos de texto em script latino, por exemplo, valor hexadecimal \ x01 \ x02, ou combinações deles, ou talvez \ xe188b4 localidade UTF-8 (ሴ - SYLLABLE ETIÓPICO VER) .. por exemplo. echo -e 'Z' |sed 's/./\xe1\x88\xb4/'=> Quando localidade é UTF-8 ..
Peter.O