やりたいこと
Windows のコマンドプロンプトの CD コマンドは,デフォルトでは異なるドライブへのパスを指定してもドライブまでは移動しません。ドライブを移動したい場合はオプション /D を追加すればよいので,デフォルトでオプション /D が有効になるよう DOSKEY コマンドを使って下記のようなマクロを定義するとしましょう。
doskey CD=CD $*
ところが CD コマンドにはディレクトリを移動する以外にもう一つ機能があり,引数なしで実行するとカレントディレクトリを表示してくれるというものです。このため上記のエイリアスを定義してしまうと,今度は引数なしで CD コマンドを呼び出すとエラーになってしまうのです。
また,わざわざオプション /D を付けてディレクトリを移動しようとするとエラーになってしまいます。これはオプションを二重に指定するとエラーになるというより,第一引数以外ではオプション /D を指定できないという理解のほうが正しいと思います。
ということで引数なしで実行した場合はそのまま,第一引数がオプション /D の場合もそのまま,第一引数がオプション /D 以外の場合はオプションを追加するようなマクロを定義したいのです。
| 入力 | マクロ定義 |
|---|---|
CD |
CD |
CD ディレクトリ |
CD /D ディレクトリ |
CD /D ディレクトリ |
CD /D ディレクトリ |
ちなみにバッチファイルで実現するなら超簡単です。下記のバッチファイル chd.cmd を作成します。
@echo off
if "%~1"=="" goto NOARG
if /I "%~1"=="/D" goto OPT_D
rem ----------------------------------------------------------------------------
rem オプション /D が指定されていない場合
rem ----------------------------------------------------------------------------
cd /D %*
exit /b
rem ----------------------------------------------------------------------------
rem オプション /D が既に指定されている場合
rem ----------------------------------------------------------------------------
:OPD_D
cd %*
exit /b
rem ----------------------------------------------------------------------------
rem 引数なしの場合
rem ----------------------------------------------------------------------------
:NOARG
cd
exit /b
そして DOSKEY コマンドで下記のマクロ定義を行えば良いだけです。
doskey CD=CHD $*
しかし,わざわざこの程度の機能のためにバッチファイルを作って呼び出すというのも大仰であると思ったのです。
今回の課題は,バッチファイルを作らずに DOSKEY コマンドだけで上記のような条件分岐を行うマクロ定義を実現できるか?ということです。
AI に相談した結果
結論から言うと、あなたが望むような条件分岐を純粋に DOSKEY マクロだけで実現することは不可能。
え・・・じゃあ,その不可能に挑戦してみましょう。
不可能への挑戦
実はこのマクロ定義は着想から実現まで4年かかっています・・・
というのも DOSKEY コマンドの変態仕様に悩まされたからです。通常,コマンドプロンプトではスペースを含むパスは二重引用符で囲みます。
cd "c:\Program Files"
しかし CD コマンドは例外で二重引用符は不要です。付けても付けなくてもいいのです。
cd c:\Program Files
問題は DOSKEY コマンドで引き渡すマクロ引数です。パスを二重引用符で囲わない場合は想定通りですが,二重引用符で囲った場合の挙動がちょっと想定外でした。要するに二重引用符特有の処理をまったく行っておらず,マクロ引数を単なるスペース区切りで生成しているだけなのです。
| マクロ引数 | 二重引用符で囲わない場合 | 二重引用符で囲った場合 |
|---|---|---|
$* |
c:\Program Files |
"c:\Program Files" |
$1 |
c:\Program |
"c:\Program |
$2 |
Files |
Files" |
目的はマクロ引数の一番目 $1 が /D と等しいかどうか判定することですが,$1 には二重引用符が一つだけ付いている場合があり,このまま if コマンドで比較するとエラーになります。
この一つだけ付いた二重引用符を外すのがとても難しいのです。両端についた二重引用符を外す方法としては FOR コマンドのループ変数を使う方法があります。全マクロ引数 $* を対象にして
for %I in ($*) do echo "%~I"
とします。こうするとパスを二重引用符で囲っていないときはループが複数回実行されます。
| ループ | 二重引用符で囲わない場合 | 二重引用符で囲った場合 |
|---|---|---|
| 1回目 | "c:\Program" |
"c:\Program Files" |
| 2回目 | "Files" |
そこでループ1回目の値を得るには下記のようにすればよいのです。
set CDARG=
for %I in ($*) do if not defined CDARG set "CDARG=%~I"
ところが,これだとパスが c:\Program Files (x86) のように剥き出しのカッコを含んでいると FOR コマンドでエラーになります。そこで下記のようにいったん全マクロ引数 $* を環境変数 CDARGS にコピーしてカッコを置換することにします。
set CDARGS=$*
set "CDARGS=%ARGS:(=_%"
set "CDARGS=%ARGS:)=_%"
set CDARG=
for %I in (%CDARGS%) do if not defined CDARG set "CDARG=%~I"
しかし,これだと引数なしのときに環境変数 CDARGS が削除されるので,以降の文字列置換がエラーになります。そこで一つ空白を挟むことにしました。環境変数 CDARGS が空白のみの場合は FOR コマンドも実行されないのでちょうど良いのです。
set CDARGS= $*
set "CDARGS=%ARGS:(=_%"
set "CDARGS=%ARGS:)=_%"
set CDARG=
for %I in (%CDARGS%) do if not defined CDARG set "CDARG=%~I"
こうして環境変数 CDARG が存在し,かつ /D と等しくなければ環境変数 CDCMD=CD /D とし,それ以外の場合は CDCMD=CD とします。
そして環境変数 CDCMD の内容を実行すればよいのです。
set CDARGS= $*
set "CDARGS=%ARGS:(=_%"
set "CDARGS=%ARGS:)=_%"
set CDARG=
for %I in (%CDARGS%) do if not defined CDARG set "CDARG=%~I"
set CDCMD=CD
if defined CDARG if /I not "%CDARG%"=="/D" set CDCMD=CD /D
%CDCMD% $*
後は実行過程をエコーしないように @echo off ~ @echo on で囲み,最後に作業用に作成した環境変数を忘れずに削除します。
@echo off
set CDARGS= $*
set "CDARGS=%ARGS:(=_%"
set "CDARGS=%ARGS:)=_%"
set CDARG=
for %I in (%CDARGS%) do if not defined CDARG set "CDARG=%~I"
set CDCMD=CD
if defined CDARG if /I not "%CDARG%"=="/D" set CDCMD=CD /D
%CDCMD% $*
set CDCMD=
set CDARG=
set CDARGS=
@echo on
これをバッチファイルで定義するためには改行コードをマクロ定義内の区切り記号 $T に置き換え,さらにパーセント記号を重ねて % ⇒ %% と置き換えれば完成です。
doskey CD=@echo off$Tset CDARGS= $*$Tset "CDARGS=%%CDARGS:(=_%%"$Tset "CDARGS=%%CDARGS:)=_%%"$Tset CDARG=$Tfor %%I in (%%CDARGS%%) do if not defined CDARG set "CDARG=%%~I"$Tset CDCMD=CD$Tif defined CDARG if /I not "%%CDARG%%"=="/D" set CDCMD=CD /D$T%%CDCMD%% $*$Tset CDCMD=$Tset CDARG=$Tset CDARGS=$T@echo on
ちなみに,これはバージョン3です。
もう一つの解
まず,下記のマクロ定義ファイル macro.ini を作ります。
CD=@echo off$Tset CDARG= $1$Tset CDARG=%CDARG:"=%$Tset CDARG=%CDARG: =%$Tset CDCMD=CD$Tif defined CDARG if /I not "%CDARG%"=="/D" set CDCMD=CD /D$T%CDCMD% $*$Tset CDARG=$Tset CDCMD=$T@echo on
そしてマクロ定義ファイルを指定して DOSKEY コマンドを呼び出します。
doskey /macrofile=macro.ini
マクロ定義ファイルの内容ですが,$T は DOSKEY コマンドの区切り文字なので,改行コードに置き換えると下記のようになります。
CD=@echo off
set CDARG= $1
set CDARG=%CDARG:"=%
set CDARG=%CDARG: =%
set CDCMD=CD
if defined CDARG if /I not "%CDARG%"=="/D" set CDCMD=CD /D
%CDCMD% $*
set CDARG=
set CDCMD=
@echo on
この別解ではマクロの第一引数 $1 を直接扱います。第一引数を環境変数 CDARG に格納する際に敢えて空白を入れている理由は前に述べたのと同じです。
そして二重引用符 " を削除しますが,単なる文字列置換で実現しています。このときに二重引用符のペアではなく,単独の二重引用符を記述しています。単独の二重引用符はバッチファイル中だとエラーになってしまいますが,マクロ定義ファイル中では問題ないようです。
ちなみに,これはバージョン2です。
AIの反論
あなたが使ったのは CMD の機能を活用したインラインバッチと呼ぶべきテクニックであり,純粋に DOSKEY マクロの機能だけでは実現していない。
それはその通りです・・・だがしかし,せっかくなのでこの記事のタイトルは AI に考えて貰いました。