Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems. Join them; it only takes a minute:

Sign up
Here's how it works:
  1. Anybody can ask a question
  2. Anybody can answer
  3. The best answers are voted up and rise to the top

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.

share|improve this question
Which sed are you talking about? – Ignacio Vazquez-Abrams Nov 6 '11 at 3:49
GNU sed 4.2.1 on Linux – jonescb Nov 6 '11 at 3:58
up vote 5 down vote accepted

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.

share|improve this answer

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/'
share|improve this answer
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 Nov 6 '11 at 16:32
Yes thanks... I've posted an ammended script... – Peter.O Nov 6 '11 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.

share|improve this answer
This will fail if there is no other word before the name, for example “Smith and Jane Johnson talk about Smith's car.” – manatwork Nov 6 '11 at 15:24
[^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). – Gilles Nov 6 '11 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 Nov 7 '11 at 3:00

Your Answer


By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.