NAME

perlopentut - Perl でファイルを開いたりパイプを使ったりするための簡単なレシピ

DESCRIPTION

Perl でファイルに対して入出力をするとき、Perl では ファイルハンドル と 呼ばれるものを通して行います。 ファイルハンドルは外部ファイルに対する内部名です。 open 関数の仕事は内部名と外部名を関連づけることで、close 関数は 関連づけを壊すことです。

便利なように、Perl は実行開始時に既に開いているいくつかの特別な ファイルハンドルを設定します。 それは STDIN, STDOUT, STDERR, ARGV です。 これらは既に開いているので、自分でこれらを開くときの問題を受けることなく 正しく使うことができます。

    print STDERR "This is a debugging message.\n";

    print STDOUT "Please enter something: ";
    $response = <STDIN> // die "how come no input?";
    print STDOUT "Thank you!\n";

    while (<ARGV>) { ... }

これらの例で見られるように、STDOUTSTDERR は出力ハンドルで、 STDINARGV は入力ハンドルです。 これらは @ARGV 配列や %ENV ハッシュと同様に Perl によって 予約されているので、全て大文字になっています。 これらの外部関連づけはシェルによって行われます。

その他のファイルハンドルは自分で開く必要があります。 多くのバリエーションはありますが、Perl の open() 関数を開く最も一般的な方法は 3 引数と一つの返り値のものです:

OK = open(HANDLE, MODE, PATHNAME)

ここで:

OK

これは、開くのに成功すれば何らかの定義された値、失敗すれば undef です;

HANDLE

これは、成功すれば open 関数によって埋められる未定義のスカラ変数です;

MODE

これはファイルを開くときのアクセスモードとエンコーディング型式です;

PATHNAME

これは開きたいファイルの外部名です。

open 関数の複雑さの大部分は、MODE 引数が多くの値を 取ることのできることにあります。

ファイルの開き方を説明する前に最後に一言: Perl ではファイルを開いても (普通は)自動的にロックすることはしません。 ロックの方法については perlfaq5 を参照してください。

テキストファイルを開く

読み込み用にテキストファイルを開く

テキストファイルを読み込みたい場合、まず次のように読み込み専用モードで 開きます:

    my $filename = "/some/path/to/a/textfile/goes/here";
    my $encoding = ":encoding(UTF-8)";
    my $handle   = undef;     # this will be filled in on success

    open($handle, "< $encoding", $filename)
        || die "$0: can't open $filename for reading: $!";

シェルと同様に、Perl でもファイルを読み込み専用モードで開くために "<" が使われます。 これに成功すると、Perl は新しいファイルハンドルを割り当て、未定義だった $handle 引数にそのハンドルへのリファレンスを設定します。

これでこのハンドルに対して readline, read, getc, sysread のような関数が使えます。 おそらく最も一般的な入力関数は演算子のように見えるものでしょう:

    $line = readline($handle);
    $line = <$handle>;          # same thing

readline 関数はファイル終端やエラーのときに undef を返すので、 時々次のように使われているのを見るでしょう:

    $line = <$handle>;
    if (defined $line) {
        # do something with $line
    }
    else {
        # $line is not valid, so skip it
    }

また、次のようにして単に未定義値に対してすばやく die することもできます:

    $line = <$handle> // die "no input found";

しかし、EOF に到達するのが想定されていて通常の出来事の場合は、 入力がなくなっただけで終了したくありません。 そうではなく、単に入力ループを終了したいでしょう。 実際のエラーがループを終了させたのかをテストして、適切に行動できます:

    while (<$handle>) {
        # do something with data in $_
    }
    if ($!) {
        die "unexpected error while reading from $filename: $!";
    }

エンコーディングに関する注意: テキストエンコーディングを毎回指定する 必要があるのは少し面倒に感じるかもしれません。 毎回設定する必要がないように open のためのデフォルトエンコーディングを 設定するために、open プラグマを使えます:

    use open qw< :encoding(UTF-8) >;

一度これを行えば、open モードからエンコーディングの部分を安全に省略できます:

    open($handle, "<", $filename)
        || die "$0: can't open $filename for reading: $!";

しかし、先にデフォルトのエンコーディングを設定することなく裸の "<" を使うことは決してしないでください。 さもなければ、Perl はとてもとてもとてもたくさんあるテキストファイルの 種類のうちどれかを知ることができず、Perl はあなたのファイルのデータを 動作させるための実際の文字にマッピングすることができません。 その他のよくあるエンコーディング形式には "ASCII", "ISO-8859-1", "ISO-8859-15", "Windows-1252", "MacRoman" および、 "UTF-16LE" すらもあります。 エンコーディングに関するさらなる情報については perlunitut を 参照してください。

書き込み用にテキストファイルを開く

ファイルに書き込みたい場合、そのファイルの既存の内容をどうするかを まず決定する必要があります。 二つの基本的な選択肢があります: 保存するか上書きするかです。

