« ◇スイカ炭素 | トップページ | ◇量子力学では観測は本質ではない:神は勝手にサイコロを振る »

Windowsバッチスクリプト記述法メモ

メモです。

 バッチファイルの雛形:戻り値、カレントフォルダ、pause

次の観点で作成したバッチファイルの雛形です。

  • 結果を呼び出しもとにerrorlevelとして返す
  • カレントフォルダをバッチファイルのあるフォルダにする
  • クリックされて起動した場合はpauseする。他ファイルから 引数付きで起動された場合はpauseしない
バッチファイルがクリック起動か他バッチからの起動かを判断する方法は用意され ていません。そこで、他バッチから起動する場合で引数不要の場合は擬似引数 を付加することにしました。
@echo off
pushd %~dp0
::-------------------------
ここに動作
::-------------------------
:OK
set result=0
goto END
:ERR
set result=1
:END
if not "%1"=="" goto NOPAUSE
pause
:NOPAUSE
popd
exit /b %result%
"exit /b 変数"で返した値は呼び出し元でerrorlevelで参照できます。 /bを忘れると呼び出し元に戻らず終了してしまうので注意が必要です。 errorlevelに直接値を入れても期待する当たり前の動作はしないので 注意が必要です。
標準のerrorlevelは例えばtypeコマンドなどでも変化してしまいます ので通常は状態を別変数にとりexit /bすべきです。仮にerrorlevel をそのまま返したい場合でもset result=%errorlevel%などとした 上でresultを返す習慣をつけるのが安全です。

pause制御は単純ですが、色々な部品から組み上げられるシステムで、部分部分で 起動可能で、かつ、全体を統括するバッチからも起動可能となります。引数のない バッチの呼び出しには擬似引数(便宜上nopauseとする)を付加します。
 call clear.bat nopause
バッチは直接書き下ろさず、必ずこういった雛形を使うべきです。

 他の.batを呼ぶときは必ずcallを使う

バッチファイルから他のバッチファイルを呼ぶことはできますが、callを 使わない場合、信じられないことに、戻って来ません。

callで別フォルダのbatを呼び出した場合、カレントフォルダは通常は呼び出し元のまま です。通常はフォルダをまずpushdで変更した上でcallします。戻った後直ぐpopdで 戻します。popdではerrorlevelは変化しません。

@echo off
pushd %~dp0
pushd dir1\dir2\dir3
call test1.bat nopause
popd
if errorlevel 1 goto ERR
:OK
set result=0
echo === OK ===
goto END
:ERR
set result=1
echo === SOME ERROR OCCURED ===
:END
@if not "%1"=="" goto NOPAUSE
pause
:NOPAUSE
popd
exit /b %result%
なお、errorlevelのチェックには==などは使いません。if errolevel 1で正解です。

 コメント

コメントは"::"で始めます。

   :: コメント

これは本来はラベルなのですが、コメントとして利用できます。
本来のコメント文はREMですが、REMは極めて視認性が悪いため 使うべきではありません。

   REM コメントだが余りにも見づらい

 改行(長いコマンド行を分ける)

長いコマンド行を分けるには分ける部分に^を置きます。

  java -jar ..\..\lib\symphonie.jar syntax-check ^
       -syntax syntax.hn -source e2.txt -xml -with_begin_end -out out2.xml

 echo

通常はコマンドはエコーされますが、@をつけるとエコーされません。

   del temp.log     "del temp.log"と実行コマンドが表示される
   @del temp.log    実行コマンドは表示されない

"echo off"を実行すると、それ以降のエコーは抑止されます。通常はバッチファイル の先頭に

   @echo off
を置きます。

echoで改行するにはecho;と書きます。

   echo;
これで";"がエコーされず改行となる理由は分かりませんが、Windowsバッチ世界 は理由のある世界ではなく、「たまたま世界」なのでこういうもんだ と思ってください。設計思想のないままたまたま出来上がったものが仕様と なっているのです。

 リダイレクト

標準出力と標準エラーをまとめてリダイレクトするには

コマンド > 出力ファイル 2>&1
例)
dir /w > kekka.txt 2>&1
というとんでもない構文を用います。"2>&1 kekka.txt"ではありません。

なお、標準出力と標準エラーを別々に標準エラーのみをリダイレクトするなら、

コマンド >出力ファイル 2> err出力ファイル
です。

 無意味なメッセージの抑止 "del *.log > NUL 2>&1"

Windowsのコマンドは余計なメッセージを出します。
例えばdelコマンドで対象ファイルが存在しない場合などメッセージ は通常不要です。copyコマンドでもcopyが成功した場合メッセージ は不要です。

これらの不要なメッセージにより重要なメッセージがマスクされて しまうことは往々にして起ります。

残念ながらコマンド自体には「鬱陶しい邪魔な作業妨害メッセージ抑止」 のオプションはありません。

UNIXであれば/dev/nullにリダイレクトすることによりメッセージを 見せなくできますが、WindowsではNULにリダイレクトします。 Lが一個であることに注意が必要です。NULLでなくNUL

基本的にdelコマンドは標準エラーと標準出力を捨てます。削除 不能のエラーが見えなくなるのはちょっと心配ですが、しかた ありません。
copyは標準出力のみ捨てます。

なお、システムの設定によってはこの方法をとると"NUL"というファイルが 作成されてしまうことがあるということです。ま、大して害はないので NULというファイルは見て見ぬふりしましょう。 実はNULが作られるというのは間違いでNULL(Lが2つ)と書いて しまったんじゃないかと思っています。普通の人ならNULL と書きます。普通の人が間違うように設計してあるのが Windowsなのです。

 複数ファイルを指定してcopyする

そんなの"copy A B C D\ "でいいじゃん。と考えた貴方。貴方は普通で真っ当です。 しかしWindowsバッチ世界は普通でも真っ当でもありません。

コピーコマンドはソースを1個しか指定できないのです。*で複数ファイルを表し ますが、コマンド指定はあくまで1個しかできないのです。

ファイルA,B,CをフォルダDにコピーしたいばあいは次の呪文を唱えます。

set SRC=A B C
set DIST=D\
for %%f in ( %SRC% ) do copy %%f %DIST%
for文に関しては次に述べます。なお、このような定型"呪文"以外は動作はcallでサブルーチン とすべきです。

 xcopyが無い?xcopyではフォルダコピーができない?

フォルダをコピーするxcopyはdelやcopyなどと異なり、組み込みの コマンドではありません。

xcopyの置いてあるフォルダ

 C:\WINDOWS\system32
にパスが通っていない場合動作しません。

パスはたまに壊れることがあります。

xcopy(または他のあるはずのコマンド)が無いと言われた場合、[コントロールパネル]- [システム]-「詳細設定」-[環境変数]でpathをチェックします。

注意しなくてはならないのが、

xcopyはフォルダコピーコマンドではなく、フォルダの中のファイルコピーコマンド
だということです。
これは恐ろしく馬鹿げていて、例えば、フォルダD:\A\CをD:\Bの下にコピーしようと
 xcopy D:\A\C D:\B
とすると、何と!D:\B\Cが出来上がるのではなく、Bの下にCのファイル群が展開されて しまうのです。すんげえ仕様だ!
フォルダD:\A\CをD:\Bの下にコピーするには
 xcopy /S /Q /I /Y D:\A\C D:\B\C
とする必要があります。バッチ記述で/Iを忘れると、行く先はディレクトリかどうか などと訳の分からないことを言い始めるので必ず/Iが必要です。
/Yを忘れるとオーバーライトするかどうか聞いてきます。間違いのもとですので 必ず付けます。

実際の仕事ではオーバーライトで壊れる壊してしまうことなどありません。 数多く発生するのは一部ファイルがオーバーライトされず、全体が訳の分からない 状態になることです。

 ディレクトリの削除

ディレクトリの削除はrd /Q /Sで行います。

 for制御

forによる繰り返しができます。繰り返し実行させる内容は通常サブルーチン に記述します。サブルーチン化するのは変数設定に関する問題を回避するためです。 注意が必要なのはgoto ラベルには:は不要だがcall :ラベルには:が必要という点です。 ただしgoto :eofは:が必要らしい。

形式:

for %%変数名in (A B C) do (
   call :サブルーチン名 引数  <-- 単純なものは直接書いても良い
   )
繰り返し外の動作
goto END     <-- プログラム終了(exitは使わないこと)

:サブルーチン名     <-- サブルーチンの入り口
繰り返し動作
goto :eof          <-- サブルーチン終了

::------
:END
例:(test.bat)
@echo off
pushd %~dp0
for %%f in (A B C) do (
  call :FUNC %%f
)
goto END
::----------------------------------
:FUNC
echo %1
goto :eof
::----------------------------------
:END
result=%errorlevel%
if not "%1"=="" goto NOPAUSE
pause
:NOPAUSE
popd
exit /b %result%

実行結果を示します。

A
B
C
続行するには何かキーを押してください . . .

サブルーチンの終わりは"goto :eof"です。複数のサブルーチンを並べる ことができます。
pauseを入れてあるのは、バッチファイルをクリックで動かした場合 窓が消えてしまわないようにするためです。
先頭のサブルーチンの前でgoto :eofで終了させるか、最後に:END ラベルを置きgoto :ENDで飛ばします。

ここの例のような単純なものの場合、サブルーチンを呼ばす 直接コマンドを書くことも可能ですが、定型のもの以外はあまり勧められません。

for %%f in (A B C) do echo %%f

doの括弧内に複数動作を書くこともできますが 変数のスコープにバグ(Windowsの常としてバグのことを 仕様と呼んでいますが)があり、避ける方法は用意されて はいますが、あまりにも汚く見づらく間違いの元なので 絶対にその形式は用いてはなりません。

 if制御

ifは次の形式です。普通の言語のようにif文と解釈してはなりません。 条件行、else行、終了行の間に動作が入ると考えてください。else行は 省略可能です。

形式:

if 条件 ( <-- 条件行
   call :サブルーチン名 引数  <-- 単純なものは直接書いても良い
) else (   <-- else行
   call :サブルーチン名 引数  <-- 単純なものは直接書いても良い
)          <-- 終了行

注意しなくてはならないのがelseと括弧の間に空白が必要 だということです。

forの動作、ifの括弧の中もサブルーチン呼び出しにしてあります。この例では ifの方は括弧内に書いても全く問題はないのですが、 この習慣をつけておく方が良いと考えました。

例:(rename.bat)

:: ファイル名を変更する
@echo off
pushd %~dp0
set count=0
for %%f in (*.txt) do (
   call :FUNC %%f
)
echo %count% files
goto END
::----------------------------------------
:FUNC
set  xx=%1
set  xx=%xx:ABC=XYZ%
if not %1== %xx% (
   call :FUNC2 %1 %xx%
) else (
   echo ignore %1
)
goto :eof
::----------------------------------------
:FUNC2
   echo %1 to %2
   set /a count=%count%+1
goto :eof
::----------------------------------
:END
result=%errorlevel%
if not "%1"=="" goto NOPAUSE
pause
:NOPAUSE
popd
exit /b %result%

この例はファイル名を一括変更(ABCをXYZに)するものですが、変更の代わりにechoしています。 "echo %1 to %xx%"を"ren %1 %xx%"に変えればファイル名変換になります。
ABCを含まないファイルは無視しています。

AAA.txt,AB.txt,ABC.txt,AbCD.txt,ABCDE.txt,XYZABCZYZ.txtがある 環境で動かした結果を載せませす。

XYZABCXYZ.txt to XYZXYZXYZ.txt
ABC.txt to XYZ.txt
ignore AAA.txt
ignore AB.txt
ABCDE.txt to XYZDE.txt
AbCD.txt to XYZD.txt
4 files
続行するには何かキーを押してください . . .

 SET

setには幾つかオプション指定があります。

  • /a:数値演算結果をセットします。
    set /a count=%count%+1

 ifの条件

 変数のファイル名取得修飾

変数にファイル名が入っている場合、その一部を参照する修飾法があります。
コマンド引数は%の後ろに一桁の数字を入れ、参照できます。
%0はコマンドそのものであり、%1は第一引数です。
%と数字の間に"~オプション文字"をはさむことにより、ファイル名の一部を 取得することができます。

  • %~dp1:第一引数のドライブ名とフォルダ名
  • %~f2:第二引数のファイルフルパス
といったものです。
実際のバッチファイルで動かした例を載せます。%0はこのバッチファイル そのものです。"%%"は変数参照ではなく単なる%文字となります。"echo;"は改行を出しています。

バッチファイル例:(test2.bat)

:: 変数のファイル名取得修飾テスト
@echo off
echo %%0 引数そのもの
echo %0
echo;
echo %%~f0 ファイルフルパス
echo %~f0
echo;
echo %%~d0 ドライブ文字
echo %~d0
echo;
echo %%~p0 フォルダ名
echo %~p0
echo;
echo %%~n0 ファイル名(フォルダ、拡張子無し)
echo %~n0
echo;
echo %%~x0 拡張子
echo %~x0
echo;
echo %%~dp0 ドライブ文字とフォルダ名
echo %~dp0
echo;
echo %%~dpnx0 ドライブ名+フォルダ名+ファイル名+拡張子
echo %~dpnx0
echo;
pause

出力:

%0 引数そのもの
"Q:\BLG_ネタ\271Windowsスクリプト\sample1\test2.bat"

%~f0 ファイルフルパス
Q:\BLG_ネタ\271Windowsスクリプト\sample1\test2.bat

%~d0 ドライブ文字
Q:

%~p0 フォルダ名
\BLG_ネタ\271Windowsスクリプト\sample1\

%~n0 ファイル名(フォルダ、拡張子無し)
test2

%~x0 拡張子
.bat

%~dp0 ドライブ文字とフォルダ名
Q:\BLG_ネタ\271Windowsスクリプト\sample1\

%~dpnx0 ドライブ名+フォルダ名+ファイル名+拡張子
Q:\BLG_ネタ\271Windowsスクリプト\sample1\test2.bat

続行するには何かキーを押してください . . .

 変数の部分文字列へのアクセス

変数の文字列の一部を置き換えたり、文字位置指定で取り出したりすることができます。(未)

|

« ◇スイカ炭素 | トップページ | ◇量子力学では観測は本質ではない:神は勝手にサイコロを振る »

トラックバック

この記事のトラックバックURL:
http://app.f.cocolog-nifty.com/t/trackback/489055/31619297

この記事へのトラックバック一覧です: Windowsバッチスクリプト記述法メモ:

« ◇スイカ炭素 | トップページ | ◇量子力学では観測は本質ではない:神は勝手にサイコロを振る »