Macでsedが期待通り動かない時の対処法あれこれ

Mac のターミナルで sed コマンドを利用した際、期待通りに動かないことがあったので調べてみたメモ。

sed コマンドとは

sed(Stream EDitor)は、入力として受け取ったテキストに対して、変換など処理を行うコマンド。 端的にいうと文字列の加工ができるコマンドである。

例えば、マッチした文字列の一部を置換したい時などに使う。以下の例では、-e の後に置換するルールを記述しており、WorldJapan に置換している。

% echo "Hello, World\!" | sed -e "s/World/Japan/g"
Hello, Japan!

パイプで入力を受け取る以外には、ファイルを入力として受け取り処理を行うこともできる(処理結果は標準出力されるのみのため、ファイルに出力したい場合は後述の -i オプションを利用する)。

% echo "Hello, World\!" > hello.txt             
% sed -e s/World/Japan/g hello.txt 
Hello, Japan!

Mac で使える sed について

Mac でも標準で sed コマンドが用意されている*1

% which sed
/usr/bin/sed
% sed --version
sed: illegal option -- -
usage: sed script [-Ealn] [-i extension] [file ...]
       sed [-Ealn] [-i extension] [-e script] ... [-f script_file] ... [file ...]

ただし、Mac で標準になっている sedBSD 系のもの(POSIX sed)だが、他の Linux ディストリビューションでは GNUsed が利用されていることが多い。 両者はオプションの仕様や対応している正規表現に微妙に違いがあるため、同じコマンドを実行しても結果が一致しない場合がある。

個人の主観にはなるが、Google などで検索して出てくる情報は主に後者の GNU sed のものが多く、Mac で実行した場合は期待した出力にならないものがあるので注意が必要。

対処法

主な対処法は以下の2つ。

  1. GNU sed をインストールして使う
  2. 対応している記法に修正して使う

1. GNU sed をインストールして使う

GNUsed は、Homebrew で gsed をインストールすると利用可能。

% brew install gsed

通常のコマンドで GNUsed を使いたい場合は、alias で指定してしまうとよい。

% alias sed='gsed'

2. 対応している記法に修正して使う

こちらに関してはどこまで差異があるのか全て把握できているわけではないが、現時点までに Macsed で実行してハマったものを記載しておく。

-i オプションを使うとエラーになってしまう

-i オプションは、入力として与えたファイルの内容を書き換えるオプションであるが、以下のように実行するとエラーになってしまう。

% cat hello.txt
Hello, World!
% sed -i s/World/Japan/g hello.txt 
sed: 1: "hello.txt": extra characters at the end of h command

Macsed で実行したい場合は、-i の後の引数にバックアップとして作成されるファイルの拡張子を指定する必要がある。sed コマンドで指定したファイルの内容は上書きされ、変更前の内容がバックアップファイルとして残される。

% sed -i .bak s/World/Japan/g hello.txt
% cat hello.txt
Hello, Japan!
% cat hello.txt.bak 
Hello, World!

バックアップファイルを作成せずに上書きしたい場合は、-i の後の引数に空文字を指定するとよい。

% sed -i "" s/World/Japan/g hello.txt
% cat hello.txt
Hello, Japan!

正規表現+ が期待通りにマッチしない

正規表現では + を指定することで、「直前の表現を1回以上繰り返したもの」にマッチさせることができる。 しかし、Macsed だと + が利用できず、使用しても期待通りに動いてくれない場合がある。

以下の例では B が1個以上連続している箇所を B\+*2 でマッチさせ、DDD に書き換えようとしているが、Macsed では置換が起こっていない(GNU sed では置換される)。

% echo "AAA BBB BBBBB CCC" | sed -e "s/B\+/DDD/g"
AAA BBB BBBBB CCC

% echo "AAA BBB BBBBB CCC" | gsed -e "s/B\+/DDD/g"
AAA DDD DDD CCC

+ ではなく、繰り返し回数指定の {MIN, MAX}*3 の表現は利用できる。 MIN = 1, MAX = (なし) として利用すると同じような条件でマッチさせることができるので、こちらを使う。

% echo "AAA BBB BBBBB CCC" | sed -e "s/B\{1,\}/DDD/g" 
AAA DDD DDD CCC

まとめ

どのような差異があるかは全て把握するのは難しそうであるが、上記以外の差異が見つかったらまた記事にしていく予定。 ただ、目的の操作をしたい場合に情報量が多いのは GNUsed かと思われるため、普段使う上では gsed を入れておくのが良いかと思う。

*1:--version オプションは存在しないが、usege を表示させるために指定している

*2:+ は特殊文字として扱うために \ を先に書いておく必要がある

*3:{ と } も特殊文字として扱うために \ を先に書いておく必要がある