CD コマンドの機能拡張
このところBATスクリプトの話が途絶えてたので、自作スクリプトの紹介。cd コマンドの pushd 化と csh 風拡張。移動はすべて pushd で行う。
- cd のみのときはディレクトリスタックの表示
- ホーム(%HOME%)への移動は cd ~
- cd - で前のディレクトリに戻る
- cd 存在するディレクトリ だとそこへ移動
- cd 存在するファイル だとそのファイルのあるディレクトリへ移動
(explorerからcmdプロンプトへファイルをドラッグ&ドロップしたとき便利)
- cd 英数字 で、その英数字が環境変数であり、その値が "?:\*" つまりフルパスのディレクトリならそこへ移動
- cd 文字列 で環境変数CDPATHを探索してオペラランドがそこにディレクトリとして存在すればそこに移動
- cd /s 文字列 だと、カレントディレクトリの下を再帰的に探して最初に見つかった該当ディレクトリに移動
ディレクトリスタックの表示は、単に pushd を使うと新しい順に縦に1つずつ表示されるが、古い順に横に並べたかった。for /f "delims=" %%A in ('pushd') doでは現在のディレクトリスタックを取得できない。'pushd' の出力は空となる。おそらく、'pushd' がサブシェルで実行され、そのサブシェルに現在のディレクトリスタック情報が継承されないためと思われる。そのため、一時ファイルを作らざるを得なかったのが少し残念。
このスクリプトを cdd.bat 等として、doskey cd=cdd $* と定義して使う。
実際使ってみると、環境変数名指定での移動機能までは要らなかったかなと思う。それを省けば20行ほど短く出来る。
@echo off if "%~1"=="" ( %引数が無ければ表示のみ% call :dirs goto :eof ) if "%~1"=="-" ( % - なら戻って表示% popd call :dirs goto :eof ) if "%~1"=="~" if defined HOME ( % ~ ならホームへ% call :pushd %HOME% goto :eof ) if exist "%~1" ( %存在するか?% if exist "%~1\" ( %ディレクトリか?% call :pushd "%~1" ) else ( %普通のファイルだ% call :pushd "%~dp1" ) goto :eof ) setlocal enabledelayedexpansion call :isname "%~1" if %ERRORLEVEL%==0 ( %英数字だけの名前か?% if defined %1 ( %環境変数名か?% set "Q=!%1!" if "!Q:~1,2!"==":\" if exist !%1!\ ( %内容がディレクトリ絶対パスか?% endlocal&call :pushd %%%1%% goto :eof ) ) ) set "P=%~$CDPATH:1" %CDPATHの探索% if defined P if exist "!P!\" ( %見つかった?% endlocal&call :pushd %~$CDPATH:1 goto :eof ) endlocal if /i "%1"=="/s" if not "%2"=="" for /r %%A in (%2) do if exist %%A\ ( %第一引数が /s ならサブディレクトリを探す% call :pushd %%A goto :eof ) call :pushd %* goto :eof :isname %英数字と一部の記号だけかチェック% setlocal enabledelayedexpansion set "W=%~1" for %%A in (@ A B C D E F G H I J K L M N O P Q R S T U V W X Y Z _ a b c d e f g h i j k l m n o p q r s t u v w x y z 0 1 2 3 4 5 6 7 8 9) do ( if not defined W endlocal&exit /b 0 set "W=!W:%%A=!" ) endlocal exit /b 1 :pushd %pushdが成功しても元と同じならpopdする(履歴を残さないため)% for /f "delims=" %%X in ("%CD%") do ( pushd %* if not ERRORLEVEL 1 for /f "delims=" %%Y in ('echo %%CD%%') do ( if "%%X"=="%%Y" popd ) ) goto :eof :dirs %ディレクトリスタックを古い順に横に並べて表示する% setlocal enabledelayedexpansion set TEMPFILE="%TEMP:"=%\cd$$%TIME::=.%.tmp" pushd > %TEMPFILE% set DIRS= for /f "usebackq delims=" %%A in (%TEMPFILE%) do set "DIRS=%%A !DIRS!" del %TEMPFILE% 2>NUL if defined DIRS echo %DIRS% endlocal goto :eof
ディレクトリスタックに同じものが連続しないように工夫。そのために :pushd というサブルーチンを作ったが、setlocal が使えない( setlocal と endlocal の間で pushd すると、endlocal で元に戻ってしまう。unix の シェルスクリプト内で cd 出来ないのと同じ) ので環境変数を汚さないために for/f の制御変数に新旧のディレクトリをセットして比較せざるを得なかった。後で考えると、OLDCD のような環境変数を新設して使う手もあった。