• ベストアンサー

マルチバイト文字列の部分取得方法は?

EUCコードでスクリプトを書いています。 substr() で部分文字列を取得すると、2バイト文字がきれいに丸まりません。 $s = "12月23日はABCの誕生日"; print substr($s, 0, 14); "12月23日はABC" または "12月23日はABCの" を取得したい時は、どうすればよいのでしょうか?

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

  • ベストアンサー
  • BLUEPIXY
  • ベストアンサー率50% (3003/5914)
回答No.6

use encoding 'euc-jp'; $s = "12月23日はABCの誕生日"; print my_substr($s, 0, 14); sub my_substr($$$){ /* my_substr は、部分文字列を指定したバイト数幅で取り出す ** $str は、全角半角混在の文字列 ** $start は、取り出す"文字"位置 ** $len は、取り出す数(文字数でなくてバイト数) ** 取り出すバイト数を越えない(サンプルの'の'は含まない) */ my ($str, $start, $len) = @_; my $work=""; my $count=0; my $i; my $strlen=length $str; for($i=$start;$i<$strlen;$i++){ my $c = substr($str,$i,1); $count += ord($c) < 256 ? 1 : 2 ; last if $count > $len; $work .= $c; } return $work; }

xespr
質問者

お礼

さすが BLUEPIXY さん! 完璧です!お見事です。 素晴らしく大成功でした! ありがとうございました!!

その他の回答 (5)

  • t140
  • ベストアンサー率39% (59/150)
回答No.5

$n= 14; $s=~/^((?:\x8E[\xA1-\xDF]|\x8F?[\xA1-\xFE][\xA1-\xFE]|[\x09-\x7E]){0,$n})/; print $1,"\n"; #14文字以内にマッチなら上記のようになりますが、 #14文字ぴったりのときだけマッチさせるなら {$n} に書き換えてください。

xespr
質問者

補足

ありがとうございます。ダメでした。 $s = "12月23日はABCの誕生日"; foreach $i ( 3 .. 9 ) { print "----\n\$i:$i\n"; # $s2 = my_substr($s, 0, $i); $s2 = substr($s, 0, $i); # $s2 =~ s/(\x8e|\x8f.|[^\x00-\x7F\x8e\x8f])$//; # $s2 =~ s/(\x8e|\x8f.?|[^\x00-\x7F\x8e\x8f])$//; $s2 =~ /^((?:\x8E[\xA1-\xDF]|\x8F?[\xA1-\xFE][\xA1-\xFE]|[\x09-\x7E]){0, $i})/; # $n -> $i に変えてます print "\$s2:$s2" . "\n"; } __END__ の実行結果 ---- $i:3 $s2:12・ ---- $i:4 $s2:12月 ---- $i:5 $s2:12月2 ---- $i:6 $s2:12月23 ---- $i:7 $s2:12月23搭 ---- $i:8 $s2:12月23日 ---- $i:9 $s2:12月23日k

noname#25358
noname#25358
回答No.4

 #2です。  んー。こっちじゃ特に問題なく出てますね……。(連結しても)  s/(\x8e|\x8f.?|[^\x00-\x7F\x8e\x8f])$//;  ↑これでどうですか?

xespr
質問者

補足

すみません。連結するしないは、私の確認ミスのようです。 色々試しすぎて混乱してしまったようです。すみませんでした。 どちらにしてもバグが表れるようです。 $s = "12月23日はABCの誕生日"; foreach $i ( 3 .. 25 ) { print "----\n\$i:$i\n"; $s2 = substr($s, 0, $i); # $s2 =~ s/(\x8e|\x8f.|[^\x00-\x7F\x8e\x8f])$//; $s2 =~ s/(\x8e|\x8f.?|[^\x00-\x7F\x8e\x8f])$//; # print "\$s2:$s2", "\n"; print "\$s2:$s2" . "\n"; } __END__

  • bgbg
  • ベストアンサー率53% (94/175)
回答No.3

use encodingしましょう。 実行時に内部的にUTF-8に変換され、1バイト文字も2バイト文字も1文字として扱えるようになります。 use encoding 'euc-jp'; $s = "12月23日はABCの誕生日"; print substr($s, 0, 14); 結果: 12月23日はABCの誕生日

参考URL:
http://www.pure.ne.jp/~learner/program/Perl_unicode.html#encoding
noname#25358
noname#25358
回答No.2

 Perl 5.8 未満の場合の対応法です。  substr() で取り出した文字列に対し、下記の正規表現を実行してください。 $s = "12月23日はABCの誕生日"; $s = substr($s, 0, 14); $s =~ s/(\x8e|\x8f.|[^\x00-\x7F\x8e\x8f])$//; print $s;  テストしてませんが、多分巧くいくと思います。  ロジックはEUC専用です。

xespr
質問者

お礼

ありがとうございます。 まさに求めていたご回答です。 しかしながら、 $s = "12月23日はABCの誕生日"; $s = substr($s, 0, 4); $s =~ s/(\x8e|\x8f.|[^\x00-\x7F\x8e\x8f])$//; print $s, "\n"; # 正しく見える print $s . "\n"; # 連結すると末尾がおかしくなる 丸め後の文字列の末尾に、まだ何か付いているのでしょうか?

  • fxdwg99
  • ベストアンサー率45% (43/94)
回答No.1

perl5.8以上であれば、 use encoding “euc-jp”; を使うと、EUC文字列でもsubstrが正しく動作するそうです。 それ以下のバージョンだと、substrで取り出した文字列の末尾を 調べてEUC1バイト目なら削除する、という処理を自前で行う 必要があるでしょう。

xespr
質問者

お礼

ありがとうございます。 use encoding “euc-jp”; できれいに取得できました。 しかし、今回は、特定の印字サイズで取得したいのです。 $s1 = "あいうえお"; $s2 = "12345かきくけこ"; 4桁なら下記のように取りたい あい 1234 のですが、use encoding では あいうえ 1234 となってしまいます。 「文字列の末尾を調べてEUC1バイト目なら削除する」 という技を教えていただける方、いらっしゃいますでしょうか?

関連するQ&A