Windowsで「cd -」や「ls」


WindowsのDOSコマンドは、長ったらしくてイライラする！！
Unixの`cd -`って便利だよね！　なんでWindowsではできないんだろう orz

そんな貴方に、DOSのAutoRunスクリプトを利用したハックを紹介します。

![example.jpg](https://qiita-image-store.s3.amazonaws.com/0/74102/6bdffe7c-fc17-5bf8-5ebd-aefd718b1db1.jpeg "example.jpg")

### 動作確認環境
* Windows 7
* Windows Vista

### 導入方法

ごたくはいいからやり方だけ教えて！　な貴方は、次の部分だけ読めば大丈夫です。

* [DOSのAutoRunスクリプト](#dosのautorunスクリプト)
* [お手軽な指定方法](#お手軽な指定方法)

## DOSのAutoRunとは

指定したDOSスクリプトを、`cmd`の起動時に実行させることができます。

* `Win+R ⇒ cmd`　で起動したDOS窓に適用されます
* Explorerのアドレスバーに`cmd`と入力して起動したDOS窓に適用されます
* `at`コマンドや`*.bat`をダブルクリックで起動したDOSスクリプトにも適用されます

(後述する) **指定したユーザが起動したすべての`cmd`に適用**されます！！

### 注意事項

* 初期ディレクトリ変更をした場合、多くのDOSスクリプトが正常に動作しなくなります  
  ⇒　たいていのDOSスクリプトは「作業ディレクトリ＝DOSスクリプトのディレクトリ」を前提としているため  
  ⇒　DOSスクリプトの先頭で`cd /d %~dp0`をすれば回避できるが、このような配慮あるスクリプトはあまり見かけないため
* やりすぎに注意！！　

## DOSのAutoRunスクリプト

このDOSスクリプトを、`dos_profile.bat`の名前で保存します。

```dos:dos_profile.bat
@echo off

rem プロンプト
prompt $g 

rem エイリアス
doskey pwd=cd
doskey cat=type $*
doskey ls=dir /w $*
doskey ll=dir $*
doskey la=dir /a:h
doskey mv=move $*
doskey rm=del $*
doskey ln=mklink $2 $1
doskey clear=cls

rem エイリアス：エディタ
set VI_EDITOR=C:\Program Files\EmEditor\EmEditor.exe
doskey   vi="%VI_EDITOR%"    $*
doskey view="%VI_EDITOR%" /r $*
set VI_EDITOR=

rem エイリアス：cd
set PREV_PWD_FILE=%TEMP%\pwd.prev
set PREV_PWD_TEMP=%TEMP%\pwd.temp
doskey cd=if ""=="$1" (^
    @cd ^
) else if "-"=="$1" (^
    @for /f "usebackq tokens=*" %%i in ^(`cd`^) do @echo %%i^>"%PREV_PWD_TEMP%" ^& ^
    @for /f "usebackq tokens=*" %%i in ^("%PREV_PWD_FILE%"^) do @cd "%%i" ^&^& ^
    @type "%PREV_PWD_FILE%" ^& ^
    @type "%PREV_PWD_TEMP%" ^> "%PREV_PWD_FILE%" ^
) else (^
    @for /f "usebackq tokens=*" %%i in ^(`cd`^) do @echo %%i^>"%PREV_PWD_TEMP%" ^& @cd $* ^&^& @type "%PREV_PWD_TEMP%" ^> "%PREV_PWD_FILE%" ^
)
set PREV_PWD_FILE=
set PREV_PWD_TEMP=
```

### プロンプトの変更

規定値ではカレント・ディレクトリが表示されます。
長すぎるので`>`に変更。　文末に半角スペースを置くと可読性が向上します。

### DOSKEYによるエイリアスの割り当て

DOSコマンドに、Unixライクなエイリアスを割り当てます。

#### 補足：ln

Windowsの`mklink`は、何故かUnixの`ln`と引数が逆転しています。

* Unix ln ... ln FILE LINK
* Windows mklink ... mklink LINK FILE

後発である`mklink`がなぜ引数を逆転させる必要があるのか？
Microsoftの ~~馬鹿な~~ 残念な仕様に振り回されないよう、エイリアスを割り当てます。

`mklink`の実行には、DOS窓を管理者権限で実行する必要があります。
※詳細は異なるが、ざっくりなら管理者権限で実行すればOK

ディレクトリのシンボリックリンクを作る場合は、`/d`スイッチを指定する必要があります。
引数を逆転させている関係で、空白を含むパスにリンクを貼る場合は失敗するかもしれません。　あしからず。

#### 補足：エディタ

お好みのエディタを、`VI_EDITOR`環境変数に指定してください。
⇒　atomは初期起動が遅すぎるため、僕はカジュアルなテキストエディタとしては使ってません

#### 補足：cd

やってることは単純で、次のようになります。

* 引数なし
	* 作業ディレクトリを表示
* 引数＝"-"
	* 作業ディレクトリをpwd.tempに退避
	* `cd pwd.prevの退避ディレクトリ`
	* `cd`が成功したら、移動先を標準出力してからpwd.prevに元の作業ディレクトリを退避
* 上記以外
	* 作業ディレクトリをpwd.tempに退避
	* `cd パラメータ`
	* `cd`が成功したら、pwd.prevに元の作業ディレクトリを退避

これらを`doskey`の記述ルールに従って表現するのに、実はかなり苦戦しました。

##### 利用した構文

* `^` ... DOSのエスケープ文字です。　行末に記述してあるのは行継続。
* `@` ... コマンドのエコーバックを抑制してます
* `^>` ... リダイレクトです。　MSDNには`$g`と記載されていますが、英字だと直感的ではないのでエスケープ文字で代替。
* `^&` `^&^&` ... コマンド結果演算です。　MSDNには`$t`と記載されていますが、英字だと直感的ではないのでエスケープ文字で代替。

##### 制限事項

作業ディレクトリの退避ファイル名を固定にしているため、 **同時に複数の`cmd`を実行している場合は`cd -`が正しく機能しません**。
他プロセスも含めて、最後に`cd`を実行した時の元ディレクトリへ移動します。

僕は「Windowsで複数ターミナルを起動＆`cd -`も使い倒す」的なシチュエーションはめったになくこの仕様で困ることはないのです  ~~が、記事を公開するにあたっては不親切仕様であるので、いずれ次のように改修しようと思います~~。

* ~~退避ファイルをpid毎に分ける~~
* ~~ログオフスクリプトで退避ファイルを削除~~

powershellを利用してpidを取得してみたのですが、DOS窓を起動するたびにクッと引っかかるようになったため改修はやめました。

`pushd/popd`を使っても戻る操作を表現できますが、ネスト制限について書かれた文書が見つからなかったため不採用としました。
⇒　基本操作である`cd`の振る舞いを書き換えており、これによる想定外の事象を嫌ったため

## DOSのAutoRunスクリプトを指定

次のいずれかのレジストリに`AutoRun`という名前でDOSスクリプトを指定します。
適用スコープに応じたレジストリを編集してください。

|レジストリ・キー|適用スコープ|備考|
|---|---|---|
|HKEY_LOCAL_MACHINE\Software\Microsoft\Command Processor|すべてのユーザ||
|HKEY_CURRENT_USER\Software\Microsoft\Command Processor|現在のユーザ|おすすめ|

|名前|種類|設定値(例)|
|---|---|---|
|AutoRun|REG_SZ|C:\xxx\xxx.bat|

### お手軽な指定方法

このDOSスクリプトを、前述した`dos_profile.bat`と同じディレクトリに保存します。
そしてダブルクリック！！

```bat:dos_profile_regist.bat
@echo off

reg add   "HKEY_CURRENT_USER\Software\Microsoft\Command Processor" /v AutoRun /d %~dp0dos_profile.bat
reg query "HKEY_CURRENT_USER\Software\Microsoft\Command Processor" /v AutoRun

pause
```