150

How do I search and replace whole words using sed?

Doing

sed -i 's/[oldtext]/[newtext]/g' <file> 

will also replace partial matches of [oldtext] which I don't want it to do.

fedorqui
  • 275,237
  • 103
  • 548
  • 598
ksuralta
  • 16,276
  • 16
  • 38
  • 36

5 Answers5

203

\b in regular expressions match word boundaries (i.e. the location between the first word character and non-word character):

$ echo "bar embarassment" | sed "s/\bbar\b/no bar/g"
no bar embarassment
Joakim Lundborg
  • 10,920
  • 6
  • 32
  • 39
  • 19
    [not posix](http://sed.js.org/?gist=0d0377fffd0f8b21886ff288bc260423) | [GNU sed](http://sed.js.org/?gist=3c69aaff8facf8040a7c9556e80f0868) OK – Oleg Mazko Aug 25 '16 at 10:19
  • @OlegMazko I also came looking for the posix acceptable method to use in vim and what worked for me was s/\<7//g. I was trying to remove a 7 at the start of a word in my code. This link, specifically, J-P's solution, led me to the answer: http://stackoverflow.com/questions/3864467/whats-the-difference-between-vim-regex-and-normal-regex (couldn't get markdown to embed it like yours for some reason). – jimh Mar 29 '17 at 23:46
  • Thank you I was going mad trying to do a replacement on the word "and" so `\band\b` saved the day :) – MitchellK Jun 23 '18 at 13:22
  • Would be using `[[:space:]]word[[:space:]]` be considered acceptable @jimh – stephanmg Oct 16 '20 at 09:05
  • 1
    Didn't work for me. Maybe a different build of sed? Was default on CentOS 7 – jimh Oct 17 '20 at 17:03
161

On Mac OS X, neither of these regex syntaxes work inside sed for matching whole words

  • \bmyWord\b
  • \<myWord\>

Hear me now and believe me later, this ugly syntax is what you need to use:

  • /[[:<:]]myWord[[:>:]]/

So, for example, to replace mint with minty for whole words only:

  • sed "s/[[:<:]]mint[[:>:]]/minty/g"

Source: re_format man page

Roland Weber
  • 1,865
  • 2
  • 17
  • 27
Larry Gerndt
  • 1,827
  • 1
  • 12
  • 10
  • 24
    Just install GNU `sed` (and every other GNU tool) via MacPorts or Homebrew and make sure it comes first in your `PATH`. It's possible to make a Mac fairly usable. – Jim Stewart Aug 15 '13 at 20:41
  • 14
    @JimStewart great way to break a bunch of tools that assume ‘when on OS X, do as the OS Xers.’ That said, I can definitely suggest `brew install coreutils`, which prefixes all of the gnu tools. – ELLIOTTCABLE Jan 24 '15 at 00:33
  • 20
    `brew install gnu-sed` to use `gsed` – k107 Feb 25 '15 at 23:01
  • brew coreutils is awesome. are there any other brew packages which install a whole bunch of gnu versions of commands or do I need to install the rest individually? – ckot Mar 10 '15 at 16:58
  • 1
    answered my own question. there's a list of packages @ http://apple.stackexchange.com/questions/69223/how-to-replace-mac-os-x-utilities-with-gnu-core-utilities – ckot Mar 11 '15 at 13:55
  • 4
    Using perl is always a better solution: `perl -pe 's|\bone\b|two|g'`. Works stably while sed fails here and there. – Pavel Vlasov Mar 22 '19 at 01:13
19

Use \b for word boundaries:

sed -i 's/\boldtext\b/newtext/g' <file>
t0r0X
  • 4,212
  • 1
  • 38
  • 34
Mitch Wheat
  • 295,962
  • 43
  • 465
  • 541
9

In one of my machine, delimiting the word with "\b" (without the quotes) did not work. The solution was to use "\<" for starting delimiter and "\>" for ending delimiter.

To explain with Joakim Lundberg's example:

$ echo "bar embarassment" | sed "s/\<bar\>/no bar/g"
no bar embarassment
Community
  • 1
  • 1
Arun
  • 19,750
  • 10
  • 51
  • 60
1

For a posix compliant alternative, consider replacing word boundary matches (\b) by an expanded equivalent ([^a-zA-Z0-9]), also taking into account occurrences at start of line (^) and end of line ($).

However, this quickly becomes impractical if you want to support repeated occurrences of the word to replace (e.g. oldtext oldtext). sed --posix doesn't recognize expressions such as \(^\|[^a-zA-Z0-9]\), and you can't make use of lookarounds.

It seems we have to explictly match all possible cases. Here's a solution to replace mint with minty:

echo 'mint 0mint mint mint0 mint__mint mint__ mint_ -mint mint mint mint_ mint -mint- mint mint mintmint mint' \
  | sed --posix '   
s/^mint$/minty/g;
s/^mint\([^a-zA-Z0-9]\)/minty\1/g;
s/\([^a-zA-Z0-9]\)mint$/\1minty/g;
s/\([^a-zA-Z0-9]\)mint\([^a-zA-Z0-9]\)mint\([^a-zA-Z0-9]\)mint\([^a-zA-Z0-9]\)/\1minty\2minty\3minty\4/g;
s/\([^a-zA-Z0-9]\)mint\([^a-zA-Z0-9]\)mint\([^a-zA-Z0-9]\)/\1minty\2minty\3/g;
s/\([^a-zA-Z0-9]\)mint\([^a-zA-Z0-9]\)/\1minty\2/g;
'
# minty 0mint minty mint0 minty__minty minty__ minty_ -minty minty minty minty_ minty -minty- minty minty mintmint minty
fzbd
  • 477
  • 2
  • 8