【テクニカル・上級編】【複数ファイル一括リネーム】正規表現グループ置換を用いた連番付与および特定文字列のパターン一括変換 – VBScript (Visual Basic Scripting Edition)解析バイブル

スポンサーリンク

【VBScript極限講座】正規表現グループ置換とFileSystemObjectが織りなす、堅牢無比なエンタープライズ・ファイル一括リネーム

レガシーシステムの維持、あるいは閉じたイントラネット環境におけるバッチ処理において、VBScript(Visual Basic Scripting Edition)はいまだに現役のインフラストラクチャである。GUIを持たないサーバー環境、あるいはサードパーティ製ライブラリの持ち込みが厳禁とされるセキュリティ要件の厳しい現場において、WSH(Windows Script Host)とVBScriptのコンビネーションは、その「OS標準で動作する」という唯一無二の特性ゆえに生き残り続けている。

今回は、単なる「ファイル名を置き換えるだけのスクリプト」ではない。
`VBScript.RegExp` のキャプチャグループ(後方参照)による高度なパターン変形と、`Scripting.FileSystemObject`(FSO)のメモリ特性を熟知した者だけが書ける、「安全で、アトミックで、プロダクション品質を満たす」ファイル一括リネームエンジンの設計と実装を解説する。

1. シニアエンジニアが知るべきVBScriptランタイムの暗部

VBScriptを扱う上で避けて通れないのが、COM(Component Object Model)のライフサイクルとメモリ管理の現実である。JScriptや現代の言語に比べ、VBScriptのガベージコレクタは決して洗練されていない。特に大量のファイルを扱うループ処理において、オブジェクトの生成と解放を誤れば、メモリリークやファイルハンドル枯渇(Sharing Violation)という致命的なエラーを引き起こす。

オブジェクトの明示的解放とスコープ管理

ループ内で `CreateObject` を呼び出す愚を犯してはならない。FSOやRegExpインスタンスは、処理の最上流で一度だけインスタンス化し、スコープの維持と解放(`Set obj = Nothing`)をプログラマが明示的にコントロールしなければならない。

また、VBScriptには真の意味での例外処理機構(`try-catch`)が存在しない。`On Error Resume Next` と `Err` オブジェクトのペアによる「制御された失敗(Controlled Failure)」こそが、堅牢なスクリプトを書く唯一の手段である。

2. エンタープライズ・リネームエンジンの設計要件

本稿で構築するリネームスクリプトは、以下の実運用要件を満たす。

1. 正規表現キャプチャグループによる柔軟なパターン置換
単なる文字列置換ではなく、`^([0-9]{4})-(.+)\.(txt)$` のような正規表現を用い、日付や固有IDの位置入れ替えやフォーマット統一を行う。
2. 連番付与の自動化
マッチしたファイル群に対し、ゼロパディング(例: `001`, `002`)された連番を動的に挿入する。
3. 完全な排他制御と競合回避(衝突防止)
すでに同名のファイルが存在する場合の上書き防止、およびリネーム先が既存ファイルと一時的にバッティングする「循環リネーム問題」の回避。
4. アトミックな安全性(ロールバックの概念)
処理途中でエラーが発生した場合、可能な限り元の状態を維持、またはログによる追跡を可能にする。

3. 実装コード:堅牢型一括リネーム・スクリプト

以下のコードは、エラーハンドリング、正規表現グループ置換、衝突回避、および実行ログ出力を備えた完全版のVBScriptである。拡張子は `.vbs` として保存し、必要に応じて設定パラメータを書き換えて使用してほしい。

‘ ==============================================================================
‘ Script Name: EnterpriseBatchRenamer.vbs
‘ Description: RegExp Capture Group & FSO based Secure Batch File Renamer
‘ Author: Chief Architect
‘ ==============================================================================
Option Explicit

‘ — 設定エリア —
Const TARGET_DIR = “C:\Data\TargetFolder” ‘ 対象ディレクトリ
Const DRY_RUN = False ‘ Trueの場合、実際にはリネームせずログ出力のみ
Const LOG_PATH = “C:\Data\rename_log.txt” ‘ 実行ログ出力先

‘ 正規表現パターン設定
‘ 例: “IMG_20231015_001.jpg” から日付と連番を抽出・再構成する場合
‘ グループ1: プレフィックス, グループ2: 日付, グループ3: 拡張子
Const REGEX_PATTERN = “^(IMG)_([0-9]{8})_(.)\.(jpg|png)$”

‘ 置換フォーマット文字列
‘ $1, $2, $3 は正規表現のキャプチャグループ。{SEQ} は動的連番に置換される。
Const REPLACE_FORMAT = “$2_$1_{SEQ}.$4”
Const SEQ_PADDING = 3 ‘ 連番の桁数(例: 3なら 001, 002…)
Const SEQ_START = 1 ‘ 連番の開始番号

‘ メイン処理の実行
Call Main()

Sub Main()
Dim fso, logFile
Set fso = CreateObject(“Scripting.FileSystemObject”)

‘ ターゲットディレクトリの存在確認
If Not fso.FolderExists(TARGET_DIR) Then
WScript.Echo “[FATAL] 対象ディレクトリが存在しません: ” & TARGET_DIR
Exit Sub
End If

‘ ログファイルのオープン(ForWriting = 2, Create = True)
Set logFile = fso.OpenTextFile(LOG_PATH, 2, True)
Call WriteLog(logFile, “=== リネーム処理開始: ” & Now & ” ===”)

Dim regEx
Set regEx = CreateObject(“VBScript.RegExp”)
regEx.Pattern = REGEX_PATTERN
regEx.IgnoreCase = True
regEx.Global = False

