バッチファイルでカレントディレクトリ以下の階層を表示する


カレントディレクトリ以下のファイルをサブディレクトリも含めて、階層を表示してくれるバッチファイルを作成してみましょう。サイトの差分バックアップをとった際に、このバッチファイルで内容を書き出し、必要なバックアップが含まれているか確認することができます。

まずは、完成したソースを見てましょう。

```bat
@echo off
setlocal enabledelayedexpansion
echo 実行ディレクト以下のファイルの階層を表示します。
echo ※空のディレクトリは表示されません。
set /p output=出力するテキストファイル名を決めて下さい:

echo 出力形式を数字で選んで下さい。
echo 0. 木構造
echo 1. ディレクトリパス
set /p pattern=出力形式:

if %pattern%==0 (
    goto :d-tree
) else (
    goto :d-pass
)
goto :EOF

rem 木構造で階層を表示
:d-tree
    rem 現在のフォルダ名を取得
    set mydir=%cd%
    :loop_mydir
    set mydir=%mydir:*\=%
    rem \が残っているかをチェック
    if not "%mydir:*\=%"=="%mydir%" (goto loop_mydir)
    rem ルートディレクトリを追記
    echo %mydir% >> %output%.txt

    rem ルート以下のディレクトリを表示
    for /f "delims=^: skip=3" %%i in ('tree /f .') do (
        rem 2つの文字列を含まないのなら追記
        echo %%i | find /v "ECHO は <ON> です。" | find /v "%~n0%~x0" >> %output%.txt
    )
exit /b

rem ディレクトリパスで階層を表示
:d-pass
    rem フルパスの文字数を取得し、strlenに格納
    call :GET_STRLEN %cd%

    rem dirの各行を繰り返し処理
    for /f %%i in ('dir /b /s /a-d') do (
        set result=%%i
        rem カレントまでのパスを取り除く
        set pass=!result:~%strlen%!

        rem 実行バッチファイル以外を出力
        if not !pass!==\%~n0%~x0 (
            echo !pass:\=/! >> %output%.txt
        )
)
exit /b

endlocal

rem 文字長を取得する
rem 使用法 call :GET_STRLEN (対象の文字列)
:GET_STRLEN
    set s=%1
    set strlen=0
    :LOOP_HEAD
    if defined s (
        set s=%s:~1%
        set /a strlen+=1
        goto :LOOP_HEAD
    )
exit /b
```

今回、階層表示については2通りのパターンが選べるようになっています。木構造での表示と、ディレクトリパスでの表示です。最初に数字で選択してもらい、後は2つのサブルーチンに分岐します。ちなみに、どちらもテキストファイルに出力します。

### 木構造での表示

木構造の表示はtreeコマンドで行うことができるので、すぐにできます。

```
site 
│  about.html 
│  index.html 
│  sample.bat 
│   
└─link 
    │  index.html 
    │   
    └─img 
            sample01.jpg 
            sample02.jpg 
            sample03.jpg 
```

しかし、ただtreeコマンドでそのまま表示するとルートディレクトリがフルパスで表示されてしまいます。そのため、上記のようにカレントディレクトリだけを表示するために、別途カレントディレクトリ取得し、最初に出力しておきます。

カレントディレクトリの取得は下記を参考にしました。

[コマンドプロンプト - ディレクトリパスから最下のディレクトリ名を取得する。 - 日々量産](http://d.hatena.ne.jp/ryousanngata/20090913/1252824783)

そして、treeコマンドの出力結果1行ごとにfor文に渡しています。forに/fのオプションを設定するとセットの中身を1行ずつ読み込みます。これでカレントディレクトリ以下を書き出していきます。さらに、最初の余分な行も取り除いています。

> `for /F "options" %%v in ( ファイルセット ) do コマンド`
>
> 参考：
> <cite><a href="http://www.atmarkit.co.jp/ait/articles/0106/23/news004_2.html" target="_blank">Windows 2000 活用講座Windows 2000 コマンドライン徹底活用：第８回　forコマンド（その2） (2/2) - ＠IT</a></cite>

ちなみにfor以下の中身ですが、echoの出力にfindでフィルタをかけています。

```bat
for /f "delims=^: skip=3" %%i in ('tree /f .') do (
    rem 2つの文字列を含まないのなら追記
    echo %%i | find /v "ECHO は <ON> です。" | find /v "%~n0%~x0" >> %output%.txt
)
```

何故かfor文で各行を書き出すと最後に、`ECHO は <ON> です。`と表示されてしまうので、これを表示しないようにしています。もう1つのフィルタは実行バッチファイル自身を書き出さないようにしています。

findコマンドの基本的な使い方は下記が参考になります。

[Windows 2000 活用講座　Windows 2000 コマンドライン徹底活用：第6回　findコマンド (1/2) - ＠IT](http://www.atmarkit.co.jp/ait/articles/0103/01/news001.html)

以上の処理で、実行バッチファイルのカレントディレクトリ以下のファイル構造を木構造で書き出すことができます。

### ディレクトリパスでの表示

d-passのサブルーチンでは、さらにGET_STRLENという文字列長を取得するサブルーチンを使っています。まず、dirの中身を結果をfor文で処理します。

dirのオプションは下記を参考

[Windows TIPS：dirコマンドでファイル名の一覧を取得する - ＠IT](http://www.atmarkit.co.jp/ait/articles/0412/04/news014.html)

for文の中で各フルパスからカレントパスまでを取り除きます。これは、指定修飾子使用して文字列の表示開始位置を指定することで実現しています。

文字の抽出の指定修飾子については下記を参考

[Windows TIPS：バッチ・ファイル中で日付をファイル名に使用する - ＠IT](http://www.atmarkit.co.jp/ait/articles/0405/01/news002.html)

これで、ディレクトリパスの加工は完了したので、あとは実行バッチファイル以外のディレクトリパスを書き出せば処理は終了です。