''cmd.exeビルトイン関係のバグについて''
#contents
////////////////////////////////////////////////////////////////////////////
*for /f %A in ('command ....') の日本語処理のバグ (Win2K) [#z08ee886]

 for /f %%A in ('echo あいう123456789') do echo %%A
 for /f %%A in ('echo あ123456789') do echo %%A
では、それぞれ、「あいう123456789」「あ123456789」が表示されるはずだが、実際には、「あいう123456」「あ12345678」が表示される。(' ') の2バイト文字の数だけ (' ') 内のコマンド末尾から文字が削られるようだ。かといって空白を末尾につけても無駄で同じ結果に終わる。空白だけでなく、セミコロン、カンマ、イコールなどの in ( ) 内リストの区切り文字も駄目。また、usebackq と ` ` を使った場合も同じである。

WinXPでは直っているのにWin2000の最新SPでも直っていないということは、直す気が無いということだろう。初歩的過ぎて情けないバグである。

現実的には、

 for /f "delims=" %%A in ('find "日本語" filename') do ....
のようなケースで問題になるだろう。2バイト文字の数が分かっていれば、

 for /f "delims=" %%A in ('find "日本語" filename@@@') do ....
のようにその数だけ余分の文字をあらかじめ末尾に付けて置けば良いが、2バイト文字の数が不明の場合(及びWin2000とWinXPで共通に使う場合)は、例えば、

 for /f "delims=" %%A in ('find "%STR%" filename^|findstr /v qwertyuiopasdfghjkl') do ....
のように末尾からある程度文字が消えても良いようにする必要がある。何か情けない方法である。
////////////////////////////////////////////////////////////////////////////
*%~sA のバグ [#geb5e062]

for変数や、バッチ引数には、%~sA や %~s1 という修飾子でショートネーム(または8.3形式。以下ではSFNと書く)形式のパス名・ファイル名を得ることが出来る。しかしこれにはバグがある。NT4時代からあって、直っていないらしい。このバグは簡単に再現できる。

 md "long long long dir"
 cd "long long long dir"
 echo.>"A B.txt"
 for %%A in (*.txt) do echo %%~sA

本来は、「\LONGLO~1\ABBE64~1.TXT」 のように表示されるべき部分が、「\LONGLO~1\ABBE64~1.TXTB.txt」 のように後ろにごみ(LFNの残骸)が付いてしまう。
こういうバグがある以上、特定の環境で一時的に使うバッチならともかく、汎用ツールを作る時は ~s 修飾子を安易に使えないと言うことだ。

解決法としては、パスの上位部分から順にSFN形式にしていくしか無いようだ。汎用サブルーチン :sfnsub とその(上記スクリプトで作ったファイルでの)テストスクリプトを示す。

:sfnsub の仕様としては、" "で囲んだフルパスを引数にすると、環境変数 SFN にそのSFNを返す。" "で引数を囲むのは必須。

 @echo off
 setlocal
 rem サンプルメイン
 for %%A in ("A B.txt") do echo %%~sA&call :sfnsub "%%~fA"
 echo %SFN%
 goto :eof
 rem サブルーチン
 :sfnsub
 set ARG=%1
 set ARG=%ARG:\=\" "%
 set SFN=
 call :loop %ARG%
 goto :eof
 :loop
 for %%A in ("%SFN%%~1") do set SFN=%%~sA
 shift
 if not "%~1"=="" goto :loop
 set "SFN=%SFN:&=^&%"
 set "SFN=%SFN:^^=^%"
 goto :eof

汎用と書いたが、% はスクリプト中でサブルーチンの引数として渡せない((もちろん、% を %%%% に置換してから引数に使えばよいが、for変数には置換が使えないし、置換するために一般の環境変数に一旦セットすればさらに多くの問題を引き起こすので現実的でない))ので % を含んだファイル名は駄目だがそれ以外のファイル名に使える特殊文字には対応しているはずだ(まだ漏れがあるかもしれないが)。

なお、この方法と別に COMMAND.COM を使ってカレントディレクトリを SFN に変換する別法もある。

 @echo off
 setlocal
 for %%A in ("A B.txt") do echo %%~sA&call :sfnsub "%%~fA"
 echo %SFN%
 goto :eof
 :sfnsub
 pushd "%~dp1"
 command /c rem
 for %%A in ("%~nx1") do set SFN=%%~sA
 popd
 goto :eof

こっちのほうがわかりやすい。ただし、日本語版Windowsの場合は初回のCOMMAND.COM実行時に画面がクリアされていろいろメッセージが出る((英語版では出ないらしい。これを出さない方法は無いだろうか?))のでいまいちである。

いずれにせよたかがSFNを得るために大げさな話であり、スクリプトではSFNを使うのを避けたほうがいいだろう。

\Program Files\ の下だけ調べてみたが、%~sA が正しい結果を返さないファイルは結構
ある。

 for /r "C:\Program Files" %%A in (*) do if not exist %%~sA echo %%A&echo %%~sA
////////////////////////////////////////////////////////////////////////////
*スクリプト言語の入出力リダイレクト [#ce52c564]

Perl,Rubyなどのスクリプトを、拡張子をコマンドに関連付けた上で
PATHEXT環境変数に ;.pl;.rb を追加することで、
PATHの通ったフォルダにあればスクリプト名だけで起動できる。
VBScript, JavaScript は標準で関連付けられてPATHEXTにも拡張子がセットされているのでそのままで起動できる。

ただしBAT,CMD以外のスクリプトを拡張子関連付けで起動した場合は、標準入出力のリダイレクトやパイプ処理を行うと、エラーになる。あきらかにバグである。~
Win2KはSP4, WinXPはSP1でこのバグが修正されている。
しかし、何故かレジストリを修正しないと有効にならない。バグと認めず仕様変更と言うつもりか。

 \HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\policies\Explorer
 DWord値 InheritConsoleHandles を作成して、1 をセットする

以降、起動したコマンドプロンプトからリダイレクト・パイプが有効になる。
////////////////////////////////////////////////////////////////////////////
*拡張子関連付け起動で拡張子が無視されexeファイルと見なされるケース [#w9ac6d77]

 copy %WINDIR%\system32\calc.exe %TEMP%\abc.txt

と、電卓コマンドを拡張子.txtとしてコピーしてみる。~
abc.txtをexplorerからファイルをダブルクリックしての起動や、スタートメニューの「ファイル名を指定して実行」から起動すると、ちゃんとメモ帳が起動してexeファイルの中身が表示される。~
しかし、コマンドプロンプトから、%TEMP%\abc.txt と打つと、なぜかメモ帳じゃなくて電卓が起動する。start %TEMP%\abc.txt でも同じだ。拡張子がtxtじゃなくても同じ(bat,cmdを除く)。

どうもファイル先頭の2バイトが"MZ"だと、拡張子に関係なくexeまたはcomファイルと見なすようである。ためしにメモ帳で「MZ80は8ビットパソコン」という内容でテキストファイルを作って、そのファイル名を打ってもメモ帳は起動せず、何も起こらなかったりエラーダイアログが出たりする。

理由が理解できない仕様で、セキュリティーホールだと思うが現実にはあまり実害は無いので騒がれていないのか。
////////////////////////////////////////////////////////////////////////////

トップ   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS