回答編集履歴

2

総論追記

2018/05/23 19:43

投稿

KojiDoi
KojiDoi

スコア13671

test CHANGED
@@ -93,3 +93,73 @@
93
93
 
94
94
 
95
95
  個人ブログなどで、生半可な理解のもとにたまたまうまくいった方法を紹介している例がいろいろありますが、ほとんどは混乱のもとになるだけで率直なところ役に立ちません。あまり見ないほうがいいです。
96
+
97
+
98
+
99
+ # さらに追記:結局perlで日本語を扱うにはどうすればいいのか総論を書きます
100
+
101
+
102
+
103
+ ## 大原則
104
+
105
+
106
+
107
+ 0. 入力されたデータは速やかに「内部コード」化(デコード)されなければならない。
108
+
109
+ 0. 検索・加工は内部コード化した文字列についてのみ実行されなければならない。
110
+
111
+ 0. 出力するデータはその寸前に必ず「外部コード」化(エンコード)されなければならない。
112
+
113
+
114
+
115
+ 「内部コード」とは、正確にはflagged utf8というものらしいが、とりあえずそこは気にしなくていい。
116
+
117
+
118
+
119
+ この原則に合致しない形でのエンコード・デコードを生半可な理解のまま絶対試みないこと。
120
+
121
+
122
+
123
+ ただし、他のライブラリを通じてデータを受け入れる時、上記に合致しないタイミングでエンコード・デコードされた文字列データを受け取らざるを得ないことがある。この場合は例外的にfrom_to()やdecode(), encode()などの関数を使わなければならないが、通常はこれらの出番はないと思っていい。
124
+
125
+
126
+
127
+ ひとたび内部コード化された文字列は、正規表現による検索・置換、substrでの切り出し・置換、その他全ての処理がいわゆる半角文字列と同じ要領で可能となる。utf8では全角文字は3バイトだからなどとややこしいことは一切考える必要なし。
128
+
129
+
130
+
131
+ ## 内部コード化(デコード)の方法
132
+
133
+
134
+
135
+ 0. スクリプト内に書かれた文字列(リテラル値) - ここでいうリテラル値とは、たとえば$a="あいうえお"における「あいうえお」の部分を指す。対策: スクリプト冒頭に`use utf8;`を置く。
136
+
137
+ 0. 標準入力からキーボードをたたいて入力される文字列 - 対策: `binmode STDIN, ":utf8";`を置く。
138
+
139
+ 0. ファイルから取り込まれる文字列 - 対策: 3パラメータ形式のopen関数を使う。たとえば、`open(my $filehandle, "<:utf8", input.txt);` 普通にopenしておいて、あとから`binmode $filehandle ":utf8";`としてもいいが、回りくどい。
140
+
141
+
142
+
143
+ ## 外部コード化(エンコード)の方法
144
+
145
+ 0. ファイルへの出力 - 対策: 3パラメータ形式のopen関数を使う。`open(my $filehandle, ">:utf8", output.txt);`
146
+
147
+ 0. 標準出力 - 対策: binmode STDOUT, ":utf8";
148
+
149
+ 0. 標準エラー出力 - 対策: binmode STDERR, ":utf8";
150
+
151
+
152
+
153
+ ## use utf8についての誤解
154
+
155
+
156
+
157
+ この構文が初めて採用されたときの仕様が今日のそれとは違うものだったこともあり、いまだに不正確な情報が独り歩きしているようだが、くれぐれもややこしく考えないこと。
158
+
159
+
160
+
161
+ use utf8は「当該スクリプト中の文字列は内部コード化して取り扱え」と指示するもので、対ファイル・コンソールの入出力には関係しない。つまりuse utf8したからファイルのデコードは必要ないとかエンコードは必要ないとか、そういう議論は全部間違っている。
162
+
163
+
164
+
165
+ スクリプト中にマルチバイト文字列(漢字カタカナヒラガナその他いわゆる全角文字)を書かないならば、use utf8を書く必要はない(書いてもとくに支障はないが)。

1

追記

2018/05/23 19:43

投稿

KojiDoi
KojiDoi

スコア13671

test CHANGED
@@ -1 +1,95 @@
1
1
  ascii=>1 がマズいのではないでしょうか。
2
+
3
+
4
+
5
+ 追記:
6
+
7
+
8
+
9
+ ちょっとやってみました。質問文に合ったスクリプトを少し改変し、日本語文字列をスクリプトの中の定数として与え、もう一方でutf8のテキストファイルから読み込み、まとめてJSONで吐き出すスクリプトにしてみました。問題なく出力できました。
10
+
11
+
12
+
13
+ ```perl
14
+
15
+ #!/usr/bin/perl
16
+
17
+
18
+
19
+ use strict;
20
+
21
+ use utf8;
22
+
23
+ use JSON;
24
+
25
+
26
+
27
+ binmode STDOUT, ":utf8";
28
+
29
+
30
+
31
+ my $message="テスト文字列";
32
+
33
+ my($last_id, $ymdhms, $client_number) = ( "ID0", "170522001600", "1"); # 適当
34
+
35
+ my @array;
36
+
37
+
38
+
39
+ open(my $fhi, "<:utf8", "input_utf8.txt") or die;
40
+
41
+ open(my $fho, ">:utf8", "output.json") or die;
42
+
43
+
44
+
45
+ while(<$fhi>){
46
+
47
+ chomp;
48
+
49
+ my $message2=$_;
50
+
51
+
52
+
53
+ my $new_data = {
54
+
55
+ 'id' => $last_id,
56
+
57
+ 'message' => $message,
58
+
59
+ 'message2' => $message2,
60
+
61
+ 'date' => $ymdhms,
62
+
63
+ 'client_number' => $client_number
64
+
65
+ };
66
+
67
+ push @array, $new_data;
68
+
69
+ }
70
+
71
+
72
+
73
+ my $json_out = to_json \@array, {pretty => 1};
74
+
75
+ print $json_out,"\n"; # コンソールへ
76
+
77
+ print {$fho} $json_out, "\n"; # ファイルへ
78
+
79
+ ```
80
+
81
+
82
+
83
+ perlで日本語処理する場合の要諦については過去に何度か回答しているのでご覧ください。
84
+
85
+ https://teratail.com/questions/103103
86
+
87
+ https://teratail.com/questions/122419
88
+
89
+ https://teratail.com/questions/105443
90
+
91
+ だまされたと思って、これらの回答のルールに従って処理し、それ以外の**余計なことをやらない**ようにしてみてください。必ずうまくいくはずです。
92
+
93
+
94
+
95
+ 個人ブログなどで、生半可な理解のもとにたまたまうまくいった方法を紹介している例がいろいろありますが、ほとんどは混乱のもとになるだけで率直なところ役に立ちません。あまり見ないほうがいいです。