UNIXライクなパス操作をwindowsのcmd.exeのバッチファイルのみで実現 2009/5/31Windows 2000, XP, Vistaには、cmd.exe(コマンドプロンプト)というCUI(キャラクタ・ユーザー・インターフェース)ベースのプログラムが付いている。バッチファイル(拡張子".bat")は、このプログラムによって実行される。 cmd.exeは、cscript.exeやwscript.exeと並んで、Windowsで処理を自動化する際の強い味方だ。 cmd.exeは、最初のころはあまり高機能でなく、込み入った処理に使うのは向かなかったが、コマンド プロセッサ拡張機能のバージョン(環境変数%CMDEXTVERSION%で確認できる)が2以上であれば、そこそこ便利に利用できる。 UNIXのシェル(sh, bash等)には及ばないものの、cmd.exeで手軽にできる範囲をまとめてみた。 関数(プロシージャ)の作成これまで、ある程度以上複雑なパッチファイルを読みやすく書くのは困難だった。バッチファイルでは、基本的に制御をgoto文でバッチファイルのあちこちに飛ばすことで処理フローを記述してきた。いわゆるgoto文によるスパゲッティプログラムになりがちだった。
goto文の拡張や、call文の拡張で、多少不格好ではあるが関数(プロシージャ)が書けるようになった。これで、バッチファイルを構造化するのが容易になった。 call :ラベル [引数1] [引数2] ... で「ラベル」でマークされた関数を呼び出し、 goto :EOF で関数からリターンする(トップレベルでこれを実行した場合、バッチファイルの実行を終了する)。 call文で渡した引数は、関数内で%1, %2等として参照できる。 下記は、バッチファイル版のechoコマンドだ。 @echo off if not CMDEXTVERSION 2 ( echo cmd.exeのバージョンが古い goto :EOF ) set progName=%0 if "%1" == "" ( rem 関数呼び出し call :usage goto :EOF ) echo %* goto :EOF rem 関数定義 :usage echo Usage: %progName% 引数1 [引数2] [引数3] ... echo 引数を標準出力に出力する。 goto :EOF パス関連情報の操作%~で始まるバッチパラメータの置換が拡張されたためパスの操作が比較的簡単になった。これを利用するとUNIXのdirnameやbasename,whichコマンドなどに相当する機能を実現できる。バッチパラメータ置換の拡張の使い方は、コマンドプロンプトで下記のように入力すると確認できる。 call /? ...前略 バッチ パラメータ (%n) の置換は拡張されました。次のオプション構文 を使うことができます: %~1 - すべての引用句 (") を削除して、 %1 を展開します。 %~f1 - %1 を完全修飾パス名に展開します。 %~d1 - %1 をドライブ文字だけに展開します。 %~p1 - %1 をパスだけに展開します。 %~n1 - %1 をファイル名だけに展開します。 %~x1 - %1 をファイル拡張子だけに展開します。 %~s1 - 展開されたパスは、短い名前だけを含みます。 %~a1 - %1 をファイル属性に展開します。 %~t1 - %1 をファイルの日付/時刻に展開します。 %~z1 - %1 をファイルのサイズに展開します。 %~$PATH:1 - PATH 環境変数に指定されているディレクトリを 検索し、最初に見つかった完全修飾名に %1 を 展開します。環境変数名が定義されていない場合、 または検索してもファイルが見つからなかった 場合は、この修飾子を指定すると空の文字列に 展開されます。 修飾子を組み合わせて、複合結果を得ることもできます: %~dp1 - %1 をドライブ文字とパスだけに展開します。 %~nx1 - %1 をファイル名と拡張子だけに展開します。 %~dp$PATH:1 - PATH 環境変数に指定されているディレクトリを 検索して %1 を探し、最初に見つかったファイル のドライブ文字とパスだけに展開します。 %~ftza1 - %1 を DIR の出力行のように展開します。 ...後略 標準出力を変数へUNIXでは、コマンドの実行結果をバッククォートで変数に簡単に代入できる。しかし、バッチファイルではそれができない。と思ったら、意外な方法でできる。for文に/fオプションをつけることで実現できる。詳しくはコマンドラインで下記のように入力すればよい。 for /? 下記は日付を取得してそれをファイル名として利用する例だ。 @echo off for /f %%i in ('date /t') do ( set date=%%i ) set logfile=AppLog-%date:/=-%.log someApplication > %logfile% 下記は、UNIXライクなコマンドを実現する。unix.batだ。 @echo off if not CMDEXTVERSION 2 ( echo cmd.exeのバージョンが古い goto :EOF ) setlocal set cmd=%1 shift rem %*がshiftされないので call :%cmd% %1 %2 %3 %4 %5 %6 %7 %8 %9 if defined _RESULT_ echo %_RESULT_% endlocal goto :EOF :fullpath rem 第一引数をフルパスで返す。 set _RESULT_=%~f1 goto :EOF :dirname rem 第一引数で指定されたディレクトリまたはファイルを含むディレクトリを返す。 rem 末尾に\が付いている。Ex. C:\temp\, C:\ set _RESULT_=%~dp1 goto :EOF :which rem PATHの中から第一引数で指定されたファイルに最初に一致するものを返す。 rem 拡張子(環境変数PATHEXTで指定されたもの)を自動的に補完しないので、"cmd.exe"のように拡張子も含めて指定すること。 set _RESULT_=%~$PATH:1 if not defined _RESULT_ echo PATHに%1は存在しません goto :EOF :basename rem 第一引数からドライブレター、パスを取り除きファイル名(ディレクトリ名)のみを返す。 set _RESULT_=%~nx1 if ""=="%2" goto :EOF if "%~x1"=="%2" set _RESULT_=%~n1 goto :EOF :read rem 入力された文字列を第一引数で指定した変数に格納する。 set /p %1= goto :EOF :ls rem ファイル名の一覧を標準出力へ出力する。 dir /B %1 goto :EOF |