2019年5月9日木曜日

[Perl] 03. ファイル/ディレクトリ操作

今回はファイル/ディレクトリの操作についてのメモ。
拡張モジュールの話が出てくるが、モジュールのインストール方法ついては こちら を参照。



ファイルの操作
ファイルをコピーするには File::Copy モジュールの copy を使用する。
use File::Copy;

copy ('file.txt', 'file_copy.txt');

ファイル名の変更やファイルの移動は rename 関数を使用する。
File::Copy モジュールの move を使用する方法もあるが、rename で問題ないと思う。
既にファイルが存在する場合は上書きされる。
# ',' と '=>' の二通りの書き方がある (結果は一緒)
rename ('file.txt', 'dir/file_rename.txt');
rename ('file.txt' => 'dir/file_rename.txt');
use File::Copy;

move ('file.txt', 'dir/file_move.txt');

ファイルの削除は unlink 関数を使用する。
File::Remove 拡張モジュールを使うとワイルドカード(*, ?)が使えるので便利。
unlink 'file.txt';
use File::Remove 'remove';

remove ('*.aaa');

ディレクトリの操作
ディレクトリを作成するには mkdir 関数を使用する。
mkdir 'dir';

ディレクトリをコピーするには外部コマンド。
File::Copy::Recursive 拡張モジュールを使用するのもあり。
system ("cp -rf dir1 dir2");
use File::Copy::Recursive 'rcopy';

rcopy ('dir1', 'dir2');

ディレクトリ名の変更やディレクトリの移動は rename 関数を使用する。
File::Copy モジュールの move を使用する方法もあるが、rename で問題ないと思う。
移動先のディレクトリが既にある且つ空でない場合は移動できないので注意。(移動先をあらかじめ削除しておくか、中身だけ移動させる等で対応する。)
# ',' と '=>' の二通りの書き方がある (結果は一緒)
rename ('dir_x', 'dir_y/dir_rename');
rename ('dir_x' => 'dir_y/dir_rename');
use File::Copy;

move ('dir_x', 'dir_y/dir_move');

ディレクトリの削除は File::Path モジュールの rmtree を使用する。(rmdir 関数もあるが、こちらはディレクトリ内が空でないと削除できない。)
File::Remove 拡張モジュールを使うとワイルドカード(*, ?)が使えるので便利。
use File::Path;

rmtree('dir2');
use File::Remove 'remove';

remove('dir*');

存在確認
ファイルやディレクトリの存在確認を行いたい場合は -f, -d, -e を使用する。
# ファイル存在確認
if (-f './dir/file.txt') {
    print "file found\n";
}

# ディレクトリ存在確認
if (-d './dir') {
    print "directory found\n";
}

# ファイル or ディレクトリ存在確認
if (-e './dir/file.txt') {
    print "file or directory found\n";
}

ファイルの比較
ファイルの比較には File::Compare モジュールを使用する。
compare の戻り値は、一致の場合は 0、不一致の場合は 1 になる。また、ファイルが存在しなかった場合などは -1 となる。
use File::Compare;

$file1 = 'file1.txt';
$file2 = 'file2.txt';

if (compare($file1, $file2) == 0) {
    print "Same!\n";
}

ファイルのオープン/クローズ
open 関数を使ってファイルを開き、ファイルハンドルを割り当てる。close 関数を使ってファイルと閉じる。

open(FILEHANDLE[,MODE,EXPR]])
close(FILEHANDLE)

 FILEHANDLE : ファイルハンドル
 MODE : モード
 EXPR : ファイルパス

モードは以下。読み書きを両方できるモードもあるが、別ファイルに書き込んでリネームする方がシンプルだと思うので省略。

 > : 読み込み (ファイルが存在しない場合はエラー)
 > : 書き込み (ファイルが存在しない場合は新規作成)
 >> : 追加書き込み (ファイルが存在しない場合は新規作成)

