This question already has answers here:
sed command with -i option failing on Mac, but works on Linux
(13 answers)
sed in-place flag that works both on Mac (BSD) and Linux
(15 answers)
Closed 4 years ago.
I'm trying to get sed to replace a line in a file with the contents of another file. Got this to work, however the in-place replacement somehow produces an extra file with -e suffixed.
This only seems to happens on macOS (High Sierra), and doesn't happen on Linux (Alpine) as I tried to reproduce this in a docker container.
My commands that reproduce this in sequence:
$ echo 'someline' > target_file.txt
$ echo 'replacementcontent' > replacement.txt
$ sed -Ei -e "\#^someline\$#{
r replacement.txt
d
}" target_file.txt
$ cat target_file.txt
replacementcontent
$ ls
replacement.txt target_file.txt target_file.txt-e
The replacement worked as intended but in a Linux environment the target_file.txt-e would not be there.
I know there are differences between the macOS and Linux sed, but this just seems random but I'm likely just not understanding something.
Why does this happen, and can the command be written in an agnostic way (so that it works the same on both macOS and Linux)?
Related
This question already has answers here:
sed in-place flag that works both on Mac (BSD) and Linux
(15 answers)
How to replace string in a file in place using `sed`? [duplicate]
(2 answers)
Closed 1 year ago.
I'm using this command
sed -i'' -e "s/MARKETING_VERSION = 0.0.0;/MARKETING_VERSION = ${version//v};/" -f ios/App/App.xcodeproj/project.pbxproj
to replace the text in that file. This works on Ubuntu, but on Mac it changes the file and also creates a new file in the same directory called "project.bxproj-e" which seems to be the original file.
This command works on Mac:
sed -i '' "s/MARKETING_VERSION = 0.0.0;/MARKETING_VERSION = ${version//v};/" -f ios/App/App.xcodeproj/project.pbxproj
For it to work, I had to add a space after -i and remove the -e, however on Ubuntu I get this error:
sed: can't read s/MARKETING_VERSION = 3.0.0;/MARKETING_VERSION = 0.0.0;/: No such file or directory
I'm just looking for something that works on both envs.
If it helps, sed --version gets me "sed (GNU sed) 4.4" on Ubuntu. On Mac sed --version throws an error, but I can get this from the man page: "The sed utility is expected to be a superset of the IEEE Std 1003.2 (``POSIX.2'') specification." and also mentions that "-E -I -a and -i are non-standard and may not be available on other operating systems". But I don't know how to do it without them.
I am trying to perform Redis mass insertion using the command cat data.txt | redis-cli --pipe as mentioned in https://redis.io/topics/mass-insert.
The data format on macOS has to be converted so that mass insertion could be performed with cat ${FILE} | perl -i -p -e 's|[\r\n]+|\r\n|g' | redis-cli --pipe.
However, the above command does not work on a Linux environment (or a docker environment with the container built from an alpine based image). Instead, the following command has to performed cat ${FILE} | sed 's/\r*$/\r/' | redis-cli --pipe.
Is there a command that would work in both environments?
EDIT: Attached the following:
Redis Mass Insertion script on Alpine Linux: https://gist.github.com/francjohny/f2b13b4cfc147e07e52824ec88ba3781
Redis Mass Insertion script on Mac OS: https://gist.github.com/francjohny/b57756a1e0124dd562959ca5ece2a32b
Redis Protocol Format data file: https://gist.github.com/francjohny/0c21f32d9902809b215f4e92f5e6a9f1
➜ head ouput.rpf| xxd - Mac OS : https://gist.github.com/francjohny/e1a646ab44e7edd7374d28e9ca400711
➜ head ouput.rpf| xxd - Alpine Linux: https://gist.github.com/francjohny/252904928ded4c045448d12b205228df
Updated Answer
From the data you have added, it seems you just have linefeeds separating your lines, whereas Redis requires carriage return followed by linefeed. So basically, you want the equivalent of the unix2dos program, which is not included in macOS. However, macOS does include Perl, so you should be able to use:
perl -pe 's/\n/\r\n/' data.rpf | redis-cli --pipe
It works fine on my Mac.
Original Answer
You appear to have mixed line endings in your various environments. I would imagine this Perl would replace any number of carriage returns and line feeds in any mixture with a single carriage return and linefeed like Redis requires:
perl -pe 's|[\r\n]*|\r\n|' data.txt | redis-cli ...
If not, please answer my question in the comments.
I am trying to trouble shoot a problem I am seeing when running bash commands in Cygwin.
I am trying to assign the CLang version from a text file to a variable. If I run this in Cygwin:
$ (sed -n 1p "$CLANGC2_VERSION_FILE" | sed 's/\s//g')
I get this output (which is exactly what I want):
14.10.25903
Now, if I try and assign this to a variable it doesn't work. Here is what I am trying:
$ CLANGC2_VERSION=$(sed -n 1p "$CLANGC2_VERSION_FILE" | sed 's/\s//g')
but when I inspect or print the variable, it is empty.
What am I doing wrong?
Turns out that there is a known 'Big List of Dodgy Apps' (BLODA) which can interfere with Cygwin and bash.
The discussion I found is here: https://cygwin.com/ml/cygwin/2017-07/msg00197.html
The BLODA list is here: https://cygwin.com/faq/faq.html#faq.using.bloda
Turns out my AntiVirus is on the list.
I've removed the AV and now the commands work. There must be some low-level stuff going with the AV that causes it to fail.
You can use backticks to get the desired results.
CLANGC2_VERSION=`(sed -n 1p "$CLANGC2_VERSION_FILE" | sed 's/\s//g')`
I'm trying to use SED to extract text from a log file. I can do a search-and-replace without too much trouble:
sed 's/foo/bar/' mylog.txt
However, I want to make the search case-insensitive. From what I've googled, it looks like appending i to the end of the command should work:
sed 's/foo/bar/i' mylog.txt
However, this gives me an error message:
sed: 1: "s/foo/bar/i": bad flag in substitute command: 'i'
What's going wrong here, and how do I fix it?
Update: Starting with macOS Big Sur (11.0), sed now does support the I flag for case-insensitive matching, so the command in the question should now work (BSD sed doesn't reporting its version, but you can go by the date at the bottom of the man page, which should be March 27, 2017 or more recent); a simple example:
# BSD sed on macOS Big Sur and above (and GNU sed, the default on Linux)
$ sed 's/ö/#/I' <<<'FÖO'
F#O # `I` matched the uppercase Ö correctly against its lowercase counterpart
Note: I (uppercase) is the documented form of the flag, but i works as well.
Similarly, starting with macOS Big Sur (11.0) awk now is locale-aware (awk --version should report 20200816 or more recent):
# BSD awk on macOS Big Sur and above (and GNU awk, the default on Linux)
$ awk 'tolower($0)' <<<'FÖO'
föo # non-ASCII character Ö was properly lowercased
The following applies to macOS up to Catalina (10.15):
To be clear: On macOS, sed - which is the BSD implementation - does NOT support case-insensitive matching - hard to believe, but true. The formerly accepted answer, which itself shows a GNU sed command, gained that status because of the perl-based solution mentioned in the comments.
To make that Perl solution work with foreign characters as well, via UTF-8, use something like:
perl -C -Mutf8 -pe 's/öœ/oo/i' <<< "FÖŒ" # -> "Foo"
-C turns on UTF-8 support for streams and files, assuming the current locale is UTF-8-based.
-Mutf8 tells Perl to interpret the source code as UTF-8 (in this case, the string passed to -pe) - this is the shorter equivalent of the more verbose -e 'use utf8;'.Thanks, Mark Reed
(Note that using awk is not an option either, as awk on macOS (i.e., BWK awk and BSD awk) appears to be completely unaware of locales altogether - its tolower() and toupper() functions ignore foreign characters (and sub() / gsub() don't have case-insensitivity flags to begin with).)
A note on the relationship of sed and awk to the POSIX standard:
BSD sed and awk limit their functionality mostly to what the POSIX sed and
POSIX awk specs mandate, whereas their GNU counterparts implement many more extensions.
Editor's note: This solution doesn't work on macOS (out of the box), because it only applies to GNU sed, whereas macOS comes with BSD sed.
Capitalize the 'I'.
sed 's/foo/bar/I' file
Another work-around for sed on Mac OS X is to install gsedfrom MacPorts or HomeBrew and then create the alias sed='gsed'.
If you are doing pattern matching first, e.g.,
/pattern/s/xx/yy/g
then you want to put the I after the pattern:
/pattern/Is/xx/yy/g
Example:
echo Fred | sed '/fred/Is//willma/g'
returns willma; without the I, it returns the string untouched (Fred).
The sed FAQ addresses the closely related case-insensitive search. It points out that a) many versions of sed support a flag for it and b) it's awkward to do in sed, you should rather use awk or Perl.
But to do it in POSIX sed, they suggest three options (adapted for substitution here):
Convert to uppercase and store original line in hold space; this won't work for substitutions, though, as the original content will be restored before printing, so it's only good for insert or adding lines based on a case-insensitive match.
Maybe the possibilities are limited to FOO, Foo and foo. These can be covered by
s/FOO/bar/;s/[Ff]oo/bar/
To search for all possible matches, one can use bracket expressions for each character:
s/[Ff][Oo][Oo]/bar/
The Mac version of sed seems a bit limited. One way to work around this is to use a linux container (via Docker) which has a useable version of sed:
cat your_file.txt | docker run -i busybox /bin/sed -r 's/[0-9]{4}/****/Ig'
Use following to replace all occurrences:
sed 's/foo/bar/gI' mylog.txt
I had a similar need, and came up with this:
this command to simply find all the files:
grep -i -l -r foo ./*
this one to exclude this_shell.sh (in case you put the command in a script called this_shell.sh), tee the output to the console to see what happened, and then use sed on each file name found to replace the text foo with bar:
grep -i -l -r --exclude "this_shell.sh" foo ./* | tee /dev/fd/2 | while read -r x; do sed -b -i 's/foo/bar/gi' "$x"; done
I chose this method, as I didn't like having all the timestamps changed for files not modified. feeding the grep result allows only the files with target text to be looked at (thus likely may improve performance / speed as well)
be sure to backup your files & test before using. May not work in some environments for files with embedded spaces. (?)
Following should be fine:
sed -i 's/foo/bar/gi' mylog.txt
I've got a makefile (developed for gmake on Linux) that I'm attempting to port to MacOS, but it seems like sed doesn't want to cooperate. What I do is use GCC to autogenerate dependency files, and then tweak them a bit using sed. The relevant portion of the makefile:
$(OBJ_DIR)/%.d: $(SRC_DIR)/%.cpp
$(CPPC) -MM -MD $< -o $#
sed -i 's|\(.*\)\.o:|$(OBJ_DIR)/\1.o $(OBJ_DIR)/\1.d $(TEST_OBJ_DIR)/\1_utest.o:|' $#
While this runs with no trouble under GNU/Linux, I get errors like the following when attempting to build on MacOS:
sed: 1: "test/obj/equipmentConta ...": undefined label 'est/obj/equipmentContainer_utest.d'
sed: 1: "test/obj/dice_utest.d": undefined label 'est/obj/dice_utest.d'
sed: 1: "test/obj/color-string_u ...": undefined label 'est/obj/color-string_utest.d'
It would seem like sed is chopping off a character, but I can't see the solution.
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'
Actually, doing
sed -i -e "s/blah/blah/" files
doesn't do what you expect in MacOS either. Instead it creates backup files with -e extension.
The proper command for MacOS is
sed -i "" -e "s/blah/blah/" files
On Linux, remove the space between -i and "" (see related answer)
sed -i"" -e "s/blah/blah/" files
The currently accepted answer is flawed in two very important ways.
With BSD sed (the OSX version), the -e option is interpreted as
a file extension and therefore creates a backup file with a -e
extension.
Testing for the darwin kernel as suggested is not a reliable
approach to a cross platform solution since GNU or BSD sed could
be present on any number of systems.
A much more reliable test would be to simply test for the --version option which is only found in the GNU version of sed.
sed --version >/dev/null 2>&1
Once the correct version of sed is determined, we can then execute the command in its proper syntax.
GNU sed syntax for -i option:
sed -i -- "$#"
BSD sed syntax for -i option:
sed -i "" "$#"
Finally put it all together in a cross platform function to execute an in place edit sed commend:
sedi () {
sed --version >/dev/null 2>&1 && sed -i -- "$#" || sed -i "" "$#"
}
Example usage:
sedi 's/old/new/g' 'some_file.txt'
This solution has been tested on OSX, Ubuntu, Freebsd, Cygwin, CentOS, Red Hat Enterprise, & Msys.
martin clayton's helpful answer provides a good explanation of the problem[1], but his solution - as he states - has a potentially unwanted side effect.
Here are side-effect-free solutions:
Caveat: Solving the -i syntax problem alone, as below, may not be enough, because there are many other differences between GNU sed and BSD/macOS sed (for a comprehensive discussion, see this answer of mine).
Workaround with -i: Create a backup file temporarily, then clean it up:
With a non-empty suffix (backup-file filename extension) option-argument (a value that is not the empty string), you can use -i in a way that works with both BSD/macOS sed and GNU sed, by directly appending the suffix to the -i option.
This can be utilized to create a backup file temporarily that you can clean up right away:
sed -i.bak 's/foo/bar/' file && rm file.bak
Obviously, if you do want to keep the backup, simply omit the && rm file.bak part.
Workaround that is POSIX-compliant, using a temporary file and mv:
If only a single file is to be edited in-place, the -i option can be bypassed to avoid the incompatibility.
If you restrict your sed script and other options to POSIX-compliant features, the following is a fully portable solution (note that -i is not POSIX-compliant).
sed 's/foo/bar' file > /tmp/file.$$ && mv /tmp/file.$$ file
This command simply writes the modifications to a temporary file and, if the sed command succeeds (&&), replaces the original file with the temporary one.
If you do want to keep the original file as a backup, add another mv command that renames the original first.
Caveat: Fundamentally, this is what -i does too, except that it tries to preserve permissions and extended attributes (macOS) of the original file; however, if the original file is a symlink, both this solution and -i will replace the symlink with a regular file.
See the bottom half of this answer of mine for details on how -i works.
[1] For a more in-depth explanation, see this answer of mine.
This isn't quite an answer to the question, but one can get linux-equivalent behavior through
brew install gnu-sed
# Add to .bashrc / .zshrc
export PATH="/usr/local/opt/gnu-sed/libexec/gnubin:$PATH"
(previously there was a --with-default-names option to brew install gnu-sed but that has recently been removed)
I came across this issue as well and thought of the following solution:
darwin=false;
case "`uname`" in
Darwin*) darwin=true ;;
esac
if $darwin; then
sedi="/usr/bin/sed -i ''"
else
sedi="sed -i"
fi
$sedi 's/foo/bar/' /home/foobar/bar
Works for me ;-), YMMV
I work in a multi-OS team where ppl build on Windows, Linux and OS X. Some OS X users complained because they got another error - they had the GNU port of sed installed so I had to specify the full path.
I've corrected the solution posted by #thecarpy:
Here's a proper cross-platform solution for sed -i:
sedi() {
case $(uname) in
Darwin*) sedi=('-i' '') ;;
*) sedi='-i' ;;
esac
LC_ALL=C sed "${sedi[#]}" "$#"
}
I avoid using sed -i when writing scripts and i came up with simple solution:
printf '%s' "$(sed 's/foo/bar' file)" > file
much compatible and is POSIX-compliant. It is doing pretty much the same as sed -i, but this one does not create temp files, it directly redirect the changes to file.
As a noob idk what's the cons of doing this, the only matters is "It works"