435

I've successfully used the following sed command to search/replace text in Linux:

sed -i 's/old_link/new_link/g' *

However, when I try it on my Mac OS X, I get:

"command c expects \ followed by text"

I thought my Mac runs a normal BASH shell. What's up?

EDIT:

According to @High Performance, this is due to Mac sed being of a different (BSD) flavor, so my question would therefore be how do I replicate this command in BSD sed?

EDIT:

Here is an actual example that causes this:

sed -i 's/hello/gbye/g' *
Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
Yarin
  • 173,523
  • 149
  • 402
  • 512
  • 1
    This means that `sed` sees a "c" in your data as a command. Are you using a variable? Please post something that more closely represents the actual command and some data that you're processing. You can get a simple demonstration of this error by doing `echo x | sed c`. – Dennis Williamson Nov 22 '10 at 15:43
  • @Dennis, the simple command above causes this, though the data it's processing is an entire website (I'm converting all image links), including html and css files... – Yarin Nov 22 '10 at 15:48

13 Answers13

543

If you use the -i option you need to provide an extension for your backups.

If you have:

File1.txt
File2.cfg

The command (note the lack of space between -i and '' and the -e to make it work on new versions of Mac and on GNU):

sed -i'.original' -e 's/old_link/new_link/g' *

Create 2 backup files like:

File1.txt.original
File2.cfg.original

There is no portable way to avoid making backup files because it is impossible to find a mix of sed commands that works on all cases:

  • sed -i -e ... - does not work on OS X as it creates -e backups
  • sed -i'' -e ... - does not work on OS X 10.6 but works on 10.9+
  • sed -i '' -e ... - not working on GNU

Note Given that there isn't a sed command working on all platforms, you can try to use another command to achieve the same result.

E.g., perl -i -pe's/old_link/new_link/g' *

Benjamin W.
  • 46,058
  • 19
  • 106
  • 116
Sinetris
  • 8,288
  • 2
  • 22
  • 17
  • 6
    I had the same issue. Thanks for this solution. But where I tried with 'man sed' to find the description of '-i', nothing about using -i '' to ignore backups is there. This is my first blame. Second, when the error "command expects \ followed by text" shows up, why doesn't it directly tell us that it expects a backup name for the option '-i'!!?? Such thing happens everywhere: you get an error but not why the error, then you search for the manual which explains nothing about it. Then you google it to find someone else also has the same problem. I mean, why not giving example in the manual? – lukmac Aug 19 '12 at 22:43
  • or tell us why the error is there instead of only a information-less message that an error happens. This is a suggestion to all tool makers, if they can ever read this comment. – lukmac Aug 19 '12 at 22:45
  • 6
    @lukmac As far as `sed` can tell, you DID supply a backup suffix. The backup suffix is `s/old_link/new_link/g`. The next argument after that is supposed to be the editing commands. Because it interpreted the commands as the backup name, it then took the first filename as the editing commands, but they weren't valid. – Barmar Mar 20 '14 at 00:52
  • 3
    Will this always be an issue? Is there some way that Apple might be able to create a workaround / package GNU sed with OSX? Or, couldn't GNU sed support `sed -i '' -e ...`? – blong Nov 21 '16 at 17:06
  • 12
    `sed -i'' -e` seems to not work as expected on mac 10.14 – MoOx Dec 06 '18 at 16:13
  • Oh, the `perl` this is so ugly, but so nice. Thanks for a way out of this mess! – odinho - Velmont Feb 07 '20 at 17:57
  • 2
    `sed -i -- ...` seems to work fine. Also mentioned @https://stackoverflow.com/a/50245014/619961 – ikaerom Apr 06 '20 at 23:50
  • You can often avoid -i altogether by directing stdout to the new file. `sed -e 's/old_link/new_link/g' filename > filename.old`. But you'd need to loop over the filenames if you wanted to apply it to multiple files. – Bampfer Dec 17 '21 at 18:45
  • ` perl -i -pe's/old_link/new_link/g' *` does the trick! – Joshua Schlichting May 30 '22 at 04:21