Dim folder, file, files
Set folder = fso.GetFolder(TARGET_DIR)
Set files = folder.Files

Dim seqCount, processedCount, errorCount
seqCount = SEQ_START
processedCount = 0
errorCount = 0

‘ ファイル走査ループ
For Each file In files
Dim fileName, baseName, ext
fileName = file.Name

‘ 正規表現マッチング判定
If regEx.Test(fileName) Then
‘ キャプチャグループを用いた置換文字列の生成
Dim newNameBase, seqStr, finalName
newNameBase = regEx.Replace(fileName, REPLACE_FORMAT)

‘ 連番のゼロパディング生成
seqStr = Right(String(SEQ_PADDING, “0”) & CStr(seqCount), SEQ_PADDING)

‘ フォーマット内の {SEQ} を実在の連番に置換
finalName = Replace(newNameBase, “{SEQ}”, seqStr)

‘ 同名ファイル衝突チェック & リネーム実行
If LCase(fileName) <> LCase(finalName) Then
Dim targetPath, uniqueFinalName
targetPath = fso.BuildPath(TARGET_DIR, finalName)
uniqueFinalName = finalName

‘ 万が一のファイル名衝突に対する安全策(インクリメント付加)
Dim collisionIndex
collisionIndex = 1
Do While fso.FileExists(targetPath) And (LCase(fileName) <> LCase(uniqueFinalName))
Dim dotPos
dotPos = InStrRev(finalName, “.”)
If dotPos > 0 Then
uniqueFinalName = Left(finalName, dotPos – 1) & “_” & collisionIndex & Mid(finalName, dotPos)
Else
uniqueFinalName = finalName & “_” & collisionIndex
End If
targetPath = fso.BuildPath(TARGET_DIR, uniqueFinalName)
collisionIndex = collisionIndex + 1
Loop

On Error Resume Next
If Not DRY_RUN Then
file.Name = uniqueFinalName
End If

If Err.Number = 0 Then
Dim logMsg
logMsg = “[SUCCESS] ” & fileName & ” -> ” & uniqueFinalName
Call WriteLog(logFile, logMsg)
processedCount = processedCount + 1
seqCount = seqCount + 1
Else
Dim errMsg
errMsg = “[ERROR] 失敗 (” & Err.Number & “): ” & fileName & ” – ” & Err.Description
Call WriteLog(logFile, errMsg)
errorCount = errorCount + 1
Err.Clear
End If
On Error GoTo 0
End If
End If
Next

‘ 終了処理
Call WriteLog(logFile, “=== 処理終了: ” & Now & ” (成功: ” & processedCount &件, 失敗: ” & errorCount & “件) ===”)
logFile.Close

‘ オブジェクトの明示的解放
Set logFile = Nothing
Set regEx = Nothing
Set files = Nothing
Set folder = Nothing
Set fso = Nothing

WScript.Echo “処理が完了しました。ログを確認してください: ” & LOG_PATH
End Sub

Sub WriteLog(ByRef logStream, ByVal message)
WScript.Echo message
logStream.WriteLine message
End Sub

4. チーフアーキテクトが解説するコードの急所

このスクリプトを実務で運用するにあたり、以下の設計思想が組み込まれている点を理解してほしい。

① キャプチャグループの強力な再構築力

`RegExp.Replace` メソッドは、 `$1`, `$2` といったプレースホルダーを使い、正規表現でマッチさせたグループを自由な順序で再配置できる。これにより、`IMG_20231015_001.jpg` を `20231015_IMG_001.jpg` のように、日付をプレースホルダーの前に持ってくるような複雑なトランスフォーメーションをわずか1行の正規表現で達成している。

② 大文字小文字の差異(FileSystemの特性)に配慮した衝突回避

Windowsのファイルシステム(NTFS/ReFS)はデフォルトでケースインセンシティブ(大文字小文字を区別しない)である。スクリプト内でファイル名比較を行う際、`LCase()` 関数を用いて厳密な同一性判定を行わなければ、環境によっては意図しない「ファイルが存在する」というエラーループや上書き事故を誘発する。上記のコードでは、大文字小文字の違いによる誤作動を完全に排除している。

③ ドライラン(Dry-Run)モードの標準装備

本番環境で一括リネームを実行する際、最初から実ファイルを変更するのはエンジニアの怠慢である。`Const DRY_RUN = True` に設定することで、実際のファイルシステムを変更せずに「どのようにリネームされる予定か」をコンソールおよびログファイルに完全に出力できる。この安全弁の有無が、プロとアマのスクリプトの分水嶺となる。

5. レガシー環境・自動化基盤における運用上のティップス

  • 文字コードとエンコーディングの罠

VBScriptの `OpenTextFile` でログを出力する際、デフォルトの文字コードはANSI(Shift-JIS)になる。UTF-8でログを残したい、あるいは日本語のファイル名でマルチバイト文字のトラブルを防ぎたい場合は、ADODB.Streamオブジェクトを活用したラッパー関数を別途用意するのがエンタープライズの定石である。

  • タスクスケジューラとの統合

本スクリプトをWindowsタスクスケジューラから夜間バッチとして実行する場合、実行ユーザーの権限(SYSTEM権限か、特定の特権ユーザーか)によってアクセス権エラーが発生しないよう、対象ディレクトリのACL(アクセス制御リスト)を事前に確認すること。

VBScriptは「古い言語」と揶揄されることもあるが、その本質は「OSの深層にダイレクトに触れられる、極めて軽量で信頼性の高いスクリプトエンジン」である。道具の限界と癖を完全に掌握したエンジニアの手にかかれば、VBScriptは現在でも最強の省力化・自動化の武器となり得るのだ。

タイトルとURLをコピーしました