今回は、sedコマンドでよく使われる置換「s」と正規表現の具体的な使い方を説明しよう。コマンドについては、「BSD sed」と「GNU sed」の両方の実行結果を示す。「sed」がBSD sedのコマンドで、「gsed」がGNU sedだ。
置換「s」と正規表現&参照
まず、正規表現を使って置換対象を指定してみよう。以下の記述では「基本正規表現」を使っている。
% echo abc012ABC | sed 's/[0-9][0-9]*//'
abcABC
% echo abc012ABC | gsed 's/[0-9][0-9]*//'
abcABC
%
「[0-9][0-9]*」の部分が、基本正規表現を使って記述した置換対象だ。これは、「1文字以上の数字の羅列」を意味している。
置換後の文字列として何も指定していないので、正規表現に一致した数字の羅列は削除される。なお、これは次のようにフラグ「g」を使って記述しても同じ結果を得ることができる。
% echo abc012ABC | sed 's/[0-9]//g'
abcABC
% echo abc012ABC | gsed 's/[0-9]//g'
abcABC
%
gを指定すると最初の置換対象だけでなく、それ以降の置換対象に対しても置換を実施する。つまり、置換対象の文字列「abc012ABC」のうち、まず最初に「0-9」に合致した「0」を置換(この場合、置換対象が指定されていないので削除)し、次に合致した「1」も置換(削除)し……と1文字ずつ処理するわけだ。
また、正規表現に一致した文字列そのものは、置換後の文字列としても利用できる。その場合、「&」を使用する。言葉で説明してもわかりにくいので、次の結果をご覧いただきたい。
% echo abc012ABC | sed 's/[0-9][0-9]*/ "&" /'
abc "012" ABC
% echo abc012ABC | gsed 's/[0-9][0-9]*/ "&" /'
abc "012" ABC
%
「abc012ABC」の文字列中、正規表現で表された「[0-9][0-9]*」に一致する文字列が「012」だ。置換後の文字列では、「&」が「012」になって置換されていることがおわかりいただけるだろう。
さらに細かく制御する機能も用意されている。&は全ての置換対象に一致するが、置換対象の一部を対象とする表記方法もある。それには、置換対象の一部を「(」と「)」で囲い、置換後の文字列には「\1」を含める。
何となく想像がつくかと思うが、「\1」が「(」と「)」で囲った内容に一致した文字列に展開されることになる。そろそろ説明が複雑になってきたので、とりあえず次の処理結果をご覧いただきたい。
% echo abc012ABC | sed 's/\([0-9][0-9]*\)/ "\1" /'
abc "012" ABC
% echo abc012ABC | gsed 's/\([0-9][0-9]*\)/ "\1" /'
abc "012" ABC
%
ここまで来ると、もはやコマンドが呪文のように感じられるかもしれない。しかし、この辺りはよく使う記述なので、慣れてもらうしかない。今は意味不明だとしても、レベルアップすればそのうち使えるようになるはずだ。
「\1」という表記でなんとなくイヤな予感がしている方もいるのではないだろうか。その勘は当たっている。「\2」とか「\3」といった書き方ができる。「(」と「)」で囲む対象を増やすと、それを順次「\1」「\2」……という風に指定できるわけだ。これは、次のように使うことができる。
% echo abc012ABC | sed 's/\([a-z][a-z]*\)[0-9][0-9]*\([A-Z][A-Z]*\)/"\1" "\2"/'
"abc" "ABC"
% echo abc012ABC | gsed 's/\([a-z][a-z]*\)[0-9][0-9]*\([A-Z][A-Z]*\)/"\1" "\2"/'
"abc" "ABC"
%
いよいよ呪文じみてきて、途方に暮れている人もいるかもしれない。さらに追い打ちをかけるようだが、この「\1」といった表記は、置換後ではなく、置換対象の指定にも使用できる。例えば、こんな感じだ。
% echo abc012cbabced | sed 's/\([a-z][a-z]*\).*\(\1\).*/"\1" "\2" /'
"abc" "abc"
% echo abc012cbabced | gsed 's/\([a-z][a-z]*\).*\(\1\).*/"\1" "\2" /'
"abc" "abc"
%
大丈夫。今はわからなくても大丈夫だ。「どうも、そういったことができるらしい」くらいに思っておいてもらえればよい。ただ、sedコマンドが持つこの機能は非常に便利なので、いずれは理解して使えるようになっていただきたい。
拡張正規表現を使ってみよう
上記のコマンドは、基本正規表現を使って記述している。これを「拡張正規表現」を使って書き換えると、次のようになる。
% echo abc012ABC | sed -E 's/([0-9]+)/ "\1" /'
abc "012" ABC
% echo abc012ABC | gsed -r 's/([0-9]+)/ "\1" /'
abc "012" ABC
%
コマンドが「sed」から「sed -E」、「gsed」から「gsed -r」となったほか、「(」と「)」が「(」と「)」になり、「[0-9][0-9]*が[0-9]+」になっている。全体的にシュッとスマートになった印象だ。
また、参照を2つ使った表記を拡張正規表現を使って書き換えると次のようになる。
% echo abc012ABC | sed -E 's/([a-z]+)[0-9]+([A-Z]+)/"\1" "\2"/'
"abc" "ABC"
% echo abc012ABC | gsed -r 's/([a-z]+)[0-9]+([A-Z]+)/"\1" "\2"/'
"abc" "ABC"
%
さらに、置換対象にも参照の記述を入れたものを拡張正規表現で書いてみると、次のようになる。
% echo abc012cbabced | sed -E 's/([a-z]+).+(\1).+/"\1" "\2" /'
"abc" "1"
% echo abc012cbabced | gsed -r 's/([a-z]+).+(\1).+/"\1" "\2" /'
"abc" "abc"
%
ご覧のように、この書き方では、BSD sedとGNU sedで実行結果が異なってしまうようだ。拡張正規表現を使う場合には、この部分の機能は使わないほうがよいだろう。
とは言え、全体としては拡張正規表現のほうがスマートな記述ができる傾向にあるように思う。GNU sedには、「-E」というオプションがあり、これは「-r」と同じ機能を持っている。つまり、これを使えば、「sed -E」と「gsed -E」という書き方でオプションを統一させることができるのだ。見た目のスマートさを取るのであれば、拡張正規表現を使ったほうがよいかもしれない。
もし今、どちらも「謎の呪文」にしか見えないとしても、気に病まないでほしい。そのうち読めるようになる、きっと!