プロジェクトの初期構築とか、資料整理のとき
「右クリック→新規作成→フォルダ→名前変更→ダブルクリックして中に入る…」
みたいなのを無限に繰り返す作業、虚無すぎませんか?
「テキストエディタで階層を書いたら、そのままフォルダ構成として実体化してほしい」
というワガママをGeminiに投げたら、「Folder Bomb(フォルダボム)」 なる物騒な名前のバッチファイルを作ってくれました。
これも前回同様、「外部ツール不要」「バッチファイル1つ」「ドラッグ&ドロップで完了」 の爆速仕様です。
どんなツール?
- テキスト駆動:メモ帳などでインデント(スペースやタブ)を付けて書いた見た目通りのフォルダ階層を作ります。
- D&D対応:構成を書いたテキストファイルを、このバッチに放り込むだけで生成開始。
-
コメントアウト機能:行頭に
#をつければ無視されるので、メモを残せます。
ソースコード
以下のコードをコピペして、拡張子 .bat (例:FolderBomb.bat)で保存してください。
保存時の文字コードは UTF-8 (BOMなし) 推奨。
使う際は自己責任で!
<# :
@echo off
set "TARGET=%*"
cls
set "MY_PATH=%~f0"
powershell -NoProfile -ExecutionPolicy Bypass -Command "try { Invoke-Expression ([System.IO.File]::ReadAllText($env:MY_PATH, [System.Text.Encoding]::UTF8)) } catch { Write-Host '[CRITICAL ERROR]' -ForegroundColor Red; Write-Host $_; pause; exit 1 }"
if %errorlevel% neq 0 pause
exit /b
#>
# ==========================================
# FOLDER BOMB - フォルダ一括生成
# ==========================================
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
try {
function Invoke-FolderBomb {
param($filePath)
if (-not (Test-Path $filePath -PathType Leaf)) {
Write-Host " [エラー] ファイルが見つかりません: $filePath" -ForegroundColor Red
return
}
# ベースディレクトリを確実に絶対パスで取得
$fileItem = Get-Item $filePath
$baseDir = $fileItem.DirectoryName
Write-Host " --------------------------------------------------"
Write-Host " File: $($fileItem.Name)" -ForegroundColor Yellow
Write-Host " Base: $baseDir" -ForegroundColor DarkGray
Write-Host " --------------------------------------------------"
try {
$lines = [System.IO.File]::ReadAllLines($filePath, [System.Text.Encoding]::UTF8)
} catch {
$lines = [System.IO.File]::ReadAllLines($filePath, [System.Text.Encoding]::GetEncoding(932))
}
# スタック: 現在の「親」たちのリスト (ハッシュテーブルで管理)
# @{ Name="フォルダ名"; Indent="インデント文字列" }
$stack = [System.Collections.Generic.List[Object]]::new()
$success = 0
$error = 0
foreach ($line in $lines) {
# 空行・コメントスキップ
if ([string]::IsNullOrWhiteSpace($line) -or $line.TrimStart().StartsWith("#")) { continue }
# --- インデント文字列をそのまま取得 ---
if ($line -match "^(\s*)(.*)") {
$currIndent = $matches[1]
$folderName = $matches[2].TrimEnd()
}
if ($folderName -eq "") { continue }
# --- 階層判定 (文字列比較ロジック) ---
# スタックの一番上(直近の親候補)とインデントを比較
while ($stack.Count -gt 0) {
$lastItem = $stack[$stack.Count - 1]
# インデント比較:
# 1. 親のインデントで始まっている (StartsWith)
# 2. かつ、親より長い (Length >)
# これを満たせば「子供」なので、ループを抜ける(今のスタックが親になる)
if ($currIndent.StartsWith($lastItem.Indent) -and $currIndent.Length -gt $lastItem.Indent.Length) {
break
}
# 満たさないなら「兄弟」か「叔父」なので、スタックから外してさらに上の親を探す
$stack.RemoveAt($stack.Count - 1)
}
# --- パス作成 (Join-Pathを使わず配列結合で安全化) ---
# スタックにある名前 + 自分の名前 をパス区切り文字で結合
$pathParts = [System.Collections.Generic.List[string]]::new()
foreach ($item in $stack) {
$pathParts.Add($item.Name)
}
$pathParts.Add($folderName)
$relPath = $pathParts -join [System.IO.Path]::DirectorySeparatorChar
$fullPath = Join-Path $baseDir $relPath
# --- フォルダ作成 ---
try {
if (-not (Test-Path $fullPath)) {
New-Item -ItemType Directory -Path $fullPath -Force | Out-Null
Write-Host " [+] 作成: $relPath" -ForegroundColor Green
$success++
} else {
Write-Host " [.] 既存: $relPath" -ForegroundColor DarkGray
}
} catch {
Write-Host " [x] エラー: $relPath" -ForegroundColor Red
$error++
}
# 自分をスタックに追加 (次の行の親候補として)
$newItem = @{ Name = $folderName; Indent = $currIndent }
$stack.Add($newItem)
}
Write-Host " --------------------------------------------------"
Write-Host " 結果: 作成 $success / エラー $error" -ForegroundColor Cyan
Write-Host ""
}
if (-not [string]::IsNullOrWhiteSpace($env:TARGET)) {
$argsList = $env:TARGET -split '"\s+"'
foreach ($rawPath in $argsList) {
$path = $rawPath.Trim('"').Trim("'")
if ($path) { Invoke-FolderBomb -filePath $path }
}
Write-Host " 終了するには Enter キーを押してください..." -ForegroundColor Gray
Read-Host
} else {
Clear-Host
Write-Host "==================================================" -ForegroundColor Yellow
Write-Host " FOLDER BOMB - フォルダ一括作成ツール " -ForegroundColor Yellow
Write-Host " ==================================================" -ForegroundColor Yellow
Write-Host " "-NoNewline
Write-Host "[使い方]" -ForegroundColor Magenta
Write-Host " フォルダ構成を書いたテキストファイルを"
Write-Host " このbatファイルにドラッグ&ドロップしてください。"
Write-Host ""
Write-Host " "-NoNewline
Write-Host "[書き方]" -ForegroundColor Magenta
Write-Host " ・インデント(TAB or 半角スペース2個)で階層になります"
Write-Host " ・行頭に # をつけると無視されます"
Write-Host ""
Write-Host " フォルダ構成を指定するテキストの例" -ForegroundColor Yellow
Write-Host " --------------------------------------------------"
Write-Host " げっ歯類" -ForegroundColor DarkBlue
Write-Host " キヌゲネズミ科" -ForegroundColor DarkCyan
Write-Host " ゴールデンハムスター" -ForegroundColor Cyan
Write-Host " ジャンガリアンハムスター" -ForegroundColor Cyan
Write-Host " ロボロフスキーハムスター" -ForegroundColor Cyan
Write-Host " チャイニーズハムスター" -ForegroundColor Cyan
Write-Host " リス科" -ForegroundColor DarkCyan
Write-Host " シマリス" -ForegroundColor Cyan
Write-Host " プレーリードッグ" -ForegroundColor Cyan
Write-Host " マーモット" -ForegroundColor Cyan
Write-Host " --------------------------------------------------"
Write-Host ""
Read-Host " Enterキーで終了"
}
} catch {
Write-Host " [予期せぬエラー] $($_.Exception.Message)" -ForegroundColor Red
Write-Host " $($_.ScriptStackTrace)" -ForegroundColor Gray
Read-Host " Enterキーで終了"
}
使い方
-
設計図を作る:
以下のようなテキストファイルを作成します(例:project_tree.txt)。
インデントはタブでもスペースでもOKです。
Project_Alpha
docs
仕様書
設計書
# ここは後で追加
# manual
src
main
java
resources
test
assets
images
fonts
2. 爆破(実行):
作成したテキストファイルを、FolderBomb.bat アイコンの上に ドラッグ&ドロップ します。
3. 完了:
テキストファイルがある場所に、記述通りのフォルダ階層が一瞬で生成されます。
※何もファイルをドロップせずにダブルクリックするとヘルプ画面が出ます。
技術的な解説
今回もgeminiくんにいろいろ無茶振りさせて面白いのができました。
1. スタックによる親判定
再帰関数を使わず、「スタック(List)」 で親フォルダを管理しています。
- 行を読み込むたびに、その行のインデントの長さをチェック。
- スタックにある直近のフォルダ(=現在の親候補)と比較。
- 親よりインデントが深い → 「子」 と判定(パスに追加)
- 親と同じか浅い → 「兄弟」または「叔父」 と判定(スタックから親を取り除く)
これにより、インデントが深くなったり浅くなったりする構造を、上から順に読み込むだけで解析しています。
# インデント比較ロジックの一部
while ($stack.Count -gt 0) {
$lastItem = $stack[$stack.Count - 1]
# 現在の行が、直近の親のインデントで始まっていて、かつ長いなら「子」
if ($currIndent.StartsWith($lastItem.Indent) -and $currIndent.Length -gt $lastItem.Indent.Length) {
break
}
$stack.RemoveAt($stack.Count - 1) # 親じゃなかったら遡る
}
2. 文字コードの自動判定(try-catch)
WindowsのテキストファイルはShift-JISだったりUTF-8だったり面倒ですが、PowerShell側で強引に解決しています。
まずUTF-8で読んでみて、失敗したらShift-JIS (CP932) で読み直すという力技です。
try {
$lines = [System.IO.File]::ReadAllLines($filePath, [System.Text.Encoding]::UTF8)
} catch {
$lines = [System.IO.File]::ReadAllLines($filePath, [System.Text.Encoding]::GetEncoding(932))
}
まとめ
要件定義や基本設計の段階で、「とりあえずこの構成でフォルダ切っておいて」と言われた時に、エクスプローラーでカチカチやるより断然早いです。
フォルダ構成をテキストで共有して、各自がこれを走らせれば構成ミスも防げます。
前回の検索ツールと合わせて、デスクトップの片隅に置いておくと地味に幸せになれるかもしれないツールでした。