TL;DR
macOS ships with BSD sed, not GNU sed. BSD sed requires an explicit backup extension after -i. Pass an empty string to skip backup creation:
# macOS fix: add '' after -i
sed -i '' 's/foo/bar/g' filename.txt
Or install GNU sed via Homebrew and use gsed with the Linux syntax you already know.
The full error
sed: 1: "filename": extra characters at the end of L command
You run a sed -i command that works perfectly on Linux, switch to a Mac, and it explodes. The error mentions an "L command" even though you're not using one. Cryptic โ but the root cause makes sense once you know there are two incompatible flavors of sed.
Root cause
GNU sed and BSD sed handle the -i flag differently:
- GNU sed (Linux, most CI servers):
-iaccepts an optional suffix. Writesed -i 's/โฆ/โฆ/' fileand GNU sed treats the suffix as omitted, editing in-place with no backup. - BSD sed (macOS built-in):
-irequires a suffix โ always. Writesed -i 's/โฆ/โฆ/' fileand BSD sed grabs your substitution pattern as the backup extension. It then opens your target file and tries to execute its contents as a sed script. The first characters of your file get misread as sed commands โ often landing on something BSD sed interprets as anl(lowercase L) command with garbage after it.
That bizarre "extra characters at the end of L command" message means: I opened your file as a sed script and couldn't parse it. Apple has shipped BSD sed on every macOS version and has never included GNU sed by default.
Confirm which version you have:
sed --version 2>&1 | head -1
# GNU: prints "sed (GNU sed) 4.x"
# macOS BSD: prints "sed: illegal option -- -" or exits non-zero
Fix 1 โ Add an empty string after -i (macOS native)
BSD sed needs a suffix argument. An empty string '' tells it to edit in-place with no backup:
# Before (breaks on macOS)
sed -i 's/old/new/g' config.txt
# After (works on macOS)
sed -i '' 's/old/new/g' config.txt
The empty string must be a separate token. Quotes touching -i produce unpredictable results across shells:
# Wrong โ behavior varies by shell
sed -i'' 's/old/new/g' config.txt
# Correct โ explicit space before ''
sed -i '' 's/old/new/g' config.txt
Want a backup copy before editing? Pass an extension instead of the empty string:
# Creates config.txt.bak before modifying config.txt
sed -i '.bak' 's/old/new/g' config.txt
Fix 2 โ Install GNU sed via Homebrew
Constantly porting Linux scripts to macOS gets old fast. Installing GNU sed keeps the syntax consistent across both platforms:
brew install gnu-sed
Homebrew installs it as gsed to avoid shadowing the system binary. Use it directly:
gsed -i 's/old/new/g' config.txt
Prefer typing plain sed? Add the GNU tools directory to your PATH in ~/.zshrc or ~/.bashrc:
export PATH="$(brew --prefix)/opt/gnu-sed/libexec/gnubin:$PATH"
After sourcing your profile, sed --version shows the GNU version. Your Linux scripts run unmodified from that point on.
Fix 3 โ Write cross-platform scripts
No Homebrew? Detect the OS at runtime. A wrapper function handles multi-argument calls cleanly and avoids the quoting pitfalls of storing the command in a variable:
#!/usr/bin/env bash
sed_inplace() {
if [[ "$(uname)" == "Darwin" ]]; then
sed -i '' "$@"
else
sed -i "$@"
fi
}
sed_inplace 's/version=.*/version=2.0/' package.properties
This pattern works in Makefiles, deploy scripts, and CI YAML โ anywhere you can't guarantee which platform will run the code.
Verification
Confirm the edit landed correctly after applying the fix:
# Run the fixed command
sed -i '' 's/foo/bar/g' test.txt
# Check the result
grep 'bar' test.txt
# Confirm no unexpected backup files were created
ls -la test.txt*
Substitution shows up in grep output, no stray backup file unless you wanted one โ you're done.
Common follow-up issues
- Script works locally but breaks in CI: Most CI containers (GitHub Actions, CircleCI) run Linux with GNU sed. Add the
sed_inplacewrapper above so both environments stay in sync. - sed -i '' works in shell but not in a Makefile: Make's default shell is
/bin/sh, where quote handling differs. AddSHELL := /bin/bashat the top of the Makefile, or escape carefully:sed -i $'\'' $'\'' 's/โฆ/โฆ/' file. - Python or Ruby calling subprocess: Pass
-iand''as separate list elements โ not a single string.subprocess.run(['sed', '-i', '', 's/a/b/', 'file'])works; collapsing them into one element does not.

