CD コマンドの機能拡張

このところBATスクリプトの話が途絶えてたので、自作スクリプトの紹介。cd コマンドの pushd 化と csh 風拡張。移動はすべて pushd で行う。

   (explorerからcmdプロンプトへファイルをドラッグ&ドロップしたとき便利)

ディレクトリスタックの表示は、単に 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 が使えない( setlocalendlocal の間で pushd すると、endlocal で元に戻ってしまう。unixシェルスクリプト内で cd 出来ないのと同じ) ので環境変数を汚さないために for/f の制御変数に新旧のディレクトリをセットして比較せざるを得なかった。後で考えると、OLDCD のような環境変数を新設して使う手もあった。