[ ↑ INDEX ] [ ← PREV ] [ NEXT → ]

Lesson 11 ファイル・ディレクトリの操作


注意

ここで扱うスクリプトは,一度に多量のファイル・ディレクトリを作成・削除する操作を含んでいるので,よく理解しないで実行すると危険なので注意すること。

処理用のディレクトリを作成し,そのディレクトリでスクリプトを実行したり,ファイル・ディレクトリのコピーを作り,そのコピーを処理対象にしたりすると,比較的安全である。

無限ループに陥るなどした場合には,早めにスクリプトを強制終了する ( cntrl + c )。


■1. ディレクトリの移動・作成・削除

   移動: chdir ディレクトリ名
   作成: mkdir ディレクトリ名, パーミッション
   削除: rmdir ディレクトリ名
ディレクトリを作成する際に指定するパーミッションは,0744 のように,先頭に 0 を付けた8進数で表わす。(cf. アクセス権の設定)

◇スクリプトの実行

次のスクリプトを順番に実行し,結果を確認しなさい。(temp という名前のディレクトリ・ファイルがないディレクトリで実行すること。)

   1. % perl -e 'mkdir "temp", 0744; system "ls -l"'

   2. % perl -e 'system pwd; chdir "temp"; system pwd'

   3. % perl -e 'chdir "temp"; system pwd; chdir ".."; system pwd'

   4. % perl -e 'rmdir "temp"; system "ls -l"'

■2. ファイルテスト演算子

   -e  ファイル/ディレクトリが存在する
   -f  通常のファイル
   -d  ディレクトリ
   -T  テキストファイル
   -B  バイナリファイル

◇ スクリプトの実行:ファイルの属性のチェック

適当なディレクトリに移動し,以下のスクリプトを実行せよ。演算子部分とファイル名部分を変更し実行し,結果を確認しなさい。

   % perl -e 'if (-e "aesop.txt") { print "yes\n" } else { print "no\n" }'

■3. ファイル・ディレクトリの一覧の取得・検索

ファイルやディレクトリの一覧を取得するには,次のような方法が考えられる。(例に挙げたのは,ディレクトリ data 内の,拡張子が txt であるファイルの一覧を配列 @files に代入する方法。)


◇スクリプトの実行

   1. % perl -e 'print (`ls data | grep '.txt'`)'

   2. % perl -e 'print (`find data -name "*.txt" -print`)'

   3. % perl -e 'foreach $i (<data/*.txt>) { print "$i\n" }'

   4. % perl -e 'foreach $i (glob "data/*.txt") { print "$i\n" }'

   5. % perl -e 'opendir (DIR, "data"); foreach $i (grep {/\.txt$/} readdir DIR) { print "$i\n" }'

■4. ファイル名の変更

    rename 旧ファイル名, 新ファイル名

◇スクリプトの実行

   ------------------------------------------------------------
   % echo "" > temp.txt; ls
   temp.txt
   % perl -e 'rename "temp.txt", "empty.txt"'; ls
   empty.txt
   %
   ------------------------------------------------------------

◇スクリプトの実行

   ----------------------------------------------------------------------
   % ls voa
   〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜
   2-257721  2-257742  2-257765  2-257786  2-257813  2-257835
   %
   % perl -e 'foreach $i (<voa/*>) { rename $i, "$i.txt" }'
   % ls voa
   〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜
   2-257730.txt  2-257761.txt  2-257793.txt  2-257824.txt
   %
   % perl -e 'foreach $i (<voa/*.txt>) { $i=~/(.+)\.txt$/; rename $i, $1 }'
   % ls voa
   〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜
   2-257721  2-257742  2-257765  2-257786  2-257813  2-257835
   ----------------------------------------------------------------------

■5. 応用例

・拡張子の変更

   拡張子 .txt を .html に変更する
   ------------------------------------------------------------
   @filenames = glob ("*.txt");
   foreach $i (@filenames) {
      $i =~ s/\.txt$//;
      rename "$i.txt", "$i.html";
   }
   ------------------------------------------------------------

次のスクリプトは,「対象のファイルがあるディレクトリ」・「変更前の拡張子」・「変更後の拡張子」をコマンドラインで指定できるようにしたもの。(少し手を入れれば,拡張子の削除などもできるようになる。)

   % perl chext.pl data txt html

   chext.pl
   ------------------------------------------------------------
   die "USAGE: chext.pl dir old_ext new_ext\n" unless ($ARGV[2]);

   foreach $i (glob "$ARGV[0]/*.$ARGV[1]") {
      $i =~ s/\.$ARGV[1]$//;
      rename "$i.$ARGV[1]", "$i.$ARGV[2]";
   }

   exit;
   ------------------------------------------------------------

・ファイル名の桁数を揃える

次のように,桁数が3桁に揃うようにファイル名を変更するスクリプト。

     1.txt,   2.txt, ...  10.txt,  11.txt, ... 100.txt, 101.txt
               ↓
   001.txt, 002.txt, ... 010.txt, 011.txt, ... 100.txt, 101.txt
   same_num_of_digits.pl
   ------------------------------------------------------------
   foreach $i (grep {/^\d+\.txt$/} glob "*.txt") {
      ($num, $rest) = $i =~ /^(\d+)(\.txt)/;
      $new = sprintf "%03d%s", $num, $rest;
      rename $i,  $new;
   }
   exit;
   ------------------------------------------------------------

・連番を付けてファイル名の重複を避ける

