PHPで簡単なアプリを作っています。

ある特定のディレクトリに
"ABC1.jpg"
"ABC2.jpg"
"ABC3.jpg"
"ABC4.jpg"
"ABC5.jpg"
とあったとします。
この中で1ファイルを削除し、ファイル名を付け直すにはどうしたら良いのでしょうか?
例えば、"ABC3.jpg"を削除。
"ABC1.jpg"
"ABC2.jpg"
"ABC4.jpg"
"ABC5.jpg"
となったものを、
"ABC1.jpg"
"ABC2.jpg"
"ABC3.jpg" ("ABC4.jpg"からリネーム)
"ABC4.jpg" ("ABC5.jpg"からリネーム)
ファイル名は"XXX0.xxx"で「X]は固定で[0]の部分1文字だけ違うものです。
アドバイス頂きたいです。お願いします。

回答の条件
  • URL必須
  • 1人2回まで
  • 登録:2008/02/02 00:29:50
  • 終了:2008/02/03 00:57:22

ベストアンサー

id:GoldenDawn No.1

GoldenDawn回答回数426ベストアンサー獲得回数812008/02/02 09:18:08

ポイント80pt

ファイルの削除は unlink でやるとして、リネームは次のような感じでどうでしょう。

$path = './testimages/' ; // ディレクトリのパス
$files = scandir($path) ;

$pre = 'ABC' ;  // ファイル名の前部
$pro = '.jpg' ; // ファイル名の後部

for ($i = 0, $n = 1; $i < count($files); ++$i, ++$n) { // $n : ファイル名に含まれる数字
  while(is_dir($path.$files[$i])) array_shift($files) ; // ディレクトリは処理しない
  if ($files[$i] !== $pre.$n.$pro) rename($path.$files[$i], $path.$pre.$n.$pro) ; // 連続していなければリネーム
}

http://www.php.net/manual/ja/function.rename.php

id:norif_h

ありがとうございます。

書いていただいたソースを参考にやってみたのですが、while(is_dir・・・の部分で

Fatal error: Maximum execution time of 30 seconds exceededと出てタイムアウト

してしまいます。

ちなみに頂きましたコードの前に該当ファイルは削除しています。

ヒントをいただけると助かります。

2008/02/02 22:36:46

その他の回答(2件)

id:GoldenDawn No.1

GoldenDawn回答回数426ベストアンサー獲得回数812008/02/02 09:18:08ここでベストアンサー

ポイント80pt

ファイルの削除は unlink でやるとして、リネームは次のような感じでどうでしょう。

$path = './testimages/' ; // ディレクトリのパス
$files = scandir($path) ;

$pre = 'ABC' ;  // ファイル名の前部
$pro = '.jpg' ; // ファイル名の後部

for ($i = 0, $n = 1; $i < count($files); ++$i, ++$n) { // $n : ファイル名に含まれる数字
  while(is_dir($path.$files[$i])) array_shift($files) ; // ディレクトリは処理しない
  if ($files[$i] !== $pre.$n.$pro) rename($path.$files[$i], $path.$pre.$n.$pro) ; // 連続していなければリネーム
}

http://www.php.net/manual/ja/function.rename.php

id:norif_h

ありがとうございます。

書いていただいたソースを参考にやってみたのですが、while(is_dir・・・の部分で

Fatal error: Maximum execution time of 30 seconds exceededと出てタイムアウト

してしまいます。

ちなみに頂きましたコードの前に該当ファイルは削除しています。

ヒントをいただけると助かります。

2008/02/02 22:36:46
id:bayan No.2

bayan回答回数100ベストアンサー獲得回数132008/02/02 10:26:16

ポイント30pt

こんな考え方でどうでしょうか。

・ディレクトリの中のファイル名のリストを得る
・リストのファイル名をひとつづつ見ながら以下を行う
    ・ファイル名の先頭3文字と、残りの部分を得る
     
    ・ひとつ前のファイル名の先頭3文字と、今見ているファイル名の3文字を比較し、
        違っていたら連番のカウンタを 1 にもどす
        同じだったら連番のカウンタを 1 増やす

   ・今見ているファイル名の3文字と、連番のカウンタの値と、残りの部分を連結して新しいファイル名を決める
    ・今見ているファイル名のファイルの名前を新しいファイル名に変更する
    ・今見ているファイル名を、ひとつ前のファイル名を保持する変数にとっておく

ファイル名が XXX0.xxx の形式で、

X が半角英数字(マルチバイトじゃない)で、

0 は1文字の半角数字という前提です。


あと連番のカウンタの初期化とか、ファイル名を保持する変数の初期化をしておくとかもいるかな。


以下はテキトーなサンプルコードです。

ディレクトリを img としています。


<?php
$directory = "img";
$list = glob("$directory/*");
$seq = 1;
$prev_filename = "";
foreach($list as $path){
  $filename = basename($path);
  $prefix = substr($filename,0,3);
  $suffix = substr($filename,4);
  $prev_prefix = substr($prev_filename,0,3);
  if($prev_prefix != $prefix){
    $seq = 1;
  }else{
    $seq += 1;
  }
  $new_filename = $prefix.$seq.$suffix;
  $before = $directory."/".$filename;
  $after  = $directory."/".$new_filename;
  echo "$before -> $after\n";
  rename($before, $after);
  $prev_filename = $filename;
}

/*

substr で文字の位置を指定は 0 始まり

ABC1.jpg
01234567
0文字目から3文字 ABC
3文字目から1文字 1
4文字名以降      .jpg

*/
?>

URLはサンプルで使った関数のマニュアルです。

http://www.php.net/manual/ja/function.glob.php

http://www.php.net/manual/ja/function.basename.php

http://www.php.net/manual/ja/function.substr.php

http://www.php.net/manual/ja/function.rename.php

id:norif_h

ありがとうございます。

参考にさせて頂きます。

2008/02/02 22:34:10
id:tobeoscontinue No.3

tobeoscontinue回答回数212ベストアンサー獲得回数522008/02/02 13:51:00

ポイント30pt

実際に動いて確認できるよう簡単なコードを書いてみました。

ディレクトリの内容をチェックボックスで表示させて、削除するものをチェックするというものです。

チェックボックスにしたことで複数の削除が可能になっています。

本体はdel_rename()です。その他は付随するもので重要ではありません。

$path = "ある特定のディレクトリ";
$glob = scandir($path);

$list = validate($_POST['delist'], $glob);
if ($list) {
  del_rename($path, $glob, $list);
  $glob = scandir($path);
}
echo form('delist',$glob);

$pathには"ある特定のディレクトリ"へのパスを指定します。このディレクトリで削除やファイル名の付け直しをするのでphpから書き込み権限を与えておく必要があります。

chmod o+w "ある特定のディレクトリ" のような。


scandir()は$pathで指定されたディレクトリ内の名前を配列にして返します。値は名前だけですので$pathが無いと実際のファイルにはアクセスできないので注意が必要です。それだと面倒だというのならフルパス名にすることも可能です。


validate()は$_POST['delist']に値が設定されていると$globに対応する値を返します。

最初は値が設定されていないので空の配列が返されform()からチェックボックスのformが出力されます。


formでチェックボックスがいくつかチェックされるとvalidate()はそのファイル名(削除する)の配列を返しますのでdel_rename()が実行されます。確認のため再度scandir()することで削除、リネームがされたことが次のform()で表示されるので確認できます。

/* php5なら必要ない。 */
function scandir($directory , $sorting_order=0) {
  $dh = opendir($directory);
  $list = array();
  while ($file_name = readdir($dh)) {
    if ($file_name == '.' || $file_name == '..')
      continue; 
    else $list[] = $file_name;
  }
  closedir($dh);

  if ($order)
    rsort($list);
  else
    sort($list);
  return $list;
}

php5ならscandir()はあるので必要ありません。逆にエラーになります。

配列の要素が名前だけでは面倒だというのなら

else $list[] = $file_name;

else $list[] = $directory.'/'.$file_name;

とすればokです。多分。

/* $globを元にcheckboxのformを生成 */
function form($name,$glob) {
  $continue = '';
  foreach ($glob as $id=>$file)
    $continue .= '<input type="checkbox" name="'.$name.'[]" value="'.$id.'">'.$file.'<br>';
  $continue .= '<input type="submit" name="send" value="送信">';
  return '<form action="'.$_SERVER['PHP_SELF'].'" method="post">'.$continue.'</form>';
}

checkboxのformを生成し、その文字列を返します。checkboxで重要なことはvalueとしてファイル名ではなく、単なる数字にしていることです。そうすることで受け取る側でその値の信憑性をチェックすることが可能になることです。

function validate($env, $glob) {
  $list = array();
  if ($env)
    foreach ($env as $id)
      if (isset($glob[$id]))
        $list[] = $glob[$id];
  return $list;
}

受け取った値が$globに対応する値の配列を返します。

function del_rename($path, $glob, $list) {
  $_glob = $glob;
  foreach ($list as $file) {
    unlink($path.'/'.$file);
    array_splice($_glob, array_search($file, $_glob), 1);
  }

  foreach ($_glob as $id=>$file)
    if ($file !== $glob[$id]) {
//     echo 'rename('.$path.'/'.$file.', '.$path.'/'.$glob[$id].')<br>';
      rename($path.'/'.$file, $path.'/'.$glob[$id]);
    }
}

$globは単なるファイル名なので削除、リネームするにはそのディレクトリーの$pathが必要です。$listは削除するファイル名の配列です。

まずリネームのために$globをコピーした$_globを作ります。最初のforeachで削除し、$_globからもarray_splice()を使ってその部分を削除します(全て削除してからscandirするという手もありますが)。

この部分が終了した時点で$globには削除する前の状態が、$_globは削除された状態になっています。

次のforeachで順番に対応をとってリネームしていきます。

echo 'rename('の部分を有効にすることでどうリネームされているかを確認できます。

http://q.hatena.ne.jp ダミー

id:norif_h

ありがとうございます。

コードまで詳しく書いていただき感謝します。

PHP5ではなかったので、scandir関数はありがたかったです。

参考にさせて頂きます。

2008/02/02 22:33:44
  • id:tobeoscontinue
    訂正
    php5のscandir()と互換性がないかもしれません(.と..が含まれるかも)。


    >配列の要素が名前だけでは面倒だというのなら
    >else $list[] = $file_name;
    >を
    >else $list[] = $directory.'/'.$file_name;
    >とすればokです。多分。
    glob()で同じことができました。
  • id:norif_h
    自己レスです。
    冷静に考えてみました。
    GoldenDawn さんから回答いただいた件で
    while(is_dir($path.$files[$i])) array_shift($files) ;
    の部分は
    if(is_dir($path.$files[$i])) array_shift($files) ;
    で、良いのでしょうかね。。
  • id:GoldenDawn
    if にすると連続してディレクトリがある場合にまずいです。
    あまりチェックしてませんがこんな感じで
    for ($i = 0, $n = 1; $i < count($files); ++$i,++$n) {
    while(is_dir($path.$files[$i]) && count($files)) array_shift($files) ; // ディレクトリは処理しない
    if (count($files) == 0) break ;
    if ($files[$i] !== $pre.$n.$pro) rename($path.$files[$i], $path.$pre.$n.$pro) ;
    }
  • id:norif_h
    GoldenDawn様

    なるほど。「連続してディレクトリがある場合」は思いつかなかったです。
    非常に助かりました。感謝します。
    ありがとうございました。
  • id:tezcello
    以下のようなのを考えました。(もう終了してしまっていますが、何となく面白そうだったので)

    削除するファイル名は(削除する時点では)既知なので、これを使って共通する部分(固定の名前)と、任意の一文字(これは使いませんが)と、拡張子に分けておきます。
    preg_match('/(.+?)(.)(¥..+)/', $filename, $result)

    結果配列から検索文字列を作り、glob() でファイル名を取得します。
     (削除するファイル名から、拡張子の直前の一文字を.(ピリオド)に置き換える方法もあり)

    (以下元々のファイル名が欠けなく並んでいるのを想定しています。そうでなければ全ファイルをリネームするようにすれば問題無いかと)
    glob() の結果(指示しなければソートされている)を使ってループを回し、リネームしていく。
    ファイル名は、(固定部)$i+1(拡張子)を(固定部)$i(拡張子)に変更する
    ディレクトリであるかのチェックは必要に応じて行なう
      (拡張子が付いているディレクトリを作っていなければ不要でしょ?)
    また、ループを削除したファイルの次から始めれば処理が短縮出来ます。
      foreach なら、ファイル名を比較(チェック)して削除するファイル名がくるまで continue でスキップ
      for なら削除するファイル名の拡張子の直前の一文字からスタートする

この質問への反応(ブックマークコメント)

「あの人に答えてほしい」「この質問はあの人が答えられそう」というときに、回答リクエストを送ってみてましょう。

これ以上回答リクエストを送信することはできません。制限について

絞り込み :
はてなココの「ともだち」を表示します。
回答リクエストを送信したユーザーはいません