第6章 ファイル操作とファイルハンドル
6-1. 標準入出力
Perlでは、ファイルの入出力にファイルハンドルというものを使います。
ファイルハンドルとは、今どのファイルを処理しているかを管理するための名前のことです。
ファイルの読み込みや書き込みをするには、まずファイルを開いてファイルハンドルに関連付けし、そのファイルハンドルを操作するのが基本手順になります。
Perlでは、特別なファイルハンドルとして、以下の3つが用意されています。
ファイルハンドル |
意味 |
内容 |
STDIN |
標準入力 |
パイプ ( *1 ) やリダイレクト ( *2 ) 時にデータを取り込む。 コマンドライン時にはキーボード。 |
STDOUT |
標準出力 |
パイプやリダイレクト時にデータを出力する。コマンドライン時にはモニター。 |
STDERR |
標準エラー |
エラー時に出力される。 |
*1 : 標準出力と標準入力を経由してデータを受け渡しする仕組みで、あるプログラムが標準出力に出力した結果を、他のプログラムの標準入力に渡す仕組みをいいます。
*2 : 標準出力をそのままのテキストファイルなどに書き出す方法をいいます。
たとえば、コマンドラインから操作する場合には、以下のようなスクリプトを実行することができます。
10 | print "こんにちは、$nameさん\n" ; |
このように、キーボード入力を標準入力であるファイルハンドルSTDINから変数$nameにセットし、文字列を操作・表示させることができます。
6-2. open関数とファイルハンドル
前節ではあらかじめ用意された標準のファイルハンドルを説明しましたが、それ以外のファイルハンドル以外を使う場合には、open関数を使用します。
open関数を使ってファイルを読み込む場合の構文は次のようになります。
1. ファイルを読み込みモードでオープンする場合 (open命令)
open ファイルハンドル, "ファイル名";
2. ファイルを読み込む処理 (read命令)
配列 = <ファイルハンドル>
3. ファイルのオープンを閉じる処理 (close命令)
close ファイルハンドル
たとえばこの一連の処理として、記録ファイルを読み込んで表示するには、次のように記述します。
ただし、読み出す記録ファイルが大量データの場合には、上記のように一気に読み込むと危険なケースがあります。
その場合には、次のようにwhile文を使って、1行ずつ読み出します。
6-3. ファイルの読み書き
ファイルに書き込み操作を行う場合にも、open関数とファイルハンドルを使います。
open関数によるファイルの読み書き操作としては、次のような構文が用意されています。
構文 |
内容 |
|
読み込みモード |
|
書き込みモード |
|
追加書き込みモード |
|
読み書き両用モード |
|
パイプ出力用 |
|
パイプ入力用 |

コード-1. ファイルの読み込み
data.txtを読み込み中身を読みこみます。読むだけですので、data.txtの中身に影響はありません。
< を省略して、open(FH, "data.txt") と書いても意味は同じです。

コード-2. ファイルの上書き
data.txtに「DEF」という文字列を上書きします。
上書きですから、data.txtの中身をいったん消して新たに書き込むことになります。
仮に、当初data.txt の中に「ABC」という文字列があった場合、実行後の中身は「DEF」となります。

コード-3. 追加上書き
data.txtに「DEF」という文字列を追加書き込みします。
追加書き込みですから、data.txtの中身は消さずに追加で上書きすることになります。
仮に、当初data.txt の中に「ABC」という文字列があった場合、実行後の中身は「ABCDEF」となります。

コード-4. 読み書き両用モード-1 ( +< )
3 | if ( $log eq 'A' ) { print FH 'B' ; } |
読んでから、書くことができます。読むのみで、書かなくてもOKです。

コード-5. 読み書き両用モード-2 ( +> )
書いてから、読むことができます。書くのみで、読まなくてもOKです。

コード-6. 読み書き両用モード-3 ( +>> )
1 | open (FH, "+>> data.txt" ); |
追加上書きしてから、読むことができます。追加上書きのみで、読まなくてもOKです。

コード-7. コマンドからパイプ入力用にオープン
ls -l (カレントディレクトリ内のファイル名を取得) というコマンドを実行し、その結果を配列@cmdに格納します。