91

I believe on OS X when you use -i an extension for the backup files is required. Try:

sed -i .bak 's/hello/gbye/g' *

Using GNU sed the extension is optional.

Dennis Williamson
  • 346,391
  • 90
  • 374
  • 439
83

This works with both GNU and BSD versions of sed:

sed -i'' -e 's/old_link/new_link/g' *

or with backup:

sed -i'.bak' -e 's/old_link/new_link/g' *

Note missing space after -i option! (Necessary for GNU sed)

Giacomo1968
  • 25,759
  • 11
  • 71
  • 103
chipiik
  • 1,970
  • 15
  • 15
  • 15
    The first one doesn't work on OSX (I've just tested it on 10.6.8) – marcin Nov 11 '13 at 12:44
  • 5
    For me on OS X (10.10.3), the first one created backup files suffixed with -e. No good. The second one was the only thing that worked for me consistently between Ubuntu and OS X. I didn't want backup files though, so I had to run a `rm` command right after to delete it. – Brendan Jul 30 '15 at 01:34
  • 5
    First line should be rewritten with a space to work on 10.10: `sed -i'' ...` => `sed -i '' ...` – Daniel Jomphe Aug 28 '15 at 20:36
  • 3
    @DanielJomphe But adding this space don't work on GNU sed – bric3 Feb 12 '16 at 16:17
  • 1
    @marcin first one works for me as well on OSX 10.11.2. Only thing is that it creates a backup file, which needs to be removed afterwards. Second looks better, as we don't have to figure out the filename later on. – vikas027 Feb 26 '17 at 03:06
  • and then clean up the annoying .bak files with `find . -name '*.bak' | xargs rm` – Blaskovicz Mar 11 '19 at 14:03
  • 2
    downvoted since I wasted so much time on this and it didn't work. (the first thing). The backup works but I wanted without, and `perl -i -pe` from the other answer gave that :) – odinho - Velmont Feb 07 '20 at 18:01
  • Didn't work on macOS 12 – senya Jun 29 '22 at 11:23
  • It does work on macOS 12, but it is important to also use `-e` before the editing command (without the string after `-i`, `-e` is optional). – friederbluemle Oct 11 '22 at 04:11
  • -i and -i'' are exactly the same, (as is '-i', "-i", -i'''''''' and so on), so having those ''s at the end makes no difference... – tomi Dec 26 '22 at 00:02
79

Had the same problem in Mac and solved it with brew:

brew install gnu-sed

and use as

gsed SED_COMMAND

you can set as well set sed as alias to gsed (if you want):

alias sed=gsed
gniourf_gniourf
  • 44,650
  • 9
  • 93
  • 104
Sruthi Poddutur
  • 1,371
  • 13
  • 7
  • 3
    Why did you give exactly the [same answer](http://stackoverflow.com/questions/4247068/sed-command-with-i-option-failing-on-mac-but-works-on-linux/10304970#10304970) as [Ohad Kravchick](http://stackoverflow.com/users/455262/ohad-kravchick)? – gniourf_gniourf Jan 01 '17 at 15:55
  • 3
    setting alias like this is not a great idea – SantaXL Oct 02 '19 at 00:24
  • 5
    Instead of an alias, as recommended in the corresponding brew page, add it to path: PATH="$(brew --prefix)/opt/gnu-sed/libexec/gnubin:$PATH" – y.luis.rojo Nov 25 '19 at 16:10
  • 1
    Since we're all lazy developers here, add this line to your `~/.zshrc` or bashrc file: `export PATH="/usr/local/opt/gnu-sed/libexec/gnubin:$PATH"` And then run `source ~/.zshrc` Change to your rc file of choice! Then run `which sed` to check if it's been changed. – Stan Smulders Jan 04 '22 at 10:48
33

Or, you can install the GNU version of sed in your Mac, called gsed, and use it using the standard Linux syntax.

For that, install gsed using ports (if you don't have it, get it at http://www.macports.org/) by running sudo port install gsed. Then, you can run sed -i 's/old_link/new_link/g' *

Ohad Kravchick
  • 1,154
  • 11
  • 15
11

Sinetris' answer is right, but I use this with find command to be more specific about what files I want to change. In general this should work (tested on osx /bin/bash):

find . -name "*.smth" -exec sed -i '' 's/text1/text2/g' {} \;

In general when using sed without find in complex projects is less efficient.

Lucas
  • 2,587
  • 1
  • 18
  • 17
11

Your Mac does indeed run a BASH shell, but this is more a question of which implementation of sed you are dealing with. On a Mac sed comes from BSD and is subtly different from the sed you might find on a typical Linux box. I suggest you man sed.

High Performance Mark
  • 77,191
  • 7
  • 105
  • 161
  • 4
    Thanks for pointing out the BSD issue- But I'm quite sed illiterate and just need a quick fix for my command- quick glance at man isn't telling me anything – Yarin Nov 22 '10 at 15:50
  • 33
    Most SO answers are buried somewhere in a man, but that's what SO is for- busy people who need answers from smart people – Yarin Nov 22 '10 at 16:05
11

Insead of calling sed with sed, I do ./bin/sed

And this is the wrapper script in my ~/project/bin/sed

#!/bin/bash

if [[ "$OSTYPE" == "darwin"* ]]; then
  exec "gsed" "$@"
else
  exec "sed" "$@"
fi

Don't forget to chmod 755 the wrapper script.

american-ninja-warrior
  • 7,397
  • 11
  • 46
  • 80
7

I've created a function to handle sed difference between MacOS (tested on MacOS 10.12) and other OS:

OS=`uname`
# $(replace_in_file pattern file)
function replace_in_file() {
    if [ "$OS" = 'Darwin' ]; then
        # for MacOS
        sed -i '' -e "$1" "$2"
    else
        # for Linux and Windows
        sed -i'' -e "$1" "$2"
    fi
}

Usage:

$(replace_in_file 's,MASTER_HOST.*,MASTER_HOST='"$MASTER_IP"',' "./mysql/.env")

Where:

, is a delimeter

's,MASTER_HOST.*,MASTER_HOST='"$MASTER_IP"',' is pattern

"./mysql/.env" is path to file

v.babak
  • 818
  • 11
  • 13
2

Here is an option in bash scripts:

#!/bin/bash

GO_OS=${GO_OS:-"linux"}

function detect_os {
    # Detect the OS name
    case "$(uname -s)" in
      Darwin)
        host_os=darwin
        ;;
      Linux)
        host_os=linux
        ;;
      *)
        echo "Unsupported host OS. Must be Linux or Mac OS X." >&2
        exit 1
        ;;
    esac

   GO_OS="${host_os}"
}

detect_os

if [ "${GO_OS}" == "darwin" ]; then
    sed -i '' -e ...
else
    sed -i -e ...
fi
Igor Dolzhikov
  • 199
  • 1
  • 2
1

As the other answers indicate, there is not a way to use sed portably across OS X and Linux without making backup files. So, I instead used this Ruby one-liner to do so:

ruby -pi -e "sub(/ $/, '')" ./config/locales/*.yml

In my case, I needed to call it from a rake task (i.e., inside a Ruby script), so I used this additional level of quoting:

sh %q{ruby -pi -e "sub(/ $/, '')" ./config/locales/*.yml}
Dan Kohn
  • 33,811
  • 9
  • 84
  • 100
1

Here's how to apply environment variables to template file (no backup need).

1. Create template with {{FOO}} for later replace.

echo "Hello {{FOO}}" > foo.conf.tmpl

2. Replace {{FOO}} with FOO variable and output to new foo.conf file

FOO="world" && sed -e "s/{{FOO}}/$FOO/g" foo.conf.tmpl > foo.conf

Working both macOS 10.12.4 and Ubuntu 14.04.5

katopz
  • 591
  • 6
  • 14
-3
sed -ie 's/old_link/new_link/g' *

Works on both BSD & Linux with gnu sed

ashokrajar
  • 148
  • 1
  • 5