macOSで`sed -i`実行時の「extra characters at the end of L command」エラーを修正する

beginner🍎 macOS2026-06-01| macOS(全バージョン)、BSD sed(組み込み)、ターミナル / bash / zsh

Error Message

sed: 1: "filename": extra characters at the end of L command
#sed#ターミナル#bash#macos-bsd

TL;DR

macOS には GNU sed ではなく BSD sed が同梱されています。BSD sed では -i の後に明示的なバックアップ拡張子が必要です。バックアップを作成しない場合は空文字列を渡してください:

# macOS の修正: -i の後に '' を追加
sed -i '' 's/foo/bar/g' filename.txt

または Homebrew 経由で GNU sed をインストールし、すでに慣れている Linux の構文で gsed を使用してください。

エラーの全文

sed: 1: "filename": extra characters at the end of L command

Linux では完璧に動作する sed -i コマンドを Mac で実行すると失敗します。エラーには「L コマンド」が言及されていますが、実際には使用していません。わかりにくいメッセージですが、sed に互換性のない2つの系統が存在することを知れば、根本原因は理解できます。

根本原因

GNU sed と BSD sed では -i フラグの扱いが異なります:

  • GNU sed(Linux、多くの CI サーバー):-i はオプションのサフィックスを受け付けます。sed -i 's/…/…/' file と書くと、GNU sed はサフィックスが省略されたと判断し、バックアップなしでインプレース編集を行います。
  • BSD sed(macOS 標準):-i には常にサフィックスが必要です。sed -i 's/…/…/' file と書くと、BSD sed は置換パターンをバックアップ拡張子として解釈します。そしてターゲットファイルを開き、その内容を sed スクリプトとして実行しようとします。ファイルの先頭の文字が sed コマンドとして誤読され、しばしば BSD sed が後ろにゴミのある l(小文字のL)コマンドと解釈するものに行き着きます。

あの奇妙な「extra characters at the end of L command」というメッセージの意味は:ファイルを sed スクリプトとして開いたが、解析できなかったということです。Apple はすべての macOS バージョンに BSD sed を同梱しており、GNU sed をデフォルトで含めたことは一度もありません。

使用しているバージョンを確認するには:

sed --version 2>&1 | head -1
# GNU: "sed (GNU sed) 4.x" と表示される
# macOS BSD: "sed: illegal option -- -" と表示されるか、非ゼロで終了する

修正方法 1 — -i の後に空文字列を追加する(macOS ネイティブ)

BSD sed にはサフィックス引数が必要です。空文字列 '' を渡すことで、バックアップなしのインプレース編集を指定できます:

# 修正前(macOS では失敗する)
sed -i 's/old/new/g' config.txt

# 修正後(macOS で動作する)
sed -i '' 's/old/new/g' config.txt

空文字列は独立したトークンである必要があります。-i にクォートをくっつけると、シェルによって予測不能な結果になります:

# 誤り — シェルによって動作が異なる
sed -i'' 's/old/new/g' config.txt

# 正しい — '' の前に明示的なスペースを入れる
sed -i '' 's/old/new/g' config.txt

編集前にバックアップを残したい場合は、空文字列の代わりに拡張子を渡してください:

# config.txt を変更する前に config.txt.bak を作成する
sed -i '.bak' 's/old/new/g' config.txt

修正方法 2 — Homebrew 経由で GNU sed をインストールする

Linux スクリプトを macOS 向けに何度も修正するのは手間がかかります。GNU sed をインストールすれば、両方のプラットフォームで構文を統一できます:

brew install gnu-sed

Homebrew はシステムのバイナリを上書きしないよう gsed としてインストールします。直接使用するには:

gsed -i 's/old/new/g' config.txt

普通に sed と打ちたい場合は、~/.zshrc または ~/.bashrc の PATH に GNU ツールのディレクトリを追加してください:

export PATH="$(brew --prefix)/opt/gnu-sed/libexec/gnubin:$PATH"

プロファイルを再読み込みすると、sed --version が GNU バージョンを表示します。それ以降は Linux のスクリプトをそのまま実行できます。

修正方法 3 — クロスプラットフォームなスクリプトを書く

Homebrew が使えない場合は、実行時に OS を検出してください。ラッパー関数を使えば複数引数の呼び出しをきれいに処理でき、変数にコマンドを格納する際のクォートの落とし穴も回避できます:

#!/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

このパターンは Makefile、デプロイスクリプト、CI の YAML など、どのプラットフォームで実行されるかを保証できない場所でも使用できます。

確認

修正を適用した後、編集が正しく反映されているか確認してください:

# 修正済みコマンドを実行する
sed -i '' 's/foo/bar/g' test.txt

# 結果を確認する
grep 'bar' test.txt

# 意図しないバックアップファイルが作成されていないか確認する
ls -la test.txt*

置換結果が grep の出力に表示され、不要なバックアップファイルがなければ(意図して作成した場合を除き)完了です。

よくある追加の問題

  • **スクリプトがローカルでは動作するが CI で失敗する:**多くの CI コンテナ(GitHub Actions、CircleCI)は GNU sed を搭載した Linux で動作します。上記の sed_inplace ラッパーを追加して、両方の環境で同期を保ってください。
  • **sed -i '' がシェルでは動作するが Makefile では動作しない:**Make のデフォルトシェルは /bin/sh で、クォートの扱いが異なります。Makefile の先頭に SHELL := /bin/bash を追加するか、丁寧にエスケープしてください:sed -i $'\'' $'\'' 's/…/…/' file
  • Python や Ruby がサブプロセスを呼び出す場合:-i'' を1つの文字列としてではなく、リストの別々の要素として渡してください。subprocess.run(['sed', '-i', '', 's/a/b/', 'file']) は動作しますが、1つの要素にまとめると動作しません。

Related Error Notes