tohokuaikiのチラシの裏

技術的ネタとか。

sedって難しいよね。俺たちは雰囲気でsedを使っている。

やりたいこと

複数のテキストファイルの中の文字列を別の文字列に一括置換したい。

俺たちは雰囲気でsedを使っている。

sedを20年使ってても(年に2~3回ペースなこともあって)使うのが難しい。何が難しいって、オプションとかフラグとか正規表現とか。そのたびに検索して事例見つけてコピペしている。

よく

$sed -i -e "s/foo/bar/g" file.txt

とかあって、-iオプションは上書き、g修飾子はグローバルマッチ(ファイル内のすべて置換)で、「あぁ、file.txtのなかのfooをbarに全部置き換えて上書きするんだな」ってわかるけど、じゃあ、-eオプションって何?/foo/bar/の前のsって何?必要?って言われるとわからん。俺たちは雰囲気でsedを使っている。

サクッとsedの代わりになるものとかないかなーって【脱sed】いい加減シェルスクリプトで文字列をsedで置換するなんてやめよう #Linux - Qiitaとか、trコマンドとか見てみるけど、なんかイマイチ理解できなかったりファイル上書きできなかったり*1で結局sedに戻ってくることになるので、ちゃんと自分用にメモしておく。

よく使うものだけ

例を挙げながらメモ。file.txtとして

foo bar
foo bar baz
foo bar baz qux

/foo/bar/
{foo} {bar} {baz}
@foo@bar@baz@qux@

を用意した。これをさっきの

$sed -i -e "s/foo/bar/g" file.txt

に通すと

bar bar
bar bar baz
bar bar baz qux

/bar/bar/
{bar} {bar} {baz}
@bar@bar@baz@qux@

になる。fooが全部barになっている。

-eオプション

【 sed 】コマンド(基礎編その4)――文字列を置き換える/置換した行を出力する:Linux基本コマンドTips(56) - @ITでは

 --expression=スクリプト  スクリプト(コマンド)を追加する

ってあるけど、スクリプト(コマンド)ってなんやねん?というと、この場合は"s/foo/bar/g"。2つつなげたい場合は-eオプションを使う。同時にbazをzzzにしたい場合は

$sed -i -e "s/foo/bar/g" -e "s/baz/zzz/g" file.txt

で、

bar bar
bar bar zzz
bar bar zzz qux

/bar/bar/
{bar} {bar} {zzz}
@bar@bar@zzz@qux@

となる。

なので、スクリプト(コマンド)が1つだけの場合は、-eオプションは不要。

s/foo/bar/のsとはスクリプト(コマンド)

スクリプト(コマンド)とは置換以外にもいろいろある。 例えばaスクリプト(コマンド)は「テキストの追加」を行う。

$sed "a add text" file.txt

とやると、

foo bar
add text
foo bar baz
add text
foo bar baz qux
add text

add text
/foo/bar/
add text
{foo} {bar} {baz}
add text
@foo@bar@baz@qux@
add text

とすべての行の次にadd textが加えられる。

sスクリプト(コマンド)は「テキストの置換」になる。

正規表現

sed正規表現は、POSIXの基本正規表現。簡単な置換ならこれで十分。

fo*でfooもfooooooも置換

$ sed -e "s/@fo*/bar/g" file.txt
foo bar
foo bar baz
foo bar baz qux

/foo/bar/
{foo} {bar} {baz}
bar@bar@baz@qux@

エスケープのバックスラッシュ

$ sed -e "s/foo\//bar/g" file.txt
foo bar
foo bar baz
foo bar baz qux

/barbar/
{foo} {bar} {baz}
@foo@bar@baz@qux@

行頭宣言の^

$ sed -e "s/^foo/bar/g" file.txt
bar bar
bar bar baz
bar bar baz qux

/foo/bar/
{foo} {bar} {baz}
@foo@bar@baz@qux@

拡張したPOSIX正規表現だとエスケープが不要になるみたい。sedで躓くLinux(POSIX)の正規表現|jig.jp engineers

だが、JavaScriptPerlなどのPCRE 形式の正規表現ではない。

空行を残さないで改行ごと置換する

sedはラインエディタなので行ごとにスクリプト(コマンド)を実行する。なので、1行丸ごと消したい場合に空行が残ってしまう。

$ sed -e "s/foo bar baz qux//" file.txt
foo bar
foo bar baz


/foo/bar/
{foo} {bar} {baz}
@foo@bar@baz@qux@

-zオプション

zオプションは改行ごとにスクリプト(コマンド)を実行するのではなく、Null文字ごとにスクリプト(コマンド)を実行する。NULL文字は\0で表現できる。普通のテキストファイル内にそんなものは無い。

改行コードは\n。Windowsだと\r\n

$ sed -z -e "s/foo bar baz qux\n//" file.txt
foo bar
foo bar baz

/foo/bar/
{foo} {bar} {baz}
@foo@bar@baz@qux@

…が、zオプションは2018年にリリースされたversion4.2.2で導入されたらしく、むしろまだsedに新機能入るのか!!!という驚きの方があるが、手元のCentOS6では動かなかった。そんな古いOS使うなって。

$ sed --version
GNU sed 4.2.1版

Copyright (C) 2009 Free Software Foundation, Inc.

*1:grep+xargsで一括上書きしたいんよ