既存の内容を保存したい場合、ファイルを追記モードで開きます。 シェルと同様に、 Perl でも既存のファイルを追記モードで開くために ">>" が使われます。 ファイルがない場合、">>" はファイルを作ります。

    my $handle   = undef;
    my $filename = "/some/path/to/a/textfile/goes/here";
    my $encoding = ":encoding(UTF-8)";

    open($handle, ">> $encoding", $filename)
        || die "$0: can't open $filename for appending: $!";

これでこのハンドルに対して print, printf, say, write, syswrite を使って書き込めます。

前述したように、ファイルが既に存在していない場合、追記モードで開くと ファイルを作ります。 しかしファイルが既に存在している場合、その内容は保護されます; 新しいテキストは 既存のテキストの末尾に追加されるからです。

一方、時々、既に何かがあっても上書きしたいときもあります。 書き込みを始める前にファイルを消すために、書き込み専用モードで 開くことができます:

    my $handle   = undef;
    my $filename = "/some/path/to/a/textfile/goes/here";
    my $encoding = ":encoding(UTF-8)";

    open($handle, "> $encoding", $filename)
        || die "$0: can't open $filename in write-open mode: $!";

ここで再び Perl はシェルと同様に動作し、">" は既存のファイルを 上書きします。

追記モードと同様に、ファイルを書き込みモードで開くと、 print, printf, say, write, syswrite を使って ファイルハンドルに書き込めるようになります。

読み書きモードについては? おそらくそれは存在しないというふりをした方がよいでしょう; なぜならテキストファイルを読み書きモードで開いても おそらくあなたが望んでいることをしないからです。 詳しくは perlfaq5 を参照してください。

バイナリファイルを開く

開こうとしているファイルがテキスト文字ではなくバイナリデータが含まれている 場合、openMODE 引数は少し異なるものになります。 エンコーディングを指定する代わりに、データが生のバイト列であることを Perl に知らせます。

    my $filename = "/some/path/to/a/binary/file/goes/here";
    my $encoding = ":raw :bytes"
    my $handle   = undef;     # this will be filled in on success

それから前述の通り、必要に応じて "<", ">>", ">" を選びます:

    open($handle, "< $encoding", $filename)
        || die "$0: can't open $filename for reading: $!";

    open($handle, ">> $encoding", $filename)
        || die "$0: can't open $filename for appending: $!";

    open($handle, "> $encoding", $filename)
        || die "$0: can't open $filename in write-open mode: $!";

あるいは、次のようにして既に存在しているハンドルをバイナリモードに 変えることが出来ます:

    binmode($handle)    || die "cannot binmode handle";

これは、Perl が既に開いているハンドルに対して特に有用です。

    binmode(STDIN)      || die "cannot binmode STDIN";
    binmode(STDOUT)     || die "cannot binmode STDOUT";

また、その場で変更するために binmode に明示的にエンコーディングを 渡すこともできます。 これは正確には「バイナリ」モードではありませんが、それでも これをするために binmode を使います:

  binmode(STDIN,  ":encoding(MacRoman)") || die "cannot binmode STDIN";
  binmode(STDOUT, ":encoding(UTF-8)")    || die "cannot binmode STDOUT";

一旦バイナリファイルを正しいモードで適切に開くと、テキストファイルで 使ったものと全て同じ Perl I/O 関数を使えます。 しかし、入力に対して可変長の readline ではなく固定長の read を使った方が良いでしょう。

次のものはバイナリファイルをコピーする例です:

    my $BUFSIZ   = 64 * (2 ** 10);
    my $name_in  = "/some/input/file";
    my $name_out = "/some/output/flie";

    my($in_fh, $out_fh, $buffer);

    open($in_fh,  "<", $name_in)
        || die "$0: cannot open $name_in for reading: $!";
    open($out_fh, ">", $name_out)
        || die "$0: cannot open $name_out for writing: $!";

    for my $fh ($in_fh, $out_fh)  {
        binmode($fh)               || die "binmode failed";
    }

    while (read($in_fh, $buffer, $BUFSIZ)) {
        unless (print $out_fh $buffer) {
            die "couldn't write to $name_out: $!";
        }
    }

    close($in_fh)       || die "couldn't close $name_in: $!";
    close($out_fh)      || die "couldn't close $name_out: $!";

パイプを開く

Perl はまた、ファイルではなく外部プログラムやシェルコマンドへの ファイルハンドルも開きます。 これを、更なる処理のために Perl プログラムから外部コマンドへ渡すため、 または処理する Perl プログラムのために他のプログラムからデータを 受け取るために行えます。

コマンドへのファイルハンドルは、パイプ としても知られます; Unix パイプラインという似たようなプロセス間通信原則に基づいて 動作するからです。 そのようなファイルハンドルは、外側が静的なファイルではなく 動作中のプログラムですが、それ以外の点については より典型的なファイルベースのファイルハンドルとちょうど同じように 動作し、この文書で既に議論した全てのテクニックが利用可能です。

2 番目の (MODE) 引数にパイプの入力または出力を示す特殊な文字を 設定することで、ファイルを開くのに使うのと同じ open で パイプを開きます。 Perl プログラムが外部プログラムからデータを読み込むファイルハンドルには "-|" を使います; プログラムにデータを送るファイルハンドルには "|-" を使います。