コード-8. コマンドへパイプ出力用にオープン
1 | open (OUT, "| /usr/sbin/sendmail" ); |
sendmailという外部にあるソフトウェアへHELOコマンドを出力します。
6-4. ファイルの削除と変更
その他にファイル操作のための関数として、次のようなものがあります。
内容 |
構文 |
ファイルの削除 |
unlink ファイル名 |
ファイル名の変更 |
rename 変更前のファイル名, 変更後のファイル名 |
パーミッションの変更 |
chmod モード, ファイル1, ファイル2, ... ファイルn |

コード例-1. unlink関数

コード例-2. rename関数
2 | rename ( "file.txt" , "file.dat" ); |

コード例-3. chmod関数
2 | chmod (0666, "file.txt" ); |
chmod関数の第1引数で指定する「モード」は、上記のように先頭に0を付けます。
これは指定するパーミッション値を10進法で記述していますという意味になります。
また、chmod関数は一度に複数のファイルのパーミッションを変更することもできます。
その場合、次のように第2引数以下に変更したいファイル名を複数指定します。

コード例-4. chmod関数(複数ファイルの指定)
2 | chmod (0666, "file.txt" , "log.txt" , "data.txt" , "bbs.dat" ); |
6-5. ファイルのコピーと移動
ファイルをコピーする場合は、Perlには関数が用意されていません。
そのため、open関数を使う方法とPerlモジュールを使う方法の2種類があります。
まずは、open関数式の場合です。

コード例-1. open関数式
ちょっとしたデータファイルであれば、上記の方法で十分です。
しかしながら、画像などの容量の大きなファイルをコピーする場合のことを考慮すると、一定サイズ毎に読み込んで書き込む方式のほうがいいでしょう(その方がサーバへの負荷が柔しい

)。
以下のコード例は、read関数を併用した改良版です。
1024バイト毎に読み込みながらコピーします。
なお、binmode関数(8,9行目)は、Windows環境でバイナリーファイルを扱う場合を考慮しています。

コード例-2. open関数式:改良版
10 | while ( read ( OLD, my $buf , 1024 ) ) { |
次に、モジュールを利用した例です。
Perlモジュールとしては、File::Copyモジュールが用意されています。
比較的新しいPerl環境では、標準モジュールとして組み込まれています。

コード例-3. File::Copyモジュール
File::Copyモジュールは、ファイルの「移動」も可能です。
その場合、move関数を使用します。

コード例-4. File::Copyモジュール「移動」
File::Copyモジュールが利用できるサーバ環境では、こちらを利用したほうが手軽です。
それに対して、open関数式は汎用的な局面で利用することができます。
6-6. ディレクトリを再帰的にコピー又は削除
前節では、単純にファイルをコピーする方法を紹介しました。
それでは、ディレクトリ内にあるファイルやディレクトリを再帰的にコピーするには、どうしたらいいでしょうか。
opendir + File::Copy の組み合わせでもできないことはありませんが、少し面倒です。
そのような用途のために、File::Copy::Recursive モジュールがあるので、ご紹介しておきます。
ただし、残念ながら標準モジュールではないようですので、ご自分でホームディレクトリ配下などへインストールする必要があります。
基本的な構文例は、次のとおりです。

コード例-1. File::Copy::Recursiveモジュール(フォルダ配下を再帰的にコピーする)
2 | use File::Copy::Recursive qw(rcopy); |
上記の rcopy により、oldディレクトリ内にある全てのファイルとディレクトリが、newディレクトリ内にそのままコピーされます。
さらには、各ディレクトリとファイルのパーミッションも忠実にコピーしてくれます。
次に今度は、ディレクトリ内のファイルやディレクトリを、「再帰的に削除」する場合にはどうしたらいいでしょうか?
ディレクトリを削除するための関数では、
rmdir がありますが、これはディレクトリの中身が空っぽのときだけしか使用できません。ですので、opendir関数等で中身のディレクトリやファイルを確認しながら、rmdir + unlink で1つ1つを削除していくしかありませんが、これも面倒です。
このような用途のために、File::Path モジュールが用意されています。このモジュールは標準モジュールですので、手軽に利用することができます。

コード例-2. File::Pathモジュール(フォルダ配下を再帰的に削除する)
上記の rtree により、dirディレクトリ内にある全てのファイルとディレクトリが、再帰的に削除されます。
さらには、rmtreeを右辺に置くと、「返り値」に削除したファイルとディレクトリの個数を返してくれます。

コード例-3. File::Pathモジュール(フォルダ配下を再帰的に削除して、その削除した個数を求める)