0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

bat&PowerShellでフォルダ一括作成ツールをgeminiと作ってみた。

0
Last updated at Posted at 2026-02-15

プロジェクトの初期構築とか、資料整理のとき
「右クリック→新規作成→フォルダ→名前変更→ダブルクリックして中に入る…」
みたいなのを無限に繰り返す作業、虚無すぎませんか?

「テキストエディタで階層を書いたら、そのままフォルダ構成として実体化してほしい」

というワガママを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キーで終了"
}

使い方

  1. 設計図を作る:
    以下のようなテキストファイルを作成します(例: 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))
}

まとめ

要件定義や基本設計の段階で、「とりあえずこの構成でフォルダ切っておいて」と言われた時に、エクスプローラーでカチカチやるより断然早いです。
フォルダ構成をテキストで共有して、各自がこれを走らせれば構成ミスも防げます。

前回の検索ツールと合わせて、デスクトップの片隅に置いておくと地味に幸せになれるかもしれないツールでした。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?