Bash, Perl, Ruby, Pythonで正規表現置換

投稿者: | 2012年10月23日

前回のC++/boost.NETに加え、各種インタプリタ言語でも同じ動作をするスクリプトを書いてみました。

シェルスクリプト(bash)

シェルスクリプトはそもそもグルー言語ですので、他のコマンドを呼び出して処理をすることが一般的です。下のサンプルではsedコマンドで置換処理をしています。bashだと正規表現マッチングは可能なので、ガリガリとスクリプトを書けばsedを使わずに実現できるかもしれません。

[bash]

!/usr/bin/env bash

[ $# -ne 2 ] && exit 1
cat | sed -e "s/$1/$2/g"
exit 0
[/bash]

リプレイスメント置換文字は、sedの書式になります。グループ指示子はダラー($)ではなくバックスラッシュ(\)を用いています。

Perl

Perlの場合、正規表現による文字列操作は、関数でもクラスでもなく構文として組み込まれています。その為、コマンドライン引数から渡された「$1」のようなリプレイスメントの展開方法に、若干の工夫が必要です。

[perl]

!/usr/bin/env perl

exit 1 if ($#ARGV != 1);
for (<stdin>) {
eval "s/$ARGV[0]/$ARGV[1]/g" && print;

s/$ARGV[0]/$ARGV[1]/gee && print; # これではダメ

}
exit 0;
[/perl]

リプレイスメントが格納されている$ARGV[1]は展開されると、例えば「$1$2」という文字列になります。この展開後の文字列をリプレイスメントとして正規表現置換の処理に投げたいのですが、正規表現置換処理を行なった後に変数展開されるようでリプレイスメントのグループ指示子としての$nが正規表現置換処理に伝わりません。そこで、正規表現置換処理を行う前に$ARGV[1]を展開させるべく、eval関数に投げています。これにより、正規表現置換処理が評価される時点でリプレイスメントは「$ARGV[1]」ではなく「$1$2」という文字列として解釈され、指示子が正しく伝わるようです。
検索パターンに加え、リプレイスメント文字列も変数で持つという処理は多々あるはずなので、もっとスマートな方法が準備されているのかもしれませんが、性質さえ知っていれば公式を知らずとも期待する処理が可能である、まさに”TMTOWTDI / There’s More Than One Way To Do It(やり方はひとつじゃない)”という設計思想を持つPerlらしい実装です。理に適った挙動は見ていて気持ちがいいです。

Python

Pythonでは、シェルスクリプトやPerlと違って正規表現機能はクラス(re)として提供されています。下のサンプルではreの静的関数を叩いていますが、プリコンパイルしたオブジェクトとしても利用できたと思います。

[python]

!/usr/bin/env python

import sys
import re
v = sys.argv
if len(v) != 3: quit(1)
for line in sys.stdin:
print re.sub(v[1], v[2], line),
quit(0)
[/python]

リプレイスメントのグループ指示子は、sedと同じくバックスラッシュです。Cやシェルライクなエスケープ文字という捉え方をするなら、バックスラッシュがしっくり来ますね。(といっても、ダラーの場合でも同じく「シェルライク」ですけど :D )

ちなみに、len() がオブジェクトのメソッドではなく独立した関数になっているのは、評価対象(上の例の場合は v )が null オブジェクトでも、null チェックなしで利用できるようにする為だとどこかで読みました。Python の場合は空文字列(len=0)は null オブジェクトになるんですね。

Ruby

Rubyの場合は、シェルスクリプトを除いた他のどの言語よりもマニアックな感じがしますが、よく見てみると一番「ナチュラル」に理解し易い構文です。これだけの構文で比較するのはアレかもしれませんが、これだけの構文だけでここまで革新的な要素をたくさん見てとれるのは、rubyだからこそと言った感じでしょうか。ファイルディスクリプタまでオブジェクトであり、イテレータをせおっているあたり、カワイイです。

[ruby]

!/usr/bin/env ruby

if ARGV.size != 2 then exit 1 end
STDIN.each_line do |line|
puts line.gsub(/#{ARGV[0]}/, ARGV[1])
end
exit 0
[/ruby]

Rubyの何十倍もPythonのコードは書いていますが、Pythonは「VB.NETよりも使いものになる、綺麗なVB.NET的な何か」というイメージが拭えません。いいところ、悪いところの両方を知った上で使いこなせていけたらいいなあ、と思いました。

See also

コメントを残す

メールアドレスが公開されることはありません。