open (FH1, "<  ./dir/file1.txt");  # 読み込み ('<' は省略可)
open (FH2, ">  ./dir/file2.txt");  # 書き込み
open (FH3, ">> ./dir/file3.txt");  # 追加書き込み

close (FH1);
close (FH2);
close (FH3);

読み込み→書き込みの簡単な例。
$file_path1 = './dir/aaa.txt';
$file_path2 = './dir/bbb.txt';

open (FH1, "< $file_path1");  # 読み込み
open (FH2, "> $file_path2");  # 書き込み

# FH1 を1行毎に読み込み ($line 変数に代入) → FH2 に出力
while (my $line = <FH1>) {
    print FH2 $line;
}

# 変数を省略した場合に代替で利用される変数 $_ を使うと以下のようになる
while (<FH1>) {
    print FH2 $_;
}

バイナリ形式のファイルの場合は open した後に binmode で切り替える。
binmode (FH);

複数ファイルの内容を1ファイルにまとめる
特殊変数 $/ を使うと簡単に書ける。
open (FH1, "> marge.txt");

foreach (@files) {
    open (FH2, "< $_");

    local $/;  # 入力レコードセパレーターを undef
    print FH1 <FH2>;

    close (FH2);
}

close (FH1);
また、join 関数を使う方法もある。但し、$/ を使った方が高速。
open (FH1, "> marge2.txt");

foreach (@files) {
    open (FH2, "< $_");

    print FH1 join('', <FH2>);

    close (FH2);
}

close (FH1);


ディレクトリの中身を取得
glob 関数を使うと便利。拡張子を指定したいときは *.txt 等にする。
以下のようなディレクトリ構成を例としてみる。
dir
├ dir_1
│ └ bbb.txt
└ aaa.txt
$dir_path = './dir'
@dirs = glob("$dir_path/*");
foreach (@dirs){
    print "$_\n";
}
実行結果
./dir/aaa.txt
./dir/dir_1

opendir/closedir を使う方法もある。opendir でディレクトリをオープンし readdir で取得。こちらの方法だと'.'や'..'も拾われるので注意。
DH は Directory Handle 名。
opendir (DH, '$dir_path');
foreach (readdir(DH)){
    next if /^\.{1,2}$/;  # '.'と'..'はスキップ
    print "$_\n";
}
closedir(DH);
実行結果
dir_1
aaa.txt

再帰的に全てのファイルを見たい場合は use File::Find モジュールを使います。
use File::Find;

$dir_path = 'dir';

find (\&sub_find, $dir_path);

sub sub_find{
    return if /^\.{1}$/;           # '.' はスキップ
    my $path = $File::Find::name;  # パス取得
    print "$path  ($_)\n";
}
実行結果
dir/aaa.txt  (aaa.txt)
dir/dir_1  (dir_1)
dir/dir_1/bbb.txt  (bbb.txt)

更新日からの日数
ファイルやディレクトリの最終更新日からの日数を知りたい場合は -M を使用する。
また、最終アクセス日からの日数を知りたい場合は -A を使用する。
○日間更新されていないファイルを削除したい!等に使える。
$elapse_m = -M 'file.txt';
$elapse_a = -A 'file.txt';

print "$elapse_m  $elapse_a\n";

おまけ
ファイル操作とはちょっと違うが、パスを調べる方法についてメモしておく。

実行している Perl スクリプトのパスを知りたい場合は use Cwd モジュールの realpath を使用する。カレントディレクトリのパスを知りたい use Cwd モジュールの getcwd を使用する。他の階層にある別のスクリプトから実行される場合に必要となることがある。
use File::Basename;
use Cwd 'realpath','getcwd';

$script_dir_path = realpath(dirname($0));  # 本スクリプトのディレクトリパスを取得
$current_dir_path = getcwd;                # カレントディレクトリのパスを取得


0 件のコメント:

コメントを投稿