Ruby におけるバイナリ文字列に対する正規表現マッチング

Ruby 1.9 系において、バイナリ文字列 (ASCII-8BIT の String オブジェクト) に対する正規表現マッチングをさせる方法について記します。 Ruby 1.9 系において URL デコードを行う際などに役に立ちます。

正規表現リテラルn オプション

正規表現リテラルの後ろに n と書くことで、その正規表現をバイナリ扱い (エンコーディング ASCII-8BIT) にすることができます。 ASCII-8BIT は ASCII 互換なので、ASCII 文字列を中身に書くことができます。

regexp = /abcd/n

しかし、ASCII に含まれないものを中に入れるとエラーになります。

regexp = /abcdあ/n

この結果は以下のようになります。

SyntaxError: (irb):1: regexp encoding option 'n' differs from source encoding 'UTF-8'
/.../n has a non escaped non ASCII character in non ASCII-8BIT script: /abcdあ/
        from /usr/local/bin/irb:12:in `<main>'

じゃあどうすればいいかというと、16 進表記のエスケープ文字 ((正式名称がよくわからないのでこう書いてます。。 "\xXX" 形式のアレです。)) を使います。

regexp = /abcd\xEE\xF0/n

URI デコードを行う関数

まあ ASCII-8BIT の正規表現はあんまり使い道はないと思いますが、例えば URI デコードなど、文字列のエンコードやデコードの際には役に立ちます。 ここに URI デコードの例を書いておきます。

##
# URI デコードを行う
#
# デコード対象になるのは文字列に含まれる '%XX' 形式の全ての文字.
# デコード対象の文字が妥当かどうか (16 進数表記に合致するか) やデコード後の文字列の妥当性
# (文字エンコーディングが正しいか) などのチェックはしない. 
# デコード後の文字列のエンコーディングは第 2 引数で指定. (デフォルトは UTF-8)
def uridec( str, enc = Encoding::UTF_8 )
  # 与えられた文字列を ASCII-8BIT 扱いにし、ASCII-8BIT の正規表現でマッチングする
  str.force_encoding(Encoding::ASCII_8BIT).gsub( /%../n ) do |s|
    [s[1,2].to_i(16)].pack('C')
  end.force_encoding(enc) # 指定のエンコーディングにして返す
end