Regex & Sed / Perl: Übereinstimmendes Wort, dem kein anderes Wort vorangestellt ist

11

Ich möchte alle Vorkommen eines Wortes verwenden sedoder perlersetzen, das kein bestimmtes Wort vor sich hat.

Zum Beispiel habe ich eine Textdatei, die eine Handlung eines Films enthält, und ich möchte alle Vorkommen des Nachnamens eines Charakters durch seinen Vornamen ersetzen, aber nur, wenn sein Vorname nicht unmittelbar vor seinem Nachnamen steht.

Der Beispieltext könnte folgendermaßen aussehen:

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

Ich möchte, dass es so aussieht:

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

Wenn ich es nur tue sed 's/Smith/John/' file, hätte ich:

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

Der Vorname, der vor dem Nachnamen steht, ist immer der gleiche. Ich muss mich nicht mit John Smithund befassen Frank Smith. Ich brauche nur einen passenden Weg, Smithdem es nicht Johnvorausgeht.

jonescb
quelle
Über welches Sed sprichst du?
Ignacio Vazquez-Abrams
GNU sed 4.2.1 unter Linux
jonescb

Antworten:

8

Wäre einfach mit jeder Sprache, in der die regulären Ausdrücke in der Lage sind, sich zu verhalten. Natürlich ist Perl der erste auf der Liste:

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

Die Schwachstelle besteht darin, dass zwischen „John“ und „Smith“ mehr als ein Nicht-Wort-Zeichen steht. Leider würde ein Quantifizierer wie +for \Wden Fehler "Lookbehind nicht implementiert" mit variabler Länge auslösen.

Mann bei der Arbeit
quelle
6

EDIT .. re your comment .. Hier ist ein neues Skript, das sich nicht um (z. B.) William Smith kümmert. Es verschleiert vorübergehend Muster, die es als Smith beibehält (unverändert).

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

Wenn Sie sich Sorgen um Herrn, Herrn, Frau machen, dann funktioniert das.

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

Sie können für William sorgen, indem Sie seinen Namen zur Liste oder hinzufügen , z.
sed -r 's/\<(William|John|...


Dies ist das ursprüngliche Skript

sed -r 's/(^|[[:punct:]] |\<[a-z]+ )(Smith\>)/\1John/'
Peter.O
quelle
Das funktioniert, aber das einzige Problem, das ich gefunden habe, war, dass wenn das Wort vor Smith groß geschrieben wird (z. B. nach dem ersten Wort in einem Satz), es nicht übereinstimmt. Die Perl-Lösung von Manatwork hat dieses Problem nicht, selbst wenn es in anderen Situationen fehlschlagen würde. Glücklicherweise enthält meine Textdatei keine Titel wie Mr. oder Personen mit demselben Nachnamen.
Jonescb
Ja danke ... Ich habe ein geändertes Skript gepostet ...
Peter.O
1
 sed -r 's/([^John] )Smith/\1John/g;s/([^Jane] )Johnson/\1Jane/g'

Das () erfasst den Nicht-Vornamen vor einem Nachnamen, sodass sie beim Ersetzen zurückverfolgt werden.

Bearbeiten

@ Manatwork, Gilles

Du hast recht. Wie wäre es mit

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

Dies scheint den Trick zu tun.

an einer
quelle
Dies schlägt fehl, wenn vor dem Namen kein anderes Wort steht, z. B. "Smith und Jane Johnson sprechen über Smiths Auto."
Manatwork
1
[^John]einstimmt Charakter , die aus einer sein muss J, o, hoder n. Ich bezweifle, dass Sie das beabsichtigt haben. In regulären Ausdrücken gibt es kein Negationskonstrukt (Perl hat (?!…)und (?<!…), aber wenn Sie es als Negation betrachten, wird es wahrscheinlich nicht das tun, was Sie erwarten).
Gilles 'SO - hör auf böse zu sein'
@ Juaco: Ihr Take-2 funktioniert, ist aber anfällig für unerwartete Daten. Ich habe eine ähnliche Methode angewendet (wenn auch etwas widerstrebend), weil die Verwendung sedohne sie zu einer aufgeblähten Logik führt ... temp1wird fast immer in Ordnung sein, aber! Pass auf den Bus auf. Um diese Möglichkeit abzuschwächen, ist es meines Erachtens besser, Zeichen zu verwenden, die (fast) nie in Textdateien mit lateinischer Schrift vorkommen, z. B. Hex-Wert \ x01 \ x02 oder Kombinationen davon oder möglicherweise das UTF-8-Gebietsschema \ xe188b4 (ሴ - ÄTHIOPISCH SYLLABLE SEE) .. zB. echo -e 'Z' |sed 's/./\xe1\x88\xb4/'=> wenn das Gebietsschema UTF-8 ist ..
Peter.O