プロが教えるわが家の防犯対策術!

連投申し訳ありません。
「まるごとPerl」(2006年9月、インプレス刊)という本の「まるごとEncode」という記事に従ってEncodeの勉強をしています。
原記事は UNIX(というか端末コードをUTF-8に出来る環境)ですが、それをWindowsに移植しようとして苦労しています。

#! perl
# list3 -- UTF-8モードとバイトモードの切り替え
#      インデントを表現するために全角空白を使っています

use strict;
use warnings;
binmode STDOUT, ':encoding(cp932)'; # 追加

# ブロックの外側ではUTF-8文字単位で解釈される
use utf8;

{
 # ブロックの外側ではバイトモードが強制される
 use bytes;
 my $text = '漢字、カタカナ、ひらがなの混じったtext';
 print Encode::decode('UTF-8', substr($text, 9, 12)); # カタカナと表示されたい
}

my $text = '漢字、カタカナ、ひらがなの混じったtext';
print substr($text, 3, 4); # カタカナと表示されたい

__END__

というプログラムを実行すると

C:\>list3.pl
カタカナカタカナ

と表示されてほしいのですが、

C:\>list3.pl
Cannot decode string with wide characters at C:/strawberry/perl/lib/Encode.pm li
ne 174.

と表示されます。

use utf8 を後ろにズラして

#! perl
# list3 -- UTF-8モードとバイトモードの切り替え
#      インデントを表現するために全角空白を使っています

use strict;
use warnings;
binmode STDOUT, ':encoding(cp932)'; # 追加


{
 # ブロックの外側ではバイトモードが強制される
 use bytes;
 my $text = '漢字、カタカナ、ひらがなの混じったtext';
 print Encode::decode('UTF-8', substr($text, 9, 12)); # カタカナと表示されたい
}

# ブロックの外側ではUTF-8文字単位で解釈される
use utf8; # 移動
my $text = '漢字、カタカナ、ひらがなの混じったtext';
print substr($text, 3, 4); # カタカナと表示されたい

__END__

とするとうまく動いて

C:\>list3.pl
カタカナカタカナ

と表示されます。

#! perl
# list3 -- UTF-8モードとバイトモードの切り替え
#      インデントを表現するために全角空白を使っています

use strict;
use warnings;
binmode STDOUT, ':encoding(cp932)'; # 追加

# ブロックの外側ではUTF-8文字単位で解釈される
use utf8;
my $text = '漢字、カタカナ、ひらがなの混じったtext'; # 移動
print substr($text, 3, 4); # カタカナと表示されたい # 移動

{
 # ブロックの外側ではバイトモードが強制される
 use bytes;
 my $text = '漢字、カタカナ、ひらがなの混じったtext';
 print Encode::decode('UTF-8', substr($text, 9, 12)); # カタカナと表示されたい
}

__END__

のようにするとやはり最初と同じエラーになりますが、

#! perl
# list3 -- UTF-8モードとバイトモードの切り替え
#      インデントを表現するために全角空白を使っています

use strict;
use warnings;
binmode STDOUT, ':encoding(cp932)'; # 追加

# ブロックの外側ではUTF-8文字単位で解釈される
use utf8;
my $text = '漢字、カタカナ、ひらがなの混じったtext'; # 移動
print substr($text, 3, 4); # カタカナと表示されたい # 移動

no utf8; # 追加

{
 # ブロックの外側ではバイトモードが強制される
 use bytes;
 my $text = '漢字、カタカナ、ひらがなの混じったtext';
 print Encode::decode('UTF-8', substr($text, 9, 12)); # カタカナと表示されたい
}

__END__

だと大丈夫です。

結果として、ブロックの外で use utf8;、中では use bytes; という記事の著者の意図通りに動作しないようで、ブロックの中まで use utf8; が効いているようです・・・。

これは Perl の実装が変わったのでしょうか。
使用しているのは Windows XP Home SP3+Strawberry Perl v5.10.0 です。

A 回答 (8件)

「まるごとPerl」という書籍を所有していないので、そこでどのようなコードが


書かれているのかが分かりませんが、以下のように一旦、変数に格納するとうま
く動作するようです。

試した環境は、

OS:Windows XP SP2
Perl:ActivePerl 5.8.9

です。

use strict;
use warnings;
binmode STDOUT, ':encoding(cp932)'; # 追加

# ブロックの外側ではUTF-8文字単位で解釈される
use utf8;

{
# ブロックの外側ではバイトモードが強制される
use bytes;
my $text = '漢字、カタカナ、ひらがなの混じったtext';

#-- まず一時変数に代入 --------------------------
my $clipped_text = substr($text, 9, 12);
#------------------------------------------------

print Encode::decode('UTF-8', $clipped_text); # カタカナと表示されたい
}

my $text = '漢字、カタカナ、ひらがなの混じったtext';
print substr($text, 3, 4); # カタカナと表示されたい

__END__


下記エラー内容から推測して、

>Cannot decode string with wide characters at
>C:/strawberry/perl/lib/Encode.pm line 174.

内部表現の文字をdecodeしようとして失敗しているのではと思い、一旦、encode
してみました。

use strict;
use warnings;
binmode STDOUT, ':encoding(cp932)'; # 追加

# ブロックの外側ではUTF-8文字単位で解釈される
use utf8;

{
# ブロックの外側ではバイトモードが強制される
use bytes;
my $text = '漢字、カタカナ、ひらがなの混じったtext';
print Encode::decode('UTF-8', Encode::encode('utf8', substr($text, 9, 12))); # カタカナと表示されたい
}

my $text = '漢字、カタカナ、ひらがなの混じったtext';
print substr($text, 3, 4); # カタカナと表示されたい

__END__

そうすると、エラーが出なくなったので、バイトストリームとしてではなく内部
表現のまま処理されているようです。

引数の中でsubstrを使った場合、use bytesが効いていないように見えます。
バグなのか仕様なのか、はたまた私の勘違いなのかはわかりません。

私より詳しい人が解明してくれるでしょう。

この回答への補足

#長すぎるようなので補足=>お礼の順番に書きます。これが前半です。

回答ありがとうございます!
おっしゃるように、私が改造した Windows 改造版ではなく、元の本に書いてあった(たぶんUNIX用の)プログラムをそのまま書き写します。(注釈は私です。)

#! perl
# list4org.pl -- UTF-8モードとバイトモードの切り替え(UTF-8版)

use strict;
use warnings;

# ブロックの外側ではUTF-8文字単位で解釈される
use utf8;

{
 # ブロックの内側ではバイトモードが強制される(ようにしたい)
 use bytes;
 my $text = '漢字、カタカナ、ひらがなの混じったtext';
 print substr($text, 9, 12); # カタカナと表示されたい
}

my $text = '漢字、カタカナ、ひらがなの混じったtext';
binmode STDOUT, ':utf8';
print substr($text, 3, 4); # カタカナと表示されたい
__END__


これを実行すると、なんと、エラーなく実行できます。

C:\>list4org.pl
翫き繧ソ繧ォ繝翫き繧ソ繧ォ繝

こんなことばっかりやっていると、これぐらいの UTF-8 は読めるようになってきますが(ウソ)これは正しく「カタカナカタカナ」と表示されています。

同じ本に書いてあったのですが、Devel::Peek::Dump というのを使うと、変数の UTF-8 フラグが覗けるので、見てみました。

補足日時:2009/11/07 16:49
    • good
    • 0
この回答へのお礼

#これが後半です。

#! perl
# list4org.pl -- UTF-8モードとバイトモードの切り替え(UTF-8版)

use strict;
use warnings;
use Devel::Peek; # <<<<< 追加
use utf8;

{
 use bytes;
 my $text = '漢字、カタカナ、ひらがなの混じったtext';
 Dump $text; <<<< 追加
 print substr($text, 9, 12); # カタカナと表示されたい
}

my $text = '漢字、カタカナ、ひらがなの混じったtext';
Dump $text; <<<< 追加
binmode STDOUT, ':utf8';
print substr($text, 3, 4); # カタカナと表示されたい

すると、上の $text は両方ともUTF8 フラグは立っています。

C:\>list4org.pl
(略)
 FLAGS = (PADMY,POK,pPOK,UTF8)
(略)
 FLAGS = (PADMY,POK,pPOK,UTF8)
(略)

そこで、#1さんのおっしゃるように、中間変数に substr の結果を取ってやってみます。

#! perl
# list4org.pl -- UTF-8モードとバイトモードの切り替え(UTF-8版)
(略)
{
 # ブロックの内側ではバイトモードが強制される(ようにしたい)
 use bytes;
 my $text = '漢字、カタカナ、ひらがなの混じったtext';
 Dump $text;
 my $clipped_text = substr($text, 9, 12);
 Dump $clipped_text;
 print $clipped_text; # カタカナと表示されたい
}

my $text = '漢字、カタカナ、ひらがなの混じったtext';
Dump $text;
binmode STDOUT, ':utf8';
print substr($text, 3, 4); # カタカナと表示されたい

すると、$clipped_text からは UTF8 フラグが倒れています。

C:\>list4org.pl
(略)
 FLAGS = (PADMY,POK,pPOK,UTF8)
(略)
 FLAGS = (PADMY,POK,pPOK)
(略)
 FLAGS = (PADMY,POK,pPOK,UTF8)
(略)

つまり、use bytes; は、my $text = '漢字、カタカナ、ひらがなの混じったtext';という変数定義および代入には効果を持たず、substr 関数および print 関数の挙動に影響を及ぼしているように見えます。(もし printにも影響を持たなければ、ブロックの中の print で Wide character と怒られるハズ)
おかげでグッと真相に迫ったような気がします。
ありがとうございます!

お礼日時:2009/11/07 16:56

print (Encode::is_utf8(substr($text, 3, 3)) ? "substr is ".substr($text, 3, 3)." and is utf8\n" : "substr is ".substr($text, 3, 3)." and is no utf8\n");



これの動作ですが、まず、最初の substr($text, 3, 3) はis_utf8の引数となっ
ているので、文字ベースの処理が行われ、UTF8フラグの立った文字列が返り値と
なります。

したがって、Encode::is_utf8(substr($text, 3, 3) は真となります。

次に、"substr is ".substr($text, 3, 3)." and is utf8\n" が評価されますが、
この中で、substr($text, 3, 3)は引数の中で使われていないので、バイト列処理
が行われます。

なので、文字列切り出しの動作として、3オクテット目から3オクテットで「字」
を返すという動作は正しいです。

substr($text, 3, 3)が、引数の中で使われるように、ラッパー関数(return_str)
をかませた以下を実行すれば、意図した通りの結果が出ると思います。

use strict;
use warnings;
use utf8;
use bytes;
use Encode;

sub return_str { shift }

my $text = '漢字ひらがなカタカナEnglish';

print Encode::is_utf8( substr($text, 3, 3) ) ?
"substr is " . return_str( substr($text, 3, 3) ) . " and is utf8\n" :
"substr is " . return_str( substr($text, 3, 3) ) . " and is no utf8\n";

__END__
    • good
    • 0
この回答へのお礼

あーバカでした、すみません!!!
完全に解明しました。
面白いからperlbugしてみようかな???
みなさん、本当にありがとうございました!!!

お礼日時:2009/11/08 10:26

>substr 関数が use bytes によってバイトベースで機能して(ゼロ始まりの)9


>バイト目から12バイトを取得するが、その文字列としては UTF8 フラグが依然
>として立っているので、さらに decode すると怒られる、ということなのでしょ
>うね。(ですから、encode してから decode すればうまくいった)

後半にちょっと誤解があると思います。

バイト列として処理した場合は、結果の文字列もUTF8フラグが落ちたバイト列とな
ります。

なので、普通なら質問者さんのコードはうまく機能するはずです。

ところが、意図したバイト列処理が行われずに、文字ベースの処理がなされており
(9文字目から12文字分を取得)、結果、UTF8フラグのついた文字列が返り値となっ
ています。

どうやら、substrを関数の引数として使うと、use bytesを無視して文字ベースとし
て処理されるみたいです。

No.6さんがその辺の説明をコードを交えながら詳細にされています。
    • good
    • 0
この回答へのお礼

すみません、やっぱり分かっていませんでした~?

下のコードを実行します。
#! perl
# expBytes.pl -- Enperiment of bytes

use strict;
use warnings;
use utf8;
use bytes;
use Encode;

my $text = '漢字ひらがなカタカナEnglish';

print (Encode::is_utf8($text) ? "text is $text and utf8\n" : "text is $text and no utf8\n");

my $subtext = substr($text, 3, 3);
print (Encode::is_utf8($subtext) ? "subtext is $subtext and utf8\n" : "subtext is $subtext and no utf8\n");

print (Encode::is_utf8(substr($text, 3, 3)) ? "substr is ".substr($text, 3, 3)." and is utf8\n" : "substr is ".substr($text, 3, 3)." and is no utf8\n");

print (Encode::is_utf8(bytes::substr($text, 3, 3)) ? "bytes::substr is ".bytes::substr($text, 3, 3)." and is utf8\n" : "bytes::substr is ".bytes::substr($text, 3, 3)." and is no utf8\n");

__END__

結果はこうなります。(Windows の場合、文字化けするが、リダイレクトしたもの)

text is 漢字ひらがなカタカナEnglish and utf8
subtext is 字 and no utf8
substr is 字 and is utf8
bytes::substr is 字 and is no utf8

この、

print (Encode::is_utf8(substr($text, 3, 3)) ? "substr is ".substr($text, 3, 3)." and is utf8\n" : "substr is ".substr($text, 3, 3)." and is no utf8\n");
の結果が、

substr is 字 and is utf8

なのがおかしい、というお話ですが、substr 関数が Encode::is_utf8 の引数となることによって、UTF8 フラグが立つのはおかしいんですが、文字列切り出しの動作としては3オクテット目から3オクテットで「字」を返している、ということでいいでしょうか?
(文字ベースで動作するのであれば戻り値は「ひらが」?)

たびたび申し訳ありませんが、ご無理のない範囲で返信いただけると幸甚です。
どうもありがとうございます!

お礼日時:2009/11/08 08:48

既に気づかれているようですが、


use bytes; はutf8フラグには影響しません。
文字列リテラルにutf8フラグを付けたくない場合に使うのは
no utf8;です。

###---Sample---###
use strict;
use warnings;
use utf8;
use Encode;

{
use bytes;

my $text = "テスト";
print (Encode::is_utf8($text) ? "text: utf8\n" : "text: noutf8\n"); #->utf8

my $subtext = substr($text, 3, 3);
print (Encode::is_utf8($subtext) ? "subtext: utf8\n" : "subtext: noutf8\n"); #->noutf8

print (Encode::is_utf8(substr($text, 3, 3)) ? "substr: utf8\n" : "substr: noutf8\n"); #->utf8

print (Encode::is_utf8(bytes::substr($text, 3, 3)) ? "substr: utf8\n" : "substr: noutf8\n"); #->noutf8
}

{
no utf8;

my $text = "テスト";
print (Encode::is_utf8($text) ? "text: utf8\n" : "text: noutf8\n"); #->noutf8

my $subtext = substr($text, 3, 3);
print (Encode::is_utf8($subtext) ? "subtext: utf8\n" : "subtext: noutf8\n"); #->noutf8

print (Encode::is_utf8(substr($text, 3, 3)) ? "substr: utf8\n" : "substr: noutf8\n"); #->noutf8
}
###---End---###


use bytes;で何が変わるのかというと、
文字列操作関数が呼ばれるときに、文字列をバイト単位で扱う関数が呼ばれるようになります。
(しかし、既に指摘されているように、引数内で使ったときはなぜかbyte系の関数が呼ばれていないみたいですね。)

bytes - perldoc.perl.org
http://perldoc.perl.org/bytes.html


それから、実験する場合は別として、通常use bytes;は使わない方が良いです。
(例えば、以下の参考ページに使わないようにと書かれています。)

perlunifaq - Perl Unicode FAQ
http://perldoc.jp/docs/perl/5.10.0/perlunifaq.pod

2008-06-25 - daily dayflower
http://d.hatena.ne.jp/dayflower/20080625


文字列をオクテットストリームとして扱いたいならencodeしましょう。
###---Sample---###
use strict;
use warnings;
use utf8;
binmode STDOUT, ':encoding(cp932)';

my $text = '漢字、カタカナ、ひらがなの混じったtext';

my $encoded_text = Encode::encode('UTF-8', $text);
print Encode::decode('UTF-8', substr($encoded_text, 9, 12));
###---End---###
    • good
    • 0
この回答へのお礼

ありがとうございます。
納得しました。
二つの問題(カン違いと、Perlのヘンな挙動)を混乱してました。

リンクされた文書も読みました。
ありがとうございます!

お礼日時:2009/11/08 08:12

use bytesは、use utf8の打ち消しではないので、補足の挙動は正しいと思います。



直に書いたリテラル文字は、use bytes下でもuse utf8のスコープ内であればUTF8
フラグは立つでしょう(No.2さんのコード)。

use bytesは、length, substr, index等の文字列を扱う関数で、UTF8フラグが立っ
ている文字列であってもバイト処理をするように指示するプラグマだと思います。

しかし、質問者さんが提示したコードだと何故かバイト列でなく文字ベースとして
解釈(character semantics)されているようです。

No.4さんの方法だとうまくいくみたいですね。
    • good
    • 0
この回答へのお礼

ありがとうございます!

「use bytesは、use utf8の打ち消しではなくlength, substr, index等の文字列を扱う関数で、UTF8フラグが立っている文字列であってもバイト処理をするように指示するプラグマ」ということですね。

最初に書いた、

{
 # ブロックの外側ではバイトモードが強制される
 use bytes;
 my $text = '漢字、カタカナ、ひらがなの混じったtext';
 print Encode::decode('UTF-8', substr($text, 9, 12)); # カタカナと表示されたい
}

では、substr 関数が use bytes によってバイトベースで機能して(ゼロ始まりの)9バイト目から12バイトを取得するが、その文字列としては UTF8 フラグが依然として立っているので、さらに decode すると怒られる、ということなのでしょうね。
(ですから、encode してから decode すればうまくいった)

ありがとうございます。

お礼日時:2009/11/07 19:03

原因はわかりませんが、何が起きているか分かったような気がします。



> print Encode::decode('UTF-8', substr($text, 9, 12));
で、サブルーチンの引数として、substr で切り出した値を
渡していますが、これが use bytes で扱われていません。

回避方法としては、サブルーチンに渡す前に切り出す
my $sub_text = substr($text, 9, 12);
print Encode::decode('UTF-8', $sub_text);

明示的に bytes::substr を使う
print Encode::decode('UTF-8', bytes::substr($text, 9, 12));
という方法があります。
    • good
    • 0
この回答へのお礼

ありがとうございます!
そういうことですね~

お礼日時:2009/11/08 08:09

No.2 です。


何かボケてました。無視してください。
    • good
    • 0
この回答へのお礼

ありがとうございます!
いや、おっしゃることが正しくて、最初のエラーが出るプログラムで use utf8; フラグはプログラム全体に影響を及ぼしているようです。

Windows のコマンドプロンプトで UTF-8 が正しく表示されるか、UNIX 機がウチにあればこんな苦労はしないのだが・・・。

(いや、Windows に移植しようとするからこういう怪奇現象にあって楽しいのかな! ^o^)

引き続き、よろしくお願いします!

お礼日時:2009/11/07 16:59

use utf8 の効果を消すのは no utf8 じゃないですか。


#!/usr/bin/perl
use strict;
use warnings;
use Data::Dumper qw( Dumper );
use utf8;
my $str_utf8 = 'あいうえお';
my $str_noutf8;
my $str_bytes;
{
no utf8;
$str_noutf8 = 'あいうえお';
}
{
use bytes;
$str_bytes = 'あいうえお';
}
print Dumper( [ $str_utf8, #=> "\x{3042}\x{3044}\x{3046}\x{3048}\x{304a}",
$str_noutf8, #=> 'あいうえお',
$str_bytes ] #=> "\x{3042}\x{3044}\x{3046}\x{3048}\x{304a}"
);
exit;
    • good
    • 0

お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!