普段sedコマンドは置換のためにしか使わず、たまに「マッチした行の削除ってどうやるんだっけ?」となって調べてまた忘れるということを繰り返していました。
いつも理屈抜きで方法のみ調べていたため、置換 s/regexp/str/g
と削除 /regexp/d
とで規則性がありそうな無さそうなという曖昧な認識でした。
もう少しsed力を上げたいと思ってマニュアルを読んだら、そもそも正規表現の使い方がひとつではないことを知りました。
要点
- sedで指定する操作は、「コマンド」の前に「アドレス」を付けられます
- アドレスに合致する行がコマンド処理の対象です
- アドレス無しなら全ての行が対象となります
-
s/regexp/str/g
の場合は**s
がコマンド**(その後ろはオプション)です- 正規表現は置換する部分文字列を表します
-
/regexp/d
の場合はd
がコマンドでその前の/regexp/
はアドレスです- 正規表現はコマンドを実行する行を表します
→ 組み合わせると /regexp1/s/regexp2/str/g
といったこともでき、行指定と置換とで正規表現を分けられます。
マニュアルの内容
manだと情報が少なかったため、 https://www.gnu.org/software/sed/manual/html_node/index.html (あるいは info sed
)を見ていきます。複雑な処理も作れそうですが、今回は簡単なものに絞ります。
基本の構文
(3.1 sed script overview)
sed
commands follow this syntax:[addr]X[options]
X is a single-letter
sed
command.[addr]
is an optional line address. If[addr]
is specified, the command X will be executed only on the matched lines.[addr]
can be a single line number, a regular expression, or a range of lines (see sed addresses). Additional[options]
are used for some sed commands.
例えば s/hello/world/
は、 s
がコマンド(X)を表し、後ろの /hello/world/
はオプションということになるようです。
一方で /^foo/d
だと、 d
がコマンドなので、その前の /^foo/
はアドレスです。アドレスが指定されるとコマンドは該当行のみに対して実行します。(ちなみに d
は削除なので、アドレスを指定しないと全ての行が消えます。)
アドレスの詳細
0〜2つの引数を、カンマ区切りで指定します。最大個数はコマンドによって異なりますが、置換 s
や削除 d
なら2個まで可能です。最後(コマンドの直前)に !
を付けると、処理対象の行を反転させられます。
[addr1[,addr2]][!]
引数の形式は「行番号」と「正規表現」があります。正規表現は /regexp/
とスラッシュで囲うほかに、 \%regexp%
などと任意の文字で囲うこともできます。(目印として最初にバックスラッシュが必要です。)
1引数なら、行番号はその1行だけ、正規表現はgrepのようにマッチする行全てを表します。
2引数では行の範囲(開始行と終了行)を表します。行番号はほぼそのままの意味なので簡単ですが、正規表現は「検索開始行以降で最初にマッチした行」となります。終了行は含むのかなど細かい振る舞いを覚えるには、第2引数が正規表現だと範囲が2行以上になるという性質が役立ちそうです。
(4.4 Range Addresses)
If the second address is a regexp, then checking for the ending match will start with the line following the line which matched the first address: a range will always span at least two lines (except of course if the input stream ends).
※ このマニュアルのページでは読み取れなかったのですが、第2引数のマッチが成功したらまた第1引数のマッチに戻るようです。(フリップフロップ)
例えば以下のコマンドは、Rubyプログラムの __END__
の行から最終行(行番号 $
)までを削除します。
sed -e '/^__END__$/,$d' script.rb
s
コマンド
(3.3 The s Command)
The
s
command (as in substitute) is probably the most important insed
and has a lot of different options. The syntax of thes
command is 's/regexp/replacement/flags
'.
単独でページが用意されているほど主要なコマンドです。とはいえ多くのエディタやプログラミング言語でも同様のことができるので、よく使う機能に関しては難しくありません。メモ書き程度に触れておきます。
- 正規表現
- 置換したい文字列にマッチするよう指定します
- デフォルトは基本正規表現(BRE)を使うようになっていて、
-E
オプションで現代的な拡張正規表現(ERE)に変えられます
https://www.gnu.org/software/sed/manual/html_node/BRE-vs-ERE.html
- 置換文字列
- キャプチャした文字列(正規表現の
\(...\)
にマッチした文字列)は、\1
,\2
, … のように指定して利用できます - 正規表現全体にマッチした文字列は、
&
で利用できます(\0
ではありません)- 単なる文字 "&" を使いたいときはエスケープが必要です
- キャプチャした文字列(正規表現の
- フラグ(任意)
-
g
は行内でマッチした文字列を全て置換します -
2
などと数字を指定すると、行内でn番目にマッチした文字列だけを置換します(デフォルトは1
ということになります)
-
- その他
- 区切り文字は
/
以外でも大丈夫です
アドレスと異なりs
が目印になるので、先頭にバックスラッシュは付けません
- 区切り文字は
(おまけ) y
コマンド
マニュアルを見ていて知ったのですが、 y/src/dst/
というのもあります。スラッシュで区切っているので s
と瓜二つですが、これは文字を置換するだけのもので正規表現ではありません。trコマンドと同じような働きです。
利用例
Capybara-RSpec のスクリプトで「一部のマッチャについて wait
オプションを追加したい」ということがありました。
# 元のスクリプト
expect(page).not_to have_css "#foobar"
expect(page).not_to have_selector "#hogehoge"
# ↓
# have_selector だけオプション追加したい
expect(page).not_to have_css "#foobar"
expect(page).not_to have_selector "#hogehoge", wait: 15
(幸い引数を括弧で囲っていなかったので)行末に文字列を追加するだけなのですが、置換したい部分を表す正規表現が他の行でもマッチすると、期待しない行まで変更してしまいます。
sed -e 's/$/, wait: 15/' sample.rb
# have_css の行にも追加されてしまう
今までは、置換したい行全体を一旦マッチさせ、キャプチャした部分文字列( &
や \1
など)を使いながら置換後の行を作っていました。
sed -e 's/.*have_selector.*/&, wait: 15/' sample.rb
アドレスを使うと、対象行の指定と置換とで正規表現を分けられます。アドレスはgrepの要領で行い( -v
相当も !
で可能)、置換は対象外の行を気にしなくていいので単純化できます。
# grep 'have_selector' が抽出する行のみ置換
sed -e '/have_selector/s/$/, wait: 15/' sample.rb