Regex & Sed / Perl: соответствует слову, которому НЕ предшествует другое слово


11

Я хотел бы использовать sedили perlзаменить все вхождения слова, у которого нет определенного слова перед ним.

Например, у меня есть текстовый файл, который содержит сюжет фильма, и я хочу заменить все вхождения фамилии персонажа на его имя, но только в том случае, если его имя не сразу перед фамилией.

Пример текста может выглядеть так:

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

Я хочу, чтобы это выглядело так:

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

Если бы я просто сделал sed 's/Smith/John/' file, то я бы:

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

Имя, которое стоит перед фамилией, всегда будет одинаковым. Мне не нужно иметь дело с John Smithи Frank Smith. Мне просто нужен способ сопоставления Smith, который не имеет Johnпредшествующего ему.


О каком седе ты говоришь?
Игнасио Васкес-Абрамс

GNU sed 4.2.1 в Linux
jonescb

Ответы:


8

Было бы легко с любым языком, где регулярные выражения способны смотреть назад. Конечно, Perl является первым в списке:

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

Слабым местом является наличие более одного несловесного символа между «Джоном» и «Смитом». К сожалению, квантификатор типа +for \Wвызовет ошибку «Переменная длина не реализована».


6

РЕДАКТИРОВАТЬ .. re ваш комментарий .. Вот новый сценарий, который не касается (например,) Уильям Смит. Он временно запутывает шаблоны, которые он сохраняет как Смит (без изменений).

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

Если вы беспокоитесь о мистере мистере миссис ... тогда это работает.

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

Вы можете обслужить Уильяма , добавив его имя в список или , например.
sed -r 's/\<(William|John|...


Это оригинальный скрипт

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

Это работает, но одна проблема, которую я обнаружил, заключалась в том, что если слово до Смита пишется с большой буквы (например, оно идет после первого слова в предложении), то оно не совпадает. У perl-решения manatwork такой проблемы нет, даже если в других ситуациях это не удастся. К счастью, мой текстовый файл не имеет заголовков, таких как Mr. или люди с одинаковой фамилией.
Jonescb

Да, спасибо ... Я разместил исправленный сценарий ...
Peter.O

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

() Будет захватывать не-Firstname перед LastName, поэтому они заменяются в обратном порядке.

редактировать

@ Manatwork, Жиль

Вы правы. Как насчет

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

Это, кажется, делает трюк.


Это не удастся, если перед именем нет другого слова, например «Смит и Джейн Джонсон говорят об автомобиле Смита».
manatwork

2
[^John]соответствует один символ , который должен быть один из J, o, hили n. Я сомневаюсь, что это то, что вы хотели. В регулярных выражениях отсутствует конструкция отрицания (Perl имеет (?!…)и (?<!…), но если вы думаете о нем как об отрицании, он, вероятно, не будет выполнять то, что вы ожидаете).
Жиль "ТАК - перестань быть злым"

@Juaco: Ваш дубль-2 работает, но подвержен неожиданным данным. Я использовал подобный метод (хотя и немного неохотно), потому что использование sedбез него делает для раздутой sed логики ... temp1почти всегда будет хорошо, но! следите за этим автобусом. Чтобы уменьшить эту возможность, я считаю, что лучше использовать символы, которые (почти) никогда не встречаются в текстовых файлах Latin-Script, например, шестнадцатеричное значение \ x01 \ x02 или их комбинации, или, возможно, \ xe188b4 языковой стандарт UTF-8 (ሴ - ЭТИОПИЧЕСКИЙ СЛОВАРЬ СМ.) echo -e 'Z' |sed 's/./\xe1\x88\xb4/'=> когда языковой стандарт UTF-8 ..
Peter.O
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.