CD コマンドの機能拡張 第三版

CD コマンドの機能拡張 第二版」以来、16年ぶりの大幅修正。機能は変えていないが、リファクタリングで機能追加がシンプルになった。

cdd.batという名前で作って、doskey cd=cdd $*でaliasを定義して使う。

機能は、

  • 移動はすべて pushd で行う。
  • cd のみのときはディレクトリスタックの表示
  • ホーム(%HOME%)への移動は cd ~
  • cd - で前のディレクトリに戻る
  • cd + で現ディレクトリと一つ前のディレクトリの交換
  • cd 存在するディレクトリ だとそこへ移動
  • cd ショートカット の場合、ショートカット先がディレクトリならそこへ移動し、ファイルだとそのファイルのあるディレクトリへ移動
  • cd 存在するファイル だとそのファイルのあるディレクトリへ移動 (explorerからcmdプロンプトへファイルをドラッグ&ドロップしたとき便利)
  • cd それ以外 の場合環境変数CDPATHを探索してオペラランドがそこにディレクトリとして存在すればそこに移動

ほとんどの機能はモジュール化していて、ファイルの最後で定義する。

  • 指定文字列 移動先ディレクトリ
  • 指定文字列 サブルーチン

サブルーチンの1つとして、RubyのGemsディレクトリへの移動を書いてみた。 Firefoxのプロファイルディレクトリも、profiles.iniを読んで決めるようにしようかと思ったけど、面倒くさいわりにメリットが無いのでやめた。

@echo off
rem 定義済みの移動先ならそこに移動する
call :PreDefined "%~dpnx0" "%~1"
if not ERRORLEVEL 1 exit /b

rem 存在するディレクトリ、ファイル、ショートカットか?
rem ファイルの場合はそのディレクトリに移動する
if exist "%~1" (
  dir /b/ad "%~1" >NUL 2>NUL
  if not ERRORLEVEL 1 (
    call :Pushd "%~1"
  ) else (
    if /i "%~x1"==".lnk" (
      call :Shortcut "%~1"
      if not ERRORLEVEL 1 exit /b
    )
    call :Pushd "%~dp1"
  )
  exit /b
)

rem %CDPATH% に存在するディレクトリなら、そこに移動する
if not "%~$CDPATH:1"=="" dir /b/ad "%~$CDPATH:1" >NUL 2>NUL && (
  call :Pushd %~$CDPATH:1
  exit /b
)

:Pushd
rem 移動用処理
rem 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
  )
)
exit /b

:Shortcut
rem ショートカットファイルのリンク先を調べてそこに移動する
setlocal enabledelayedexpansion
set "SV="
set "FILE="
for /f "delims=" %%A in ('more ^< "%~1"') do (
  set A=%%A
  if "!A:~1,2!"==":\" for %%B 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) do if "!A:~0,1!"=="%%B" set "FILE=%%A"
  if defined SV (if defined FILE (set "FILE=!FILE!%%A")else set "FILE=!SV!\%%A")&set "SV="
  if "!A:~0,2!"=="\\" set "SV=%%A"
)
if defined FILE endlocal&call :Pushd "%FILE%"&exit /b 0
rem リンク先が見つからない場合
exit /b 1

:PreDefined
rem 定義済み移動先の処理を行う
rem 移動先ディレクトリ名 または 呼び出すサブルーチン名
setlocal enabledelayedexpansion
set "DATA="
for /f "usebackq tokens=1*" %%A in (%1) do (
  if /i "%%~A" == "__DATA__" (
    set DATA=1
  ) else if defined DATA (
    set "X=%%~B"
    if /i "%%~A" == "%~2" if "!X:~0,1!" == ":" (
      endlocal
      call %%~B %*
      exit /b 0
    ) else (
      endlocal
      call :Pushd "%%~B"
      exit /b 0
    )
    if /i "%~2" == ":Help" echo.%%A %%B
  )
)
if /i "%~2" == ":Help" exit /b 0
rem 定義済み移動先に存在しなかった
exit /b 1

rem ここから個別処理
:Dirs
rem ディレクトリスタックを横に並べて表示する
  setlocal enabledelayedexpansion
  set TEMPFILE="%TEMP:"=%\cd$$.tmp"
  pushd > %TEMPFILE%
  set "DIRS=%CD%"
  for /f "usebackq delims=" %%A in (%TEMPFILE%) do set "DIRS=!DIRS! %%~A"
  del %TEMPFILE% 2>NUL
  set /p "=%DIRS%"<NUL
  exit /b

:Back
rem 1つ前のディレクトリへ
  popd
  goto :Dirs

:Swap
rem 1つ前のディレクトリに移動して、履歴に今のディレクトリを入れる
  for /f "delims=" %%X in ('echo "%%CD%%"') do (
    popd
    for /f "delims=" %%Y in ('echo "%%CD%%"') do (
      popd
      for /f "delims=" %%Z in ('echo "%%CD%%"') do (
      if not "%%~Y"=="%%~Z" (pushd %%X) else (cd /d %%X) 
      if not "%%~X"=="%%~Y"  pushd %%Y
  )))
  goto :Dirs

:Help
rem 定義済み移動先の一覧の表示
  call :PreDefined %1 :Help
  exit /b

:RubyGems
rem RubyGemsのディレクトリへ
  for /f "tokens=1-3 delims=." %%A in ('ruby -e "puts RUBY_VERSION"') do (
    call :Pushd "D:\Ruby\lib\ruby\gems\%%A.%%B.0\gems"
  )
  exit /b

rem ここから移動先定義
__DATA__
""  :Dirs
-   :Back
+   :Swap
/?  :Help
~   %HOME%
/   %TEMP%
//  D:\Temp
/e  %windir%\system32\drivers\etc
/f  %APPDATA%\Mozilla\Firefox\Profiles\xxxxxxxx.20180101
/g  :RubyGems