質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.35%
Perl

Perlは多目的に使用される実用性が高い動的プログラミング言語のひとつです。

JSON

JSON(JavaScript Object Notation)は軽量なデータ記述言語の1つである。構文はJavaScriptをベースとしていますが、JavaScriptに限定されたものではなく、様々なソフトウェアやプログラミング言語間におけるデータの受け渡しが行えるように設計されています。

Q&A

解決済

2回答

3764閲覧

perl jsonで日本語文字化け

peter_kes

総合スコア14

Perl

Perlは多目的に使用される実用性が高い動的プログラミング言語のひとつです。

JSON

JSON(JavaScript Object Notation)は軽量なデータ記述言語の1つである。構文はJavaScriptをベースとしていますが、JavaScriptに限定されたものではなく、様々なソフトウェアやプログラミング言語間におけるデータの受け渡しが行えるように設計されています。

0グッド

2クリップ

投稿2018/05/21 09:36

編集2018/05/23 08:07

Perlで日本語をふくむデータをJson出力すると文字化けが起こります。
色々解決法をためしましたが、直りません。どこがおかしいんでしょうか?

perl

1 2#!/usr/bin/perl 3 4print "Content-Type: text/html; \n\n"; 5 6# use module 7use Data::Dumper; 8use Time::Local; 9use DateTime; 10use JSON; 11use utf8; 12use Encode; 13use URI::Escape; 14 15use strict; 16use warnings; 17 18my $data; 19#get data from unity 20if ($ENV{'REQUEST_METHOD'} eq 'POST') 21{ 22 read(STDIN, $data, $ENV{'CONTENT_LENGTH'}); 23} #end of if 24else 25{ 26 $data = $ENV{'QUERY_STRING'}; 27}#end of else 28 29my $message; 30my $client_number; 31(my $message_tag,$message, my $client_number_tag, $client_number) = split(/=|&/,$data); 32 33 34$message = uri_unescape($message); 35 36my $file_name = "message.json"; 37if(! -e $file_name) #ファイルがなければ、UTF8文字コードのjsonファイル作成 38{ 39 open my $fh,">", $file_name 40 or die "$file_name error $!"; 41 close $fh; 42} 43 44my $dt = DateTime->now( time_zone=>'local');#現時刻取得 45my $ymdhms = $dt->datetime; 46 47my $json; #jsonファイルから全てのメッセージデータを取得 48{ 49 local $/; #Enable 'slurp' mode 50 open my $fh,"<", $file_name; 51 $json = <$fh>; 52 close $fh; 53} 54 55 56my @array = (); 57 58if($json){ 59 60my $json_in = decode_json($json); ##新しいメッセージを現在のメッセージデータの最後に追加するために存在するデータをあらかじめ配列に入れておく(Jsonファイルに新しいデータを追加するのはどうやったらいいのかわかっていない....) 61 62 foreach my $item( @$json_in) { 63 push @array, $item; 64 65 } 66} 67 68#新メッセージ 69my $new_data = { 70 'message' => $message, 71 'date' => $ymdhms, 72 'client_number' => $client_number 73 }; 74 75 76 push @array, $new_data;#過去のすべてのメッセージが入っている配列に追加 77 78 my $json_out = encode_json (\@array); #配列をJson化する 79 80 open my $fh, ">", $file_name 81 or die "$file_name error $!"; 82 print $fh $json_out ; 83 close $fh; 84 85 print ("Success"); 86 87exit;

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答2

0

ベストアンサー

ascii=>1 がマズいのではないでしょうか。

追記:

ちょっとやってみました。質問文に合ったスクリプトを少し改変し、日本語文字列をスクリプトの中の定数として与え、もう一方でutf8のテキストファイルから読み込み、まとめてJSONで吐き出すスクリプトにしてみました。問題なく出力できました。

perl

1#!/usr/bin/perl 2 3use strict; 4use utf8; 5use JSON; 6 7binmode STDOUT, ":utf8"; 8 9my $message="テスト文字列"; 10my($last_id, $ymdhms, $client_number) = ( "ID0", "170522001600", "1"); # 適当 11my @array; 12 13open(my $fhi, "<:utf8", "input_utf8.txt") or die; 14open(my $fho, ">:utf8", "output.json") or die; 15 16while(<$fhi>){ 17 chomp; 18 my $message2=$_; 19 20 my $new_data = { 21 'id' => $last_id, 22 'message' => $message, 23 'message2' => $message2, 24 'date' => $ymdhms, 25 'client_number' => $client_number 26 }; 27 push @array, $new_data; 28} 29 30my $json_out = to_json \@array, {pretty => 1}; 31print $json_out,"\n"; # コンソールへ 32print {$fho} $json_out, "\n"; # ファイルへ

perlで日本語処理する場合の要諦については過去に何度か回答しているのでご覧ください。
https://teratail.com/questions/103103
https://teratail.com/questions/122419
https://teratail.com/questions/105443
だまされたと思って、これらの回答のルールに従って処理し、それ以外の余計なことをやらないようにしてみてください。必ずうまくいくはずです。

個人ブログなどで、生半可な理解のもとにたまたまうまくいった方法を紹介している例がいろいろありますが、ほとんどは混乱のもとになるだけで率直なところ役に立ちません。あまり見ないほうがいいです。

さらに追記:結局perlで日本語を扱うにはどうすればいいのか総論を書きます

大原則

  1. 入力されたデータは速やかに「内部コード」化(デコード)されなければならない。
  2. 検索・加工は内部コード化した文字列についてのみ実行されなければならない。
  3. 出力するデータはその寸前に必ず「外部コード」化(エンコード)されなければならない。

「内部コード」とは、正確にはflagged utf8というものらしいが、とりあえずそこは気にしなくていい。

この原則に合致しない形でのエンコード・デコードを生半可な理解のまま絶対試みないこと。

ただし、他のライブラリを通じてデータを受け入れる時、上記に合致しないタイミングでエンコード・デコードされた文字列データを受け取らざるを得ないことがある。この場合は例外的にfrom_to()やdecode(), encode()などの関数を使わなければならないが、通常はこれらの出番はないと思っていい。

ひとたび内部コード化された文字列は、正規表現による検索・置換、substrでの切り出し・置換、その他全ての処理がいわゆる半角文字列と同じ要領で可能となる。utf8では全角文字は3バイトだからなどとややこしいことは一切考える必要なし。

内部コード化(デコード)の方法

  1. スクリプト内に書かれた文字列(リテラル値) - ここでいうリテラル値とは、たとえば$a="あいうえお"における「あいうえお」の部分を指す。対策: スクリプト冒頭にuse utf8;を置く。
  2. 標準入力からキーボードをたたいて入力される文字列 - 対策: binmode STDIN, ":utf8";を置く。
  3. ファイルから取り込まれる文字列 - 対策: 3パラメータ形式のopen関数を使う。たとえば、open(my $filehandle, "<:utf8", input.txt); 普通にopenしておいて、あとからbinmode $filehandle ":utf8";としてもいいが、回りくどい。

外部コード化(エンコード)の方法

  1. ファイルへの出力 - 対策: 3パラメータ形式のopen関数を使う。open(my $filehandle, ">:utf8", output.txt);
  2. 標準出力 - 対策: binmode STDOUT, ":utf8";
  3. 標準エラー出力 - 対策: binmode STDERR, ":utf8";

use utf8についての誤解

この構文が初めて採用されたときの仕様が今日のそれとは違うものだったこともあり、いまだに不正確な情報が独り歩きしているようだが、くれぐれもややこしく考えないこと。

use utf8は「当該スクリプト中の文字列は内部コード化して取り扱え」と指示するもので、対ファイル・コンソールの入出力には関係しない。つまりuse utf8したからファイルのデコードは必要ないとかエンコードは必要ないとか、そういう議論は全部間違っている。

スクリプト中にマルチバイト文字列(漢字カタカナヒラガナその他いわゆる全角文字)を書かないならば、use utf8を書く必要はない(書いてもとくに支障はないが)。

投稿2018/05/21 09:48

編集2018/05/23 19:43
KojiDoi

総合スコア13692

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

peter_kes

2018/05/21 11:06

御回答ありがとうございます。 my $json_out = to_json \@array, {utf8=>1, pretty => 1}; などにしてみたり、ascii=>1部分無しにしてみても文字化けしてしまいます。
KojiDoi

2018/05/21 14:50

print $messageとしたときは正常に表示できますか?
peter_kes

2018/05/22 08:14

わあ!!コードありがとうございます!! すごく勉強になります!!!! この$message なんですが、 <unity側C#> WWWForm www = new WWWForm (); www.AddField ("messsage", ”日本語"); WWW result = new WWW (url, www); yield return result; <CGI側> my $data; #get data from unity if ($ENV{'REQUEST_METHOD'} eq 'POST') { read(STDIN, $data, $ENV{'CONTENT_LENGTH'}); } #end of if else { $data = $ENV{'QUERY_STRING'}; }#end of else my $message; (my $message_tag,$message) = split(/=|&/,$data); print $message でこの時点で既に文字化け状態%e3%81%82%e3 になっていることが判明しました!! wwwで渡している時点ですでに文字化けしていることになります。書いていただいたコードと結果のとおり、Jsonの問題ではないようです。 重要なヒントいただきありがとうございます!!
peter_kes

2018/05/22 12:42

%82%e3の文字化けはuri_unescapeで解決しました。 これはチャットプログラムなんですが、 1つ目のメッセージは文字化けせずに保存されますが、1つ目以降アペンドする形でメッセージを追加すると文字化けになります。ファイルの読み込み書き込み部分に>:encoding(UTF-8) <:encoding(UTF-8) どちらもいれるか入れないか色々パターンを試してみましたが、1つ目がダメになるパターン、3つ目からだめになるなど色々なパターンの文字化けがどうしてもおこってしまいます。内部コードについてもようやく意味がわかってきたのですが、use utf8;をいれておけばいいということではないようです。なにがまちがっているんでしょうか?
KojiDoi

2018/05/22 19:41

問題が入力元なのか入力後の操作なのか出力時の問題なのかをはっきりさせましょう。まずwhile(<>)の直後でprint $_;してみて正常な出力が得られるか見ましょう。 気になる点としては、 (1) encode_jsonを使うなら、出力用ハンドルのopenで">:utf8"を指定してはいけません。encodeしたものをさらにencodeすることになってしまいます。 (2) print "Content-Type: text/html; charset=Shift_JIS\n\n"; これは明らかにダメです。 (3) 「一つ目のメッセージ」と「アペンドする形」がどう違うのか、質問文のコードを見ても理解できませんでした。いずれにせよ、データの流れを変に複雑化させると、decode/encodeが出来てなかったり逆に2回やってしまったりということをやらかしてしまいますので、できるだけシンプル化する方向で見直したほうが良いでしょう。
peter_kes

2018/05/23 06:34

御回答ありがとうございます。 (1)やはり2重エンコーディングになっているのですね。 (2)これはprint "Content-Type: text/html; charset=uft8\n\n が正しいいでしょうか? (3) はい、多重文字化けの流れがjsonでの文字化けの問題もあいまってわからないのでまずjsonにしないでtxtで保存して試してみます。
peter_kes

2018/05/24 07:03

use utf8は「当該スクリプト中の文字列は内部コード化して取り扱え」と指示する これが鉄則ですね!!  外部へのファイルの書き出しで、3パラメータ形式のopen関数open(my $filehandle, "<:utf8", input.txt); を使ったり標準出力でbinmode STDOUT, ":utf8と明記したり、インプットアウトプットする時もしっかり明記しないといけない点もわかりました!! ここまでわかりやすくまとめていただき、すごく助かりました!! たぶんこれで忘れません。 本当にありがとうございました!(^^)!
guest

0

perl

1#!/usr/bin/perl 2 3print "Content-Type: text/html; \n\n"; 4 5# use module 6use Data::Dumper; 7use Time::Local; 8use DateTime; 9use JSON; 10use utf8; 11use Encode; 12 13use strict; 14use warnings; 15 16use URI::Escape; 17 18my $data; 19#get data from unity 20if ($ENV{'REQUEST_METHOD'} eq 'POST') 21{ 22 read(STDIN, $data, $ENV{'CONTENT_LENGTH'}); 23} #end of if 24else 25{ 26 $data = $ENV{'QUERY_STRING'}; 27}#end of else 28 29my $message; 30my $client_number; 31(my $message_tag,$message, my $client_number_tag, $client_number) = split(/=|&/,$data); 32 33$message = uri_unescape($message); 34 35 my $file_name = "message.json"; 36 if(! -e $file_name) 37 { 38 open my $fh,">", $file_name 39 or die "$file_name error $!"; 40 close $fh; 41 } 42 43 my $dt = DateTime->now( time_zone=>'local'); 44 my $ymdhms = $dt->datetime; 45 46 47my $json; 48{ 49 local $/; #Enable 'slurp' mode 50 open my $fh,"<", $file_name; 51 $json = <$fh>; 52 close $fh; 53} 54 55my $last_id = 0; 56 57my @array = (); 58 59if($json){ 60 61 my $json_in = from_json($json); 62 63 foreach my $item( @$json_in) { 64 print Dumper($item); 65 push @array, $item; 66 $last_id = $item->{id}; 67 } 68 } 69 70 $last_id = $last_id + 1; 71 72 my $new_data = { 73 'id' => $last_id, 74 'message' => $message, 75 'date' => $ymdhms, 76 'client_number' => $client_number 77 }; 78 79 push @array, $new_data; 80 81 my $json_out = to_json \@array, { pretty => 1}; 82 83 open my $fh, ">", $file_name 84 or die "$file_name error $!"; 85 print $fh $json_out ; 86 close $fh; 87 88 print ("Success"); 89 90exit; 91

これが最終的に文字化けが解決したコードになります。
解決方法は以下の様になります。
1.回答者さんが教えてくれたリンクでperlの日本語文字の扱い、エンコードデコード内部データ化について学習する
2.%cd%cdaf が出たら、 URI::Escapeを使ってアンエスケープする。
3.perlのファイルはuft8に最初からしておく初級学習サイトでおなじないとかよく書いてありますが、print "Content-Type: text/html; charset=Shift_JIS\n\n はいらない。
4.use utf8;をいれて、ファイルの読み込み書き込みに"<:encoding(UTF-8)"と記述しない。(多重エンコードになってしまう)
5.use utf8;をいれると eocode_json decode_json ではなく(多重エンコードになる 参照http://orange-factory.com/dnf/perlmodule_json.html ) to_json from_json を使用すること
同じ問題が起こったかたがいれば参考にしてください。
御回答者様、沢山ヒントを出していただきありがとうございました。すごく助かりました。

投稿2018/05/23 09:14

peter_kes

総合スコア14

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

KojiDoi

2018/05/23 09:53

残念ですがそのuse utf8の理解は間違っています。 のちほどまとめて追記します。
peter_kes

2018/05/23 11:53

そうですか(;゚д゚)アッ 追記助かります!!   お時間ございましたらで大丈夫ですので、ほかの見る人が同じく間違った情報で理解しないためによろしければ追記していただけたらと思います。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.35%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問