# ==============================================================================
# Perl Script: 最新ファイル及び古いファイルのリストアップ(リネーム前情報付き)
# ==============================================================================
#
# 説明:
# 指定されたディレクトリ内にある特定の命名規則を持つファイルの中から、
# 各グループで最も数値が大きいファイルを選び出し、その情報を表示します。
# さらに、それらのファイルのうち、指定された日数以上前に最終更新された
# ファイルについては、リネーム前の名前(バッチファイルから取得)も表示します。
#
# 主な機能:
# 1. 対象ディレクトリ、拡張子、古いと判断する日数、リネーム情報ファイル名を
# スクリプト冒頭で設定可能。
# 2. ファイル名パターン: グループ名<半角スペース1つ>数値.拡張子
# (グループ名にはスペースを含むことができます)
# 3. 各グループの最新ファイル(名前に含まれる数値が最大)をリストアップ。
# 4. 最新ファイルの中から、指定日数以上古いものを抽出し、リネーム前の名前と共にリストアップ。
#
# 必要なモジュール:
# - POSIX (strftime): 日付フォーマット用 (通常、標準で利用可能)
# - File::Basename (basename): ファイルパスからファイル名部分を抽出 (通常、標準で利用可能)
#
# 最終更新日時: 2025-05-09
#
# ==============================================================================
use strict; # 変数宣言の強制など、厳密な文法チェックを有効にする
use warnings; # 潜在的な問題やよくある間違いに対して警告を出すようにする
use POSIX qw(strftime); # 日付を人間が読める形式にフォーマットするために使用
use File::Basename qw(basename); # ファイルパスからファイル名部分のみを取得するために使用
# --- 設定 (ここをユーザーが編集します) ---
# 処理対象のフォルダを指定してください。
# Windowsのパス区切りは '/' または '\\' (バックスラッシュ2つ) を使用してください。
# 例: my $target_directory = "C:/Users/YourName/Documents/MyFiles";
# 例: my $target_directory = "/home/yourname/my_files";
# 例: my $target_directory = "."; (スクリプトが置かれているディレクトリ)
my $target_directory = "."; # ★★★ 対象フォルダを実際のパスに書き換えてください ★★★
# 対象とするファイルの拡張子を指定してください (ドットは含めず、例: "txt", "log")
my $file_extension = "txt"; # ★★★ 対象の拡張子を書き換えてください ★★★
# 何日以上前のファイルを「古いファイル」と判断するかの日数を指定してください
my $days_threshold = 7; # ★★★ 日数を書き換えてください ★★★
# リネーム情報が記載されたバッチファイル名を指定してください。
# このバッチファイルは、Perlスクリプトと同じフォルダに置かれていることを前提とします。
# バッチファイル形式: rename "元のファイル名" "新しいファイル名(数値なし)"
my $rename_batch_filename = "xxx.bat"; # ★★★ バッチファイル名を書き換えてください ★★★
# --- グローバル変数 ---
# 各グループの最新ファイル情報を格納するためのハッシュ
# キー : グループ名 (例: "ggg aaa", "ccc")
# 値 : ハッシュリファレンス {
# name => ファイル名 (例: "ggg aaa 01.txt"),
# number => 数値部分 (例: 1),
# timestamp => 最終更新日時 (エポック秒),
# path => 元のファイルパス (例: "./test_files/ggg aaa 01.txt")
# }
my %latest_files_in_group;
# リネーム情報バッチファイルから読み取った対応表を格納するためのハッシュ
# キー : リネーム後のファイル名 (数値なし、例: "ggg aaa.txt", "ccc.txt")
# 値 : リネーム前のファイル名 (例: "zzzzz zzzzz.eee", "qwqwqwqw.rrr")
my %rename_map;
# --- メイン処理開始 ---
# ==============================================================================
# ステップ 0: リネーム情報バッチファイルの読み込み
# ==============================================================================
# 指定されたバッチファイルが存在すれば、その内容を読み込み、リネーム前後の対応を %rename_map ハッシュに格納します。
if (-f $rename_batch_filename) { # -f はファイルが存在し、かつ通常のファイルであるかを確認
# バッチファイルを開く (読み込みモード "<")
# openに失敗した場合は警告を出し、$fh_batch は undef のままなので、続く if($fh_batch) で処理をスキップ
open(my $fh_batch, "<", $rename_batch_filename)
or warn "警告: リネーム情報バッチファイル '$rename_batch_filename' を開けませんでした: $! (エラー内容)。リネーム前の名前は表示されません。\n";
if ($fh_batch) { # ファイルオープンに成功した場合のみ処理
print "リネーム情報ファイル '$rename_batch_filename' を読み込んでいます...\n";
while (my $line = <$fh_batch>) { # 1行ずつ読み込む
chomp $line; # 行末の改行文字を削除
# rename "元のファイル名" "新しいファイル名(数値なし)" の形式の行を正規表現で検索
# /i オプションで大文字・小文字を区別しない
# \s* は0個以上の空白文字(行頭・行末のスペースやコマンドと引数の間の余分なスペースに対応)
# "([^"]+)" はダブルクォートで囲まれた任意の文字列(ファイル名部分)をキャプチャ
if ($line =~ /^\s*rename\s+"([^"]+)"\s+"([^"]+)"\s*$/i) {
my $original_name_from_bat = basename($1); # キャプチャした元のファイル名 (パス部分を除外)
my $renamed_to_name_base = basename($2); # キャプチャした新しいファイル名(数値なし) (パス部分を除外)
# %rename_map ハッシュに格納: キーは新しいファイル名(数値なし)、値は元のファイル名
$rename_map{$renamed_to_name_base} = $original_name_from_bat;
}
}
close $fh_batch; # ファイルハンドルを閉じる
print "リネーム情報の読み込み完了。\n";
}
} else {
warn "警告: 指定されたリネーム情報バッチファイル '$rename_batch_filename' が見つかりません。リネーム前の名前は表示されません。\n";
}
# ==============================================================================
# ステップ 1: 対象ディレクトリ内のファイル一覧の取得と解析
# ==============================================================================
# 指定されたディレクトリから、指定された拡張子を持つファイルの一覧を取得します。
# glob はワイルドカードを使ってファイル名を展開する関数です。
my $glob_pattern = "$target_directory/*.$file_extension";
my @filepaths = glob $glob_pattern; # @filepaths 配列にファイルパスのリストが格納される
# 対象ファイルが見つからなかった場合の処理
unless (@filepaths) { # @filepaths 配列が空かどうかをチェック
print "対象となる *.$file_extension ファイルがディレクトリ '$target_directory' に見つかりませんでした。\n";
print "指定したパスや拡張子が正しいか、そのフォルダに該当ファイルが存在するか確認してください。\n";
exit; # スクリプトを終了
}
# 正規表現内で使用するために拡張子文字列をエスケープします。
# 例えば、$file_extension が "txt.bak" のように "." を含む場合、"." は正規表現のメタ文字なので、
# quotemeta を使うことで "\." のようにエスケープされ、リテラルのドットとして扱われます。
my $escaped_extension = quotemeta($file_extension);
# 取得したファイルパスのリストをループ処理
foreach my $filepath (@filepaths) {
my $filename = basename($filepath); # パス情報を取り除き、ファイル名部分のみを取得
# ファイル名が期待するパターンにマッチするかどうかを正規表現でチェック
# パターン: グループ名(スペース含む可能性あり) <半角スペース1つ> 数値 .拡張子
# ^(.*?) : 行頭から始まり、任意の文字列の最短マッチ (キャプチャグループ1: グループ名)
# ( ) : 半角スペース1つ (グループ名と数値の区切り)
# (\d+) : 1文字以上の数字 (キャプチャグループ2: 数値部分)
# \. : リテラルのドット
# $escaped_extension : エスケープされた拡張子文字列
# $ : 行末
if ($filename =~ /^(.*?) (\d+)\.$escaped_extension$/) {
my $group_name_from_file = $1; # キャプチャグループ1 (グループ名)
my $number = int($2); # キャプチャグループ2 (数値)、整数に変換
# ファイルの最終更新日時 (mtime) をエポック秒で取得
# (stat $filepath)[9] は stat 関数のリストの10番目の要素 (インデックス9) で、mtime を示します。
my $timestamp = (stat $filepath)[9];
unless (defined $timestamp) { # タイムスタンプが取得できなかった場合
warn "警告: ファイル '$filepath' の日時情報を取得できませんでした。スキップします。\n";
next; # このファイルの処理をスキップして次のファイルへ
}
# グループごとに、数値が最も大きいファイルを記録
# まだこのグループ名が %latest_files_in_group に存在しないか、
# または、現在のファイルの数値が記録されている数値より大きい場合に情報を更新
if ( (not exists $latest_files_in_group{$group_name_from_file}) or
($number > $latest_files_in_group{$group_name_from_file}{number}) ) {
$latest_files_in_group{$group_name_from_file} = {
name => $filename, # ファイル名 (basename)
number => $number, # 数値
timestamp => $timestamp, # 最終更新日時 (エポック秒)
path => $filepath # 元のファイルパス
};
}
}
}
# パターンにマッチするファイルが一つも見つからなかった場合の処理
unless (keys %latest_files_in_group) { # %latest_files_in_group ハッシュが空かどうかをチェック
print "指定された命名パターン(グループ名<半角スペース1つ>数値.$file_extension)にマッチするファイルが\n";
print "ディレクトリ '$target_directory' に見つかりませんでした。\n";
exit; # スクリプトを終了
}
# ==============================================================================
# ステップ 2: 各グループで数値が一番高いファイルの一覧を表示
# ==============================================================================
print "\n"; # 見やすさのための改行
print "------------------------------------------------------------------------------------------------\n"; # 幅変更(75+1+15 = 91)
print "各グループで数値が一番高いファイルの一覧 (対象: $target_directory/*.$file_extension):\n";
print "------------------------------------------------------------------------------------------------\n";
# printf でフォーマット指定して表示: %-75s (左寄せ75文字の文字列), %-15s (左寄せ15文字の文字列)
printf "%-75s %-15s\n", "ファイル名", "最終更新日時"; # ヘッダー表示
print "-" x (75+1+15), "\n"; # 区切り線の長さを調整 (75 + 1(スペース) + 15 = 91)
# グループ名でソートして表示するための準備
my @sorted_group_names = sort keys %latest_files_in_group;
my @all_latest_file_details; # 後のフィルタリング(古いファイルの抽出)で使用するため、詳細情報を一時保存する配列
# 各グループの最新ファイル情報を表示
foreach my $group_name_key (@sorted_group_names) {
my $file_info = $latest_files_in_group{$group_name_key}; #該当グループのファイル情報ハッシュリファレンス
# タイムスタンプ(エポック秒)を人間が読める日付形式 (YYYY-MM-DD) に変換
# POSIXモジュールのstrftime関数を使用: %Y(4桁年), %m(2桁月), %d(2桁日)
my $formatted_date = strftime "%Y-%m-%d", localtime($file_info->{timestamp});
printf "%-75s %-15s\n", $file_info->{name}, $formatted_date; # フォーマットして表示
# 後の処理(古いファイルの抽出)のために、グループ名も含むファイル情報を配列に保存
# %$file_info はハッシュリファレンスを展開して元のハッシュのキーと値をコピー
push @all_latest_file_details, { %$file_info, group_name_for_lookup => $group_name_key };
}
print "------------------------------------------------------------------------------------------------\n"; # 幅変更
# ==============================================================================
# ステップ 3: 指定された日数以上昔に更新されたファイルのみの一覧を表示 (リネーム前の名前も表示)
# ==============================================================================
print "\n"; # 見やすさのための改行
# ヘッダー幅: ファイル名(75) + スペース(1) + 更新日時(15) + スペース(1) + リネーム前(75) = 167
print "---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n";
print "上記一覧のうち、最終更新日が$days_threshold日以上前のファイルの一覧 (リネーム前情報付き):\n";
print "---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n";
# ヘッダー表示: ファイル名(左寄せ75文字)、最終更新日時(左寄せ15文字)、リネーム前(左寄せ75文字)
printf "%-75s %-15s %-75s\n", "ファイル名", "最終更新日時", "リネーム前";
print "-" x (75+1+15+1+75), "\n"; # 区切り線の長さを調整 (75 + 1 + 15 + 1 + 75 = 167)
my $current_time = time(); # 現在の時刻をエポック秒で取得
# 指定された日数($days_threshold)を秒数に変換
my $age_threshold_seconds = $days_threshold * 24 * 60 * 60;
# カットオフとなるタイムスタンプを計算 (これより古いタイムスタンプのファイルが対象)
my $cutoff_timestamp = $current_time - $age_threshold_seconds;
my $found_old_files_count = 0; # 条件に合致する古いファイルが見つかったかどうかのカウンター
# ステップ2で集めた最新ファイル情報のリストをループ処理
foreach my $file_detail_item (@all_latest_file_details) {
# ファイルのタイムスタンプがカットオフタイムスタンプより古い(小さい)か判定
if ($file_detail_item->{timestamp} < $cutoff_timestamp) {
my $current_filename = $file_detail_item->{name}; # 現在のファイル名 (リネーム後)
my $formatted_date = strftime "%Y-%m-%d", localtime($file_detail_item->{timestamp}); # 日付フォーマット
# リネームマップ検索用のキーを作成
# キー: ファイルから抽出したグループ名 + "." + 設定された拡張子 (例: "ggg aaa.txt")
my $group_for_lookup = $file_detail_item->{group_name_for_lookup};
my $lookup_key_for_rename_map = $group_for_lookup . '.' . $file_extension;
# %rename_map ハッシュからリネーム前の名前を検索
# exists演算子でキーが存在するか確認し、あればその値を、なければ "-" を表示
my $original_name_display = (exists $rename_map{$lookup_key_for_rename_map})
? $rename_map{$lookup_key_for_rename_map}
: "-"; # リネーム前の名前が見つからない場合はハイフンを表示
# フォーマットして表示
printf "%-75s %-15s %-75s\n", $current_filename, $formatted_date, $original_name_display;
$found_old_files_count++; # 古いファイルが見つかったのでカウントアップ
}
}
# もし古いファイルが一つも見つからなかった場合
if ($found_old_files_count == 0) {
print "最終更新日が$days_threshold日以上前のファイルはありませんでした。\n";
}
print "---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n";
print "\nスクリプトの実行が完了しました。\n";
# Perlスクリプトは実行後すぐに終了します。
# もし実行結果をコンソールで確認するために一時停止したい場合は、
# 以下のコメントアウトを解除してください。何かキーを押してEnterで終了します。
print "何かキーを押してEnterで終了します: "; <STDIN>;