ハイフンに続けて2桁の連番を付けることで,既存のファイル名と重複しないファイル名にするスクリプト。ブロック部分を他のスクリプトの一部として埋め込むことを想定しているので,$temp, $nummy によってブロック内でのみ有効な変数としている。

   ------------------------------------------------------------
   $file = '1.txt';

   {
      my ($temp, $num);
      $temp = $file;
      while (-e $temp) {
         $num++;
         $temp = sprintf "%s-%02d", $file, $num;
      }
      $file = $temp;
   }

   open (OUT, ">", $file);
   ------------------------------------------------------------

time 関数を使って重複しないファイル名を作る

重複しなければファイル名はどのようなものでもいい場合には,time 関数の返り値をファイル名とすることが考えられる。(この場合,ファイル名が作成時の時刻も表わしているので,ファイル名でソートすると自動的に作成順になるというメリットもある。)

time は秒数を返り値として返す。1秒以下は区別されないので,次のスクリプトでは,ファイル名が重複している場合には,一秒後に再度実行するようにしている。(一秒に一つしかファイルは作成されない。)

   ------------------------------------------------------------
   $file = time . '.txt'; 
   while (-e $file) {
      sleep 1;
      $file = time . '.txt'; 
   }
   ------------------------------------------------------------

   ------------------------------------------------------------
   #上のスクリプトを do を使って書き換えたもの
   do { $file = time . '.txt' } while (-e $file and sleep 1);
   ------------------------------------------------------------

次のスクリプトを実行すると,10個の空のファイルが作成される。

   ------------------------------------------------------------
   for ($count = 0; $count < 10; $count++) {
      do { $file = time . '.txt' } while (-e $file and sleep 1); 
      open (OUT, ">", $file);
   }
   exit;
   ------------------------------------------------------------

次のスクリプトでは,同じ時間の場合は連番を付けて区別するので,一秒間に複数のスクリプトが作成できる。連番の桁数は一秒間に作成されうるファイルの数を考えて変える。

   ------------------------------------------------------------
   for ($count = 0; $count < 100; $count++) {
      do { $file = time . (sprintf "-%03d", ++$num) . '.txt' } while (-e $file);
      open (OUT, ">", $file);
   }
   exit;
   ------------------------------------------------------------

・指定のディレクトリの下にあるファイルをすべて表示する

   % perl lsFiles.pl data

   lsFiles.pl
   ------------------------------------------------------------
   #!/usr/bin/perl

   if ($ARGV[0] eq '/') {
      exit;
   } elsif ($ARGV[0]) {
      $rootdir = $ARGV[0];
   } else {
      $rootdir = '.';
   }

   @dirs = ($rootdir);

   while ($dir = shift @dirs) {
      @list = glob ("$dir/*");
      push (@dirs,  grep {-d} @list);
      push (@files, grep {-f} @list);
   }

   foreach $file (sort @files) {
      print "$file\n";
   }

   exit;
   ------------------------------------------------------------

指定されたディレクトリの構成が次のようになっている (大文字はディレクトリ,小文字は普通のファイル) とすると,各段階における @dirs, @list, @files の内容は下の表のようになる。1は while ループの前の状態,8は後の状態である。

  A┬
   ├
   └D┬
     ├F─
     └G┬I┬
       │ └
       └J─
  @dirs @list ($dir/*) @files
1 A    
2 A ←shift A/b
A/c
A/D
+ A/b
+ A/c
3 A/D ←shift A/D/e
A/D/F
A/D/G
A/b, A/c
+ A/D/
4 A/D/F ←shift
A/D/G
A/D/F/h A/b, A/c, A/D/e
+ A/D/F/h
5 A/D/G ←shift A/D/G/I
A/D/G/J
A/b, A/c, A/D/e, A/D/F/h
6 A/D/G/I ←shift
A/D/G/J
A/D/G/I/k
A/D/G/I/l
A/b, A/c, A/D/e, A/D/F/h
+ A/D/G/I/k, A/D/G/I/l
7 A/D/G/J ←shift
A/D/G/J/m A/b, A/c, A/D/e, A/D/F/h, A/D/G/I/k, A/D/G/I/l
+ A/D/G/J/m
8     A/b, A/c, A/D/e, A/D/F/h, A/D/G/I/k, A/D/G/I/l, A/D/G/J/m

・キーワードを含むテキストファイルを表示

ディレクトリを指定すると,その下にあるテキストファイルでキーワードを含むものを一覧表示する。上のスクリプト (lsFiles.pl) と違って,テキストファイルを配列に入れずに処理している。ソートなどの処理をする必要がない場合,ファイルの数が配列に収まりきらなくなるほど大きくなる場合などには,配列に入れずに一つ一つ処理するようにするとよい。

   % perl findFiles.pl data "Japan"

   findFiles.pl
   ------------------------------------------------------------
   #!/usr/bin/perl

   $keyword = $ARGV[1];
   $keyword or exit;

   if ($ARGV[0] eq '/') {
      exit;
   } elsif ($ARGV[0]) {
      $rootdir = $ARGV[0];
   } else {
      $rootdir = '.';
   }

   @dirs = ($rootdir);

   while ($dir = shift @dirs) {
      @list = glob ("$dir/*");
      foreach $ i (@list) {
         if (-d $i) {
            push (@dirs, $i);
         } elsif (-T $i) {
            open (IN, "<", $i);
            $/ = undef;
            $text = <IN>;
            print "$i\n" if ($text =~ m/$keyword/i);
         }
      }
   }

   exit;
   ------------------------------------------------------------

[ ↑ INDEX ] [ ← PREV ] [ NEXT → ]