sed in-place flag that works both on Mac (BSD) and Linux
If you really want to just use sed -i
the 'easy' way, the following DOES work on both GNU and BSD/Mac sed
:
sed -i.bak 's/foo/bar/' filename
Note the lack of space and the dot.
Proof:
# GNU sed
% sed --version | head -1
GNU sed version 4.2.1
% echo 'foo' > file
% sed -i.bak 's/foo/bar/' ./file
% ls
file file.bak
% cat ./file
bar
# BSD sed
% sed --version 2>&1 | head -1
sed: illegal option -- -
% echo 'foo' > file
% sed -i.bak 's/foo/bar/' ./file
% ls
file file.bak
% cat ./file
bar
Obviously you could then just delete the .bak
files.
Compatibilty with sed for both Linux and MacOs
There is unfortunately a large number of behaviors in sed
which are not adequately standardized. It is hard to articulate a general answer which collects all of these.
For your specific question, a simple function wrapper like this should suffice.
sedi () {
case $(uname -s) in
*[Dd]arwin* | *BSD* ) sed -i '' "$@";;
*) sed -i "$@";;
esac
}
As you discovered, details of the c
command are also poorly standardized. I would simply suggest
sedi "2s/.*/$a/" file.dat
where obviously use a different separator if $a
could contain a slash; or if you are using Bash, use ${a//[\/]/\\/}
to backslash all slashes in the value.
Other behaviors which differ between implementations include extended regex support (-r
or -E
or not available), the ability to pass in multiple script fragments with -e
(the portable solution is to separate commands with newlines), the ability to pass standard input as the argument to -f
, and the general syntax of several commands (does the filename argument of the r
and w
commands extend up to the next whitespace, semicolon, or newline? Can you put a command immediately adjacent to a brace? What are the precise semantics of backslashed newlines and where are they mandatory? Where are backslashes before command arguments mandatory?) and obviously regex flags and features.
sed -i command for in-place editing to work with both GNU sed and BSD/OSX
OS X sed
handles the -i
argument differently to the Linux version.
You can generate a command that might "work" for both by adding -e
in this way:
# vv
sed -i -e 's|\(.*\)\.o:|$(OBJ_DIR)/\1.o $(OBJ_DIR)/\1.d $(TEST_OBJ_DIR)/\1_utest.o:|' $@
OS X sed -i
interprets the next thing after the -i
as a file extension for a backup copy of the in-place edit. (The Linux version only does this if there is no space between the -i
and the extension.) Obviously a side affect of using this is that you will get a backup file with -e
as an extension, which you may not want. Please refer to other answers to this question for more details, and cleaner approaches that can be used instead.
The behaviour you see is because OS X sed
consumes the s|||
as the extension (!) then interprets the next argument as a command - in this case it begins with t
, which sed
recognizes as a branch-to-label command expecting the target label as an argument - hence the error you see.
If you create a file test
you can reproduce the error:
$ sed -i 's|x|y|' test
sed: 1: "test": undefined label 'est'
sed command with -i option failing on Mac, but works on Linux
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
backupssed -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' *
In-place edits with sed on OS X
You can use the -i
flag correctly by providing it with a suffix to add to the backed-up file. Extending your example:
sed -i.bu 's/oldword/newword/' file1.txt
Will give you two files: one with the name file1.txt
that contains the substitution, and one with the name file1.txt.bu
that has the original content.
Mildly dangerous
If you want to destructively overwrite the original file, use something like:
sed -i '' 's/oldword/newword/' file1.txt
^ note the space
Because of the way the line gets parsed, a space is required between the option flag and its argument because the argument is zero-length.
Other than possibly trashing your original, I’m not aware of any further dangers of tricking sed this way. It should be noted, however, that if this invocation of sed
is part of a script, The Unix Way™ would (IMHO) be to use sed
non-destructively, test that it exited cleanly, and only then remove the extraneous file.
Related Topics
How to Find All Files Containing Specific Text on Linux
How to Search For a Multiline Pattern in a File
Exploring Docker Container'S File System
Use Sudo With Password as Parameter
Hello, World in Assembly Language With Linux System Calls
How to Create Docker Overlay Network Between Multi Hosts
Get Program Execution Time in the Shell
How to Find the Original User Through Multiple Sudo and Su Commands
How to Get File Creation Date/Time in Bash/Debian
How to Change the Output Color of Echo in Linux
What's the Difference of Section and Segment in Elf File Format
What Does $@ Mean in a Shell Script
Is There Any API For Determining the Physical Address from Virtual Address in Linux
Bash Script Process Substitution Syntax Error: "(" Unexpected
Why Is a Tilde in a Path Not Expanded in a Shell Script