2017年1月10日火曜日

[unix,perl,正規表現] perlでsed,grep,awkライクに

sedでダブルクォートで括られてる("ABC")ような文字列の置換をしようとしていたんだけど、うまくいかなかった。

$ sed -e 's/"[^"]+"/"xxx"/g'
"foo"
"foo"
"f+"
"xxx"

これだと、ダブルクォートの中が以外の任意の1文字と"+"の場合しかマッチない。
ダブルクォートの中には必ず1文字以上ある事を意図して"+"記号を使っていたんだけど、どうもsedではこれを特殊記号として扱ってくれないらしい。
なので、以下だとうまくいく。

$ sed -e 's/"[^"][^"]*"/"xxx"/g'
""
""
"foo"
"xxx"

どうしてこんなことになるかというと。。。

一般に使われている正規表現を大きく分けると以下の3つになる。
※他にも正規表現の方言はたくさんあるのであくまで、大きく分けるとという話。

BRE - Basic Regular Expressions
ERE - Extended Regular Expressions
PCRE - Perl Compatible Regular Expressions

それぞれの規格の参照先は以下。

The Open Group Base Specifications Issue 7
IEEE Std 1003.1-2008, 2016 Edition

9.3 Basic Regular Expressions

9.4 Extended Regular Expressions

Perl regular expressions man page.
PCRE - Perl Compatible Regular Expressions

この中で標準的なUnixコマンドでEREが使えるのはgrepで"-e"オプションを指定した場合で、他はほとんどBREしか使えない。
じゃあ、後方参照と選択演算子を1回の検索で使いたかったら、どうしようか。

※BREは後方参照をサポートしているのに、EREは後方参照をサポートしていないというヘンな規格だ。 つまり、EREはBREの上位互換ではないということになる。

そんな場合はPCREを使えばいいってことになるし、その場合は素直にPerlを使おう。
Perlでは"-n"や"-p"オプションを使えば、標準入力もしくは指定されたファイルの1行毎に対して、作用するプログラムを記述することができる。

例えば、catと同じ事をしたければ、以下のようになる。

$ perl -lpe '' filename

なんでこれがcat相当になるかは、"-MO=Deparse"オプションをつけると確認できる。

$ perl -MO=Deparse -lpe ''
BEGIN { $/ = "\n"; $\ = "\n"; }
LINE: while (defined($_ = )) {
    chomp $_;
}
continue {
    die "-p destination: $!\n" unless print $_;
}
-e syntax OK

ワンライナーが実際にどのようなスクリプトになるかを確認したいときは"-MO=Deparse"を使うといいだろう。
"-n"オプションだと"-p"と違い、自動で毎行printがない。

$ perl -MO=Deparse -lne ''
BEGIN { $/ = "\n"; $\ = "\n"; }
LINE: while (defined($_ = <ARGV>)) {
    chomp $_;
}
-e syntax OK

これを使えば、PCREでgrep,sed,awk相当のことができる。

まず、sedライクな置換がしたければ、以下のようになる。

$ perl -lpe 's/foo/FOO/g'
foobarfoo
FOObarFOO

grepのように該当行のみの抽出がしたければ、こうしよう。

$ perl -lne 'print $_ if $_ =~ /bar/'
foo
bar
bar

$ #マッチしない行のみ抽出するバージョン
$ perl -lne 'print $_ if $_ !~ /bar/'

awkのように分割されたフィールドを扱いたければ、"-a"オプションを使う。
そうすると、分割されたフィールドが @F 配列に格納される。

$ perl -alne 'print $F[0]'
ABC DEF
ABC

"-F"オプションを使えば、フィールドセパレータの文字を変えることもできる。

$ perl -alne 'print $F[0]' -F,
ABC,DEF
ABC

0 件のコメント:

コメントを投稿