• ベストアンサー

{ブロック}の外でのみ use utf8; したいのだが・・・

連投申し訳ありません。 「まるごと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 です。

質問者が選んだベストアンサー

  • ベストアンサー
  • ryu_chan
  • ベストアンサー率37% (69/186)
回答No.1

「まるごと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が効いていないように見えます。 バグなのか仕様なのか、はたまた私の勘違いなのかはわかりません。 私より詳しい人が解明してくれるでしょう。

TYWalker
質問者

お礼

#これが後半です。 #! 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 と怒られるハズ) おかげでグッと真相に迫ったような気がします。 ありがとうございます!

TYWalker
質問者

補足

#長すぎるようなので補足=>お礼の順番に書きます。これが前半です。 回答ありがとうございます! おっしゃるように、私が改造した 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 フラグが覗けるので、見てみました。

その他の回答 (7)

  • ryu_chan
  • ベストアンサー率37% (69/186)
回答No.8

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__

TYWalker
質問者

お礼

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

  • ryu_chan
  • ベストアンサー率37% (69/186)
回答No.7

>substr 関数が use bytes によってバイトベースで機能して(ゼロ始まりの)9 >バイト目から12バイトを取得するが、その文字列としては UTF8 フラグが依然 >として立っているので、さらに decode すると怒られる、ということなのでしょ >うね。(ですから、encode してから decode すればうまくいった) 後半にちょっと誤解があると思います。 バイト列として処理した場合は、結果の文字列もUTF8フラグが落ちたバイト列とな ります。 なので、普通なら質問者さんのコードはうまく機能するはずです。 ところが、意図したバイト列処理が行われずに、文字ベースの処理がなされており (9文字目から12文字分を取得)、結果、UTF8フラグのついた文字列が返り値となっ ています。 どうやら、substrを関数の引数として使うと、use bytesを無視して文字ベースとし て処理されるみたいです。 No.6さんがその辺の説明をコードを交えながら詳細にされています。

TYWalker
質問者

お礼

すみません、やっぱり分かっていませんでした~? 下のコードを実行します。 #! 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オクテットで「字」を返している、ということでいいでしょうか? (文字ベースで動作するのであれば戻り値は「ひらが」?) たびたび申し訳ありませんが、ご無理のない範囲で返信いただけると幸甚です。 どうもありがとうございます!

  • Gotthold
  • ベストアンサー率47% (396/832)
回答No.6

既に気づかれているようですが、 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---###

TYWalker
質問者

お礼

ありがとうございます。 納得しました。 二つの問題(カン違いと、Perlのヘンな挙動)を混乱してました。 リンクされた文書も読みました。 ありがとうございます!

  • ryu_chan
  • ベストアンサー率37% (69/186)
回答No.5

use bytesは、use utf8の打ち消しではないので、補足の挙動は正しいと思います。 直に書いたリテラル文字は、use bytes下でもuse utf8のスコープ内であればUTF8 フラグは立つでしょう(No.2さんのコード)。 use bytesは、length, substr, index等の文字列を扱う関数で、UTF8フラグが立っ ている文字列であってもバイト処理をするように指示するプラグマだと思います。 しかし、質問者さんが提示したコードだと何故かバイト列でなく文字ベースとして 解釈(character semantics)されているようです。 No.4さんの方法だとうまくいくみたいですね。

TYWalker
質問者

お礼

ありがとうございます! 「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 すればうまくいった) ありがとうございます。

  • t-okura
  • ベストアンサー率75% (253/335)
回答No.4

原因はわかりませんが、何が起きているか分かったような気がします。 > 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)); という方法があります。

TYWalker
質問者

お礼

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

  • t-okura
  • ベストアンサー率75% (253/335)
回答No.3

No.2 です。 何かボケてました。無視してください。

TYWalker
質問者

お礼

ありがとうございます! いや、おっしゃることが正しくて、最初のエラーが出るプログラムで use utf8; フラグはプログラム全体に影響を及ぼしているようです。 Windows のコマンドプロンプトで UTF-8 が正しく表示されるか、UNIX 機がウチにあればこんな苦労はしないのだが・・・。 (いや、Windows に移植しようとするからこういう怪奇現象にあって楽しいのかな! ^o^) 引き続き、よろしくお願いします!

  • t-okura
  • ベストアンサー率75% (253/335)
回答No.2

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;