I'd like to use sed or perl to replace all occurrences of a word that doesn't have a certain word in front of it.

For example, I have a text file that contains a plot of a movie and I want to replace all occurrences of a character's last name with their first name, but only if their first name doesn't come immediately before their last name.

Sample text might look like this:

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

I want it to look like this:

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

If I just do sed 's/Smith/John/' file, then I would have:

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

The first name that comes before the last name will always be the same. I don't have to deal with John Smith and Frank Smith. I just need a way to match Smith that doesn't have John preceding it.

  • Which sed are you talking about? Commented Nov 6, 2011 at 3:49
  • GNU sed 4.2.1 on Linux
    – jonescb
    Commented Nov 6, 2011 at 3:58

3 Answers 3


Would be easy with any language where the regular expressions are capable to lookbehind. Of course, Perl is the first on list:

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

The weak point is having more than one non-word character between “John” and “Smith”. Unfortunately a quantifier like + for \W would raise “Variable length lookbehind not implemented” error.


EDIT .. re your comment.. Here is a new script which doesn't concern itself about (eg.) William Smith. It temporarily obfuscates patterns which it keeps as Smith (unchanged).

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

If you are concerned about Mr. Mr Mrs... then this works.

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

You can cater for William by adding his name to the or list, eg.
sed -r 's/\<(William|John|...

This is the orginal script

sed -r 's/(^|[[:punct:]] |\<[a-z]+ )(Smith\>)/\1John/'
  • This works, but the one problem I found was that if the word before Smith is capitalized (e.g. it comes after the first word in a sentence) then it doesn't match. The perl solution by manatwork doesn't have that problem, even if it would fail in other situations. Fortunately, my text file doesn't have titles like Mr. or people with the same last name.
    – jonescb
    Commented Nov 6, 2011 at 16:32
  • Yes thanks... I've posted an ammended script...
    – Peter.O
    Commented Nov 6, 2011 at 20:52
 sed -r 's/([^John] )Smith/\1John/g;s/([^Jane] )Johnson/\1Jane/g'

The () will capture the non-Firstname before a LastName, so they are backref'd in the replacement.



You're right. How about

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

This seems to do the trick.

  • This will fail if there is no other word before the name, for example “Smith and Jane Johnson talk about Smith's car.”
    – manatwork
    Commented Nov 6, 2011 at 15:24
  • 2
    [^John] matches one character which must be one of J, o, h or n. I doubt this is what you intended. There is no negation construct in regular expressions (Perl has (?!…) and (?<!…), but if you think of it as a negation, it probably won't do what you expect). Commented Nov 6, 2011 at 16:54
  • @Juaco: Your take-2 works, but is susceptible to unexpected data. I used a similar method (albeit a bit reluctantly) because using sed without it makes for bloated sed logic... temp1 will almost always be fine, but! watch out for that bus. To mitigate this possibility, I believe it is better to use characters which (almost) never occur in Latin-Script text files, eg Hex value \x01 \x02, or combinations of them, or perhaps \xe188b4 UTF-8 locale (ሴ - ETHIOPIC SYLLABLE SEE)..eg. echo -e 'Z' |sed 's/./\xe1\x88\xb4/' => when locale is UTF-8 ..
    – Peter.O
    Commented Nov 7, 2011 at 3:00

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .