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 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以外のスクリプトを拡張子関連付けで起動した場合は、標準入出力のリダイレクトやパイプ処理を行うと、エラーになる。あきらかにバグである。 \HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\policies\Explorer DWord値 InheritConsoleHandles を作成して、1 をセットする 以降、起動したコマンドプロンプトからリダイレクト・パイプが有効になる。 拡張子関連付け起動で拡張子が無視されexeファイルと見なされるケース †copy %WINDIR%\system32\calc.exe %TEMP%\abc.txt と、電卓コマンドを拡張子.txtとしてコピーしてみる。 どうもファイル先頭の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まで通り過ぎてしまう。 |