@kyanny's blog

My life. Opinions are my own.

'\xf1\xd9' みたいな16進数文字列を変数展開?して「騙」って漢字にしたい

うまく説明できないけど、 #3. のブロックが pass して欲しい。

#!/usr/local/bin/perl
# $Id$
use strict;
use warnings;

use Test::More;
plan 'no_plan';
use Encode;

my $kanji = '騙';

{ #1. pass
    my $hex = "\xf1\xd9";
    is $hex, to_euc($kanji);
};

{ #2. fail
    my $hex = '\xf1\xd9';
    is $hex, to_euc($kanji);
};

{ #3. fail...
    my $hex = '\xf1\xd9';
    # ここでうまいこと $hex = "\xf1\xd9" 的なことをしたい
    is $hex, to_euc($kanji);
};

sub to_euc {
    encode('euc-jp', decode('utf-8', shift));
}

1;

__END__


なんかよくわからないけど "\xf1\xd9" と、16進数文字列リテラルをダブるクォートで囲むと変数展開っていうか評価されて euc-jp でエンコードされた「騙」って漢字になる。けどこれをシングルクォートで囲っちゃうと '\xf1\xd9' という記号とアルファベット8文字からなる文字列になる(当たり前だ)。

で、そのシングルクォートで囲われたほうの文字列をもう一度変数展開っていうか評価して(このあたり理解が浅いので言葉遣いも怪しい)、 euc-jp でエンコードされた「騙」って漢字にしたい。のだけどうまくいかず。プログラムの中で正規表現で抜き出して変数に入れた文字列が '\xf1\xd9' みたいになっちゃってるのでそこから先が・・・。 "$hex" って囲い直してもダメだし eval "$hex" とかもエラーだし。 s/(?:\\x(.*?))/pack("H2", $1)/eg とか s/(?:\\x(.*?))/hex($1)/eg とかもダメだった。どうしたらいいんだろう。

$ perl -e 'print "\xf1\xd9"'

とかすると漢字になってるっぽいことがわかる(UTF-8 なロケール/端末なので化けるけど)。

追記

id:clouder さんに教えてもらいました。ダブルクォートで囲った文字列を文字列連結でつくって、さらにそれを eval すると再評価される、と。なんだかすごいなぁ。こういうのは B とか使って込み入ったところをいじらなきゃできないのかなーと思っていたけどもっとずっと簡単にできた。

is eval( '"' . $hex . '"' ), to_euc($kanji);

さらに追記

はてブコメントで別なツッコミももらった。ありがとうございます。正規表現がヘンだったのか。

2008年12月28日kitsperlpackがだめだったのは (.*?) のため($1 eq '' になる)。 s/\\x([\dA-Fa-f]{2})/pack('H2', $1)/eg でいいはず。

use strict;
use warnings;
use Perl6::Say;
say "\xf1\xd9";

my $hex = '\xf1\xd9';
say $hex;
$hex =~ s/\\x([\dA-fa-f]{2})/pack('H2', $1)/eg;
say $hex;

ちゃんとうまいこといった。