Regex y Sed / Perl: coincide con la palabra que NO ESTÁ precedida por otra palabra

11

Me gustaría usar sedo perlreemplazar todas las apariciones de una palabra que no tiene una palabra determinada en frente.

Por ejemplo, tengo un archivo de texto que contiene una trama de una película y quiero reemplazar todas las apariciones del apellido de un personaje con su nombre, pero solo si su nombre no aparece inmediatamente antes de su apellido.

El texto de muestra podría verse así:

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

Quiero que se vea así:

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

Si solo lo hago sed 's/Smith/John/' file, entonces tendría:

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

El primer nombre que viene antes del apellido siempre será el mismo. No tengo que lidiar con John Smithy Frank Smith. Solo necesito una forma de igualar Smithque no tenga Johnprecedente.

jonescb
fuente
¿De qué sed estás hablando?
Ignacio Vazquez-Abrams
GNU sed 4.2.1 en Linux
jonescb

Respuestas:

8

Sería fácil con cualquier lenguaje donde las expresiones regulares sean capaces de mirar hacia atrás. Por supuesto, Perl es el primero en la lista:

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

El punto débil es tener más de un carácter sin palabras entre "John" y "Smith". Desafortunadamente, un cuantificador como +for \Wgeneraría el error "Longitud variable mirando hacia atrás no implementado".

hombre trabajando
fuente
6

EDITAR ... re su comentario ... Aquí hay un nuevo guión que no se preocupa por (por ejemplo) William Smith. Temporalmente ofusca los patrones que mantiene como Smith (sin cambios).

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

Si le preocupa el Sr. Sr. Sra. ... entonces esto funciona.

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

Puede atender a William agregando su nombre a la lista o , por ejemplo.
sed -r 's/\<(William|John|...


Este es el guión original

sed -r 's/(^|[[:punct:]] |\<[a-z]+ )(Smith\>)/\1John/'
Peter.O
fuente
Esto funciona, pero el único problema que encontré fue que si la palabra anterior a Smith está en mayúscula (por ejemplo, viene después de la primera palabra en una oración), entonces no coincide. La solución perl de manatwork no tiene ese problema, incluso si fallara en otras situaciones. Afortunadamente, mi archivo de texto no tiene títulos como Sr. o personas con el mismo apellido.
jonescb
Sí, gracias ... He publicado un script modificado ...
Peter.O
1
 sed -r 's/([^John] )Smith/\1John/g;s/([^Jane] )Johnson/\1Jane/g'

El () capturará el nombre que no sea el Nombre antes del Apellido, por lo que se volverán a consultar en el reemplazo.

Editar

@ manatwork, gilles

Tienes razón. Qué tal si

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

Esto parece hacer el truco.

ata
fuente
Esto fallará si no hay otra palabra antes del nombre, por ejemplo, "Smith y Jane Johnson hablan sobre el auto de Smith".
manatwork
1
[^John]coincide con un carácter que debe ser uno de J, o, ho n. Dudo que esto sea lo que pretendías. No existe una construcción de negación en las expresiones regulares (Perl tiene (?!…)y (?<!…), pero si lo considera una negación, probablemente no hará lo que espera).
Gilles 'SO- deja de ser malvado'
@Juaco: Su take-2 funciona, pero es susceptible a datos inesperados. Usé un método similar (aunque un poco de mala gana) porque usarlo sedsin él hace que la lógica sed hinchada ... temp1casi siempre estará bien, ¡pero! cuidado con ese autobús. Para mitigar esta posibilidad, creo que es mejor usar caracteres que (casi) nunca aparecen en los archivos de texto de Latin-Script, por ejemplo, valor hexadecimal \ x01 \ x02, o combinaciones de ellos, o tal vez \ xe188b4 UTF-8 locale (ሴ - VER SÍLABLE ETIÓPICO) .. ej. echo -e 'Z' |sed 's/./\xe1\x88\xb4/'=> cuando la configuración regional es UTF-8 ..
Peter.O