cmd.exeビルトイン関係のバグについて
外部コマンドのバグについては、コマンド別 に記す

for /f %A in ('command ....') の日本語処理のバグ (Win2K)

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 ....

のように末尾からある程度文字が消えても良いようにする必要がある。何か情けない方法である。
あるいは、一時ファイルを使って、

find "%STR%" filename > tempfile.tmp
for /f "delims=" %%A in (tempfile.tmp) do ....
del tempfile.tmp

これも、せっかくコマンド結果取得の構文があるのに使わないとは 情けないことに変わり無い。

%~sA のバグ

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

2006-09-05追記

上記だとパス中に ^ を含んだ場合に駄目なようだ。考慮したつもりだったのだが。
下記のように:loopを変更すると行けるようだが引き続き検証する。

:loop
set "W=%~1"
set "W=%W:^^^^=^"
for %%A in ("%SFN%%W%") do set SFN=%%~sA
shift
if not "%~1"=="" goto :loop
set "SFN=%SFN:&=^&%"
set "SFN=%SFN:^^=^%"
goto :eof

2006-09-05追記 終わり

汎用と書いたが、% はスクリプト中でサブルーチンの引数として渡せない*1ので % を含んだファイル名は駄目だがそれ以外のファイル名に使える特殊文字には対応しているはずだ(まだ漏れがあるかもしれないが)。

なお、この方法と別に 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実行時に画面がクリアされていろいろメッセージが出る*2のでいまいちである。

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

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

for /r "C:\Program Files" %%A in (*) do if not exist %%~sA echo %%A&echo %%~sA

スクリプト言語の入出力リダイレクト

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ファイルと見なされるケース

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ビットパソコン」という内容でテキストファイルを作って、そのファイル名を打ってもメモ帳は起動せず、何も起こらなかったりエラーダイアログが出たりする。

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

echo. と遅延展開

遅延展開を有効にした状態で、echo.!CD:~0,2! とするとカレントドライブを表示 することを期待するが、実際には、CD:~0,2 という文字列が表示される。

詳細は コマンド別/echo を参照。

pause 解除時に特殊キーを押した場合

処理1
pause
処理2
pause

のように2つの処理が終わるごとに処理を止める目的でそれぞれにpauseを置いた場合に、 最初のpauseでファンクションキーやカーソル、INS/DELなどのキーを押すと、 キーコードが2バイトのせいか2つ目のpauseまで通り過ぎてしまう。


*1 もちろん、% を %%%% に置換してから引数に使えばよいが、for変数には置換が使えないし、置換するために一般の環境変数に一旦セットすればさらに多くの問題を引き起こすので現実的でない
*2 英語版では出ないらしい。これを出さない方法は無いだろうか?

トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2007-02-04 (日) 19:17:29 (6539d)