読み込み用にパイプを開く

たくさんのテキストファイルが含まれている、unsorted と呼ばれる 近くのディレクトリに保管されているデータを処理する Perl プログラムが欲しいとしましょう。 また、処理を開始する前に、複数のファイルを単一の、ユニークな行を アルファベット順にソートしたいとします。

それぞれのファイルに対して通常のファイルハンドルを開き、 このようにして読み込んだ全てのファイルの内容を徐々にメモリ内の配列に 構築し、読み込むファイルがなくなったら最後にソートとフィルタリングをする、 という形でこれを行うことも出来ます。 あるいは、結合とソートをオペレーティング自身の sort コマンドに 任せて、その出力を直接パイプで開くことで、遙かに速く作業することも出来ます。

以下は、これがどのように見えるかです:

    open(my $sort_fh, '-|', 'sort -u unsorted/*.txt')
        or die "Couldn't open a pipe into sort: $!";

    # And right away, we can start reading sorted lines:
    while (my $line = <$sort_fh>) {
        #
        # ... Do something interesting with each $line here ...
        #
    }

open の 2 番目の引数である "-|" は、ファイルへの通常の ファイルハンドルではなく、別個のプログラムへの読み込みパイプにします。

open の 3 番目の引数は、 プログラム名 (sort) とその全ての引数を含んだ文字列です: この場合、-u はユニークソートを指定し、それからファイルグロブは ソートするファイルを指定することに注意してください。 結果のファイルハンドル $sort_fh は ちょうど読み込み専用 ("<") ファイルハンドルのように動作し、 プログラムは、通常の単一のファイルが開かれたかのように、 引き続いてそこからデータを読み込むことができます。

書き込み用にパイプを開く

前回の例の続きとして、プログラムの処理を完成させて、 結果は @processed と呼ばれる配列に入っているとしましょう。 これらの行を numbered.txt というファイル名に、 いい感じに整形された行番号の列と共に出力したいとします。

確かにこれをするコードを自分で書くこともできます - あるいは、再び、 この作業を他のプログラムに送ることもできます。 この場合、cat を、行番号付けを有効にする -n オプション込みで 実行するには、次の技を使います:

    open(my $cat_fh, '|-', 'cat -n > numbered.txt')
        or die "Couldn't open a pipe into cat: $!";

    for my $line (@processed) {
        print $cat_fh $line;
    }

ここで、open の 2 番目の引数に "|-" を使います; これにより、$cat_fh に代入されるファイルハンドルが書き込み パイプであることを示します。 それから、データを print する基本的な関数を含めて、 書き込み専用の普通のファイルハンドルを使うのと同じようにこれを使えます。

パイプしたいコマンドを指定する 3 番目の引数は、 cat の出力を ">" 記号を使ってファイル numbered.txt に リダイレクトするように指定していることに注意してください。 これは最初は少しおかしく見えるかもしれません; この同じ記号は、open の 2 番目の引数では全く違うものを意味するからです! しかし、ここ 3 番目の引数では、これは単に Perl がパイプを開く シェルコマンドの一部であり、Perl 自身はこれに何の特別な意味も与えません。

コマンドをリストとして表現する

パイプを開くために、Perl は、目的のコマンドとそれ自身の引数を、 前述の例のように単一の文字列として結合するのではなく、 別個の要素として構成されたリストで open を呼び出すという 選択肢を提供しています。 例えば、最初の例の open 呼び出しは次のように書けます:

    open(my $sort_fh, '-|', 'sort', '-u', glob('unsorted/*.txt'))
        or die "Couldn't open a pipe into sort: $!";

この方法で open を呼び出す場合、 Perl はシェルをバイパスして指定されたコマンドを直接起動します。 シェルはコマンド路の引数リストの中の特殊文字を解釈しようとはしません; さもなければ望まない効果を生むことがあります。 これはより安全で、open 呼び出しの誤りを減らし、 引数として変数の内容を渡すような場合に有用で、 単に空白を含むファイルを参照する場合にも安全です。

しかし、シェルに意味のあるメタ文字を 渡したい、 例えば最終的な unsorted/*.txt の中の "*" のような場合、 この代替文法は使えません。 この場合、引数をファイル名として評価する Perl の便利な glob 組み込み関数で 回避します; そして前述したように、結果のリストを open に安全に 渡せます。

また、このようなリスト形式でのパイプコマンド引数表現は、全ての プラットフォームで動作するわけではないことに注意してください。 真の fork 関数を提供する Unix ベースの OS (例えば macOS や Linux)、 および Perl 5.22 以降の Windows では動作します。

SEE ALSO

open の完全な文書は、 ここでカバーしているベストプラクティスベースのものを超えて、 この関数の完全なリファレンスを提供します。

AUTHOR and COPYRIGHT

Copyright 2013 Tom Christiansen; now maintained by Perl5 Porters

This documentation is free; you can redistribute it and/or modify it under the same terms as Perl itself.