ディシプリンについて 補足

STDIN / STDOUT をバイナリとして取り扱いたかったんですが、どうもうまくいかなくてはまっていました。

最初に大きな思い違いをしていたのが、":bytes"の指定で

binmode(STDIN, ':bytes');
binmode(STDOUT, ':bytes');

とすることで、バイナリとして取り扱えると思い込んでいました。
実際には、改行コードの変換が行われてしまします。

例えば、単純にファイルのコピーを行いたいとして、次のようなコードを書いたとします。

my $a_file = './hoge';
my $a_size = -s $a_file;
my $a_buf;
open( IN, '<:bytes', $a_file ); read( IN, $a_buf, $a_size ); close( IN ); my $a_out = './out'; open( OUT, '>:bytes', $a_out );
print OUT $a_buf;
close( OUT );

my $a_new_size = -s $a_out;

print "$a_new_size / $a_size\n";

私が試した環境では"0x0a"(つまり\n)が、"0x0d 0x0a"(\r\n)に変換されて出力されてしまいました。

正しくは":raw"になるみたいです。

試していた環境が、winxp + ActivePerl5.10 (+ Cygwin) なので、恐らくご丁寧に環境に合わせて\r\nに変換してくれたものだと思います。
Cygwinはシェルとしてだけ利用。
(bytes指定がどのような動きをするのかよくわかっていないので、ご存知の方がいらしたらご指摘お願いいたします)

さて、もう少しはまった時の足跡を残しておくことにします。

以前、"use utf8;" と指定しておくと、内部文字列はutf8フラグがたっているものとして扱うという話をしました。

utf8フラグが立っている文字列は、フォーマット指定されていないファイルハンドルに対して出力が行えないのでした。

そこで次のようにして、エンコードを行って出力することにしました。

use utf8;

print Encode::encode( ':shiftjis', "ほげほげ\n" );

これをリダイレクトして、ファイルに出力すると、

82 D9 82 B0 82 D9 82 B0 0D 0A | ほげほげ\r\n

として出力されました。

「デフォルト = 何もしない」と思い込んでいたので、Encode::encode( ':shiftjis', ... ) では、改行コードを"\r\n"に変換するものだと考えていました。

これが、大きな間違いで、Encode::encode( ':shiftjis', ... ) は、改行コードの変更を行いません。

use utf8;
binmode( STDOUT, ':raw' );
print Encode::encode( ':shiftjis', "ほげほげ\n" );

とすれば、

82 D9 82 B0 82 D9 82 B0 0A | ほげほげ\n

と得られますし、

use utf8;
binmode( STDOUT, ':raw' );
print Encode::encode( ':shiftjis', "ほげほげ\r\n" );

とすれば、

82 D9 82 B0 82 D9 82 B0 0D 0A | ほげほげ\r\n

という結果が得られます。

すなわち、ファイルハンドルのデフォルトのディシプリン指定は':raw'ではなくて、':bytes'だったわけです。

これ、こんなテストコードを書いてみれば明白で

my $a_file = 'hoge';
my $a_size = -s $a_file;
my $a_buf;

open( IN, '< $a_file' );
read( IN, $a_buf, $a_size );
close( IN );

my $a_out = 'out';
open( OUT, '> $a_out' );
print OUT $a_buf;
close( OUT );

my $a_new_size = -s $a_out;

print "$a_new_size / $a_size\n";

ファイル'hoge'に"0x0a"ってバイトを見つけると、ご丁寧に"0x0d 0x0a"に置換してくれます。

...むぅ