2013-05-02
開発に役立つ,BATファイルの書き方・パターン集 (コマンドプロンプトの定石を体系的に学び,バッチ中級者になろう)
Windows上の処理を自動化するプログラムが,BATファイルである。
「コマンドプロンプト」上での手作業を省略し,自動実行できる。
Windowsが存続する限り,BATファイルはなくならないだろう。
バッチ・プログラミングの需要は,生き残る。このWindows 8の時代でもそうだ。
BATは,MS-DOSの時代から長く使われてきた。
そのため,各コマンドに関する個別のノウハウや情報は多い。
だが,実用的なノウハウを体系的に整理したものは,あまり見かけない。
そこで以下では,BATをコーディングする際の良質なパターンを列挙する。
- (0)BATプログラミングの特徴
- (1)BATファイルの雛型
- (1−1)冒頭と末尾のテンプレート
- (1−2)反復して実行可能に
- (2)バッチの構造化
- (2−1)ルーチンの分割
- (2−2)ファイルやプロセスの分割
- (2−3)外部ツールの呼び出し
- (3)ファイル操作
- (3−1)for文によるスキャン
- (3−2)if文による判定
- (3−3)中間ファイルの利用
- (4)変数と演算
- (4−1)コマンド実行結果の保管
- (4−2)数値演算
- (4−3)日付や時間の加工
- (4−4)ファイル名の加工
本稿は,コマンドプロンプトの中級レベルのテクニックの整理,とも言える。
これらを習得すれば,あなたもコマプロ (※コマンドプロンプトのプロフェッショナル)*1。
(0)BATプログラミングの特徴
最初に,BATの長所と短所を知っておこう。
そうすれば,BATの正確な使いどころが分かる。
BATのメリット:
- ファイルシステムの一括処理に便利
- プロセスの起動処理を簡素化できる(起動引数などをまとめて保管しておける)
BATのデメリット:
- 構造型プログラムが書けない,極めて書きづらい
- →本エントリで,各種の構文を理解すべし。サブルーチンやfor文の使いようがキモ。
BATでできない難しいことを,無理やりBATでコーディングする必要はない。
それは非生産的で,時間と労力の浪費だ。SEのすべきことではない。
BATの「得意分野」はBATでコーディングし,なおかつ,
BATの「守備範囲外」の事柄であれば,BATから別のプログラムを呼び出せばよいのだ。
BATはプログラムの起動が得意なのだから,多いに外部プログラムに頼って良いのだ。
むしろ,「外部プログラムの呼び出し方」を,順番にうまく制御する役目を担うのがBATである。と考えよう。
ここから先の実務的な情報を読むにあたり,コマンドプロンプトの基礎的なツボを網羅しておこう。
以下のエントリが参考になる。
コマンドプロンプトで,暗記するべき10の必須コマンド (前半) ファイル処理系 - 主に言語とシステム開発に関して
http://d.hatena.ne.jp/language_and_engineering/20081001/1222857265
コマンドプロンプトで,暗記するべき10の必須コマンド (後半)ネットワーク系 - 主に言語とシステム開発に関して
http://d.hatena.ne.jp/language_and_engineering/20081002/1222908187
(1)BATファイルの雛型
(1−1)冒頭と末尾のテンプレート
BATの最初と最後に記述する内容は,だいたい決まっている。
冒頭と末尾は,便利なので暗記してしまおう。
@echo off rem rem このバッチの説明 rem rem 設定事項 set HOGE="変数の値" rem このバッチが存在するフォルダをカレントに pushd %0\.. cls 〜処理〜 pause exit
まず冒頭の説明。
- コマンドの先頭に@をつければ,バッチ実行時に,そのコマンド自体は表示されなくなる。
- echo offとすれば,実行しているコマンド自体の表示を全面的にOFFにできる。
- →全ての行の先頭に「@」と書かなくて済む。
- remはコメント行。冒頭には,バッチの目的などを記載する。各行では,各行の処理内容を説明する。
- setは,環境変数の定義。フォルダのパスとかを定義する。あとから変更しやすいよう,バッチの先頭部分にまとめて記述する。
- pushdは,ディレクトリの移動。
- cdコマンドだと,異なるドライブに移動できない。
- cd /dとすれば,異なるドライブに移動できるが,UNCパス上でネットワークの共有フォルダ上を移動できない。
- pushdは,ドライブの違いや,ネットワークの違いを気にせず,必ず移動できる。
- したがって,cdではなく,常にpushdコマンドを使うのが無難。なぜなら,このバッチがどこで実行されるのか,前もって知ることができないから。
- %0 は,このバッチファイルのファイルパス。
- %0\.. とすれば,このバッチファイルが存在するフォルダを指す。
- したがって,バッチの存在ずるフォルダをカレントディレクトリにすることができる。
- clsは,画面の消去。前回のコマンドの実行結果などを削除して,まっさらな画面でバッチの実行を開始できる。
ここまでで1セットである,と考えよう。
次に,バッチの末尾の説明。
- pauseコマンドは,何かキーを押すまで待機する。バッチの終了を目視でよく確認した上で,次に進む事ができる。
- 突然ウィンドウが消えてしまうと,処理が成功したのか,失敗したのか,知ることができないから。
- exitでバッチの終了。
この雛型を利用したバッチのサンプル:
bat中でforループをネストし,サブルーチンを呼び出して,条件付きファイル検索の結果を一斉コピーしよう (ファイル名の重複防止機能付き) - 主に言語とシステム開発に関して
(1−2)反復して実行可能に
便利なバッチほど,繰り返し実行するものだ。
例えば,コンパイルバッチとか。
下記のサンプルコードは,バッチの実行終了後,押したキーによって,処理をふたたび反復するかどうかを分岐する。
@echo off :start cls rem ----- メイン処理 ----- 〜〜 rem ----- キー入力で分岐 ----- echo. set userkey= set /p userkey=終了する (Enter) / メイン処理を再度実行 (o + Enter) / サブ処理を実行 (p + Enter) ? if not '%userkey%'=='' set userkey=%userkey:~0,1% if '%userkey%'=='o' goto start if '%userkey%'=='p' goto exec goto quit rem ----- サブ処理 ----- :exec 〜〜 rem ----- 終了 ----- :quit exit
解説:
- コロンで始まる行は,ラベル。goto文は,ラベルにジャンプすることができる。ラベル名は,「goto 〜〜」と書いた時にわかりやすく,処理の流れを理解しやすいものを。
- set /p は,ユーザ入力値を環境変数に格納する。
- %変数名:~0,1%で,文字列の0文字目から1文字分を切りだして抽出する。つまり,先頭の文字を判別する。
- if文とgotoをうまく組み合わせて,特定の条件で振り出しに戻るようにする。その際に,clsしてあげると親切。
反復実行可能なバッチのサンプル:
繰り返し実行可能なコンパイルバッチ - 主に言語とシステム開発に関して
http://d.hatena.ne.jp/language_and_engineering/20081208/1228708657
(2)バッチの構造化
上から下に向かってコマンドをダラダラと並べるだけのコードは,保守性も可読性もない。
プログラマは,そういうコードを決して書くべきではない。
※そういうコードを書くプログラマに限って,コメントを何も書かなかったりするので,事態は悪化する。
BATは文法の制約上,ソースコードを構造化し辛いが,それでも,できる範囲で構造を作っておこう。
できない事は要求すべきでないが,できるのにやらない,というのはおかしい。
(2−1)ルーチンの分割
メインルーチンと,サブルーチンを分割しよう。
バッチファイルの内部で,共通的な処理をくくりだし,
ルーチンとして再利用したい場合がある。
あるいは,バッチの処理の全体の見通しを良くするために,
複雑で長い処理をルーチンに切り分けて,メインの流れを読みやすくするケースもある。
サンプルコード:
@echo off rem メイン処理:サブルーチンのテスト call :routine_hello hoge call :routine_hello fuga call :routine_hello boo pause rem メイン処理はここで終了 exit rem 引数を受け取って,Hello と表示するルーチン。 :routine_hello echo Hello, %1! exit /b
実行結果:
Hello, hoge! Hello, fuga! Hello, boo! 続行するには何かキーを押してください . . .
解説:
- ラベルをgotoではなくcallで呼び出せば,サブルーチンになる。
- サブルーチンには,そのサブルーチン独自の引数を渡すことが可能。サブルーチン内では%1などで参照。
- サブルーチンからメインルーチンに戻る際には,exit /b する。/b オプションを付けないと,バッチ全体が終了してしまう。
次に,ルーチンからの戻り値について。
これは数値しか返却できず,大したことはできないので,あまり期待しないように。
バッチ内のルーチンから,呼び出し元に値を返却するには,errorlevelを使う。
LinuxのBashでいうところの「$?」(終了ステータス)に相当する。
するのは失敗、何もしないのは、、、 BAT errorlevel exit = バッチファイルを外部ファイル化
- call文で外部のバッチファイルを呼べる。でも、外部バッチで 「exit 0」 とかがあったりすると、処理が終わってしまう(CMD.EXEの終了)
- exit /b 数字 と記述のある外部バッチファイルを call で呼べば、数字を %errorlevel% に設定して、ちゃんと戻ってきてくれる
ERRORLEVELについてのメモ (1) - とあるソフトウェア開発者のブログ
http://d.hatena.ne.jp/simply-k/20100812/1281653517
- ERRORLEVELは、行したコマンドが終了コードを返却した場合や,callしたバッチスクリプトが「exit /b 終了コード」の形式で終了した場合に設定される
終了コード errorlevel の考え方について - その他(プログラミング) - 教えて!goo
http://oshiete.goo.ne.jp/qa/5202707.html
- exit /b ○ で設定した値を消去するには cd を行えばよい
Linux上でシェルが実行される仕組みを,体系的に理解しよう (bash 中級者への道) - 主に言語とシステム開発に関して
http://d.hatena.ne.jp/language_and_engineering/20110617/p1
- (2−6)各コマンドの実行結果は,終了ステータスに格納される
- 直前のコマンドの終了ステータスは $? という変数に格納されている
(2−2)ファイルやプロセスの分割
.batファイル自体を分割する。
複数のバッチファイル間で,処理を共有したい場合などに使う。
共通の処理は,1つのbat内にまとめておいて,複数のBATから呼び出せるようにする。
バッチの行数を長くせず,ソースコードのコピー&ペーストを減らすために必要。
2つ方法がある:
- call バッチファイル名
- start バッチファイル名
callでラベルを指定すればサブルーチンを呼び出せたのと同じように,
callでファイル名を指定すれば,別バッチを呼び出せる。
また,startで呼び出した場合は,プロセスも分かれる。
別バッチの呼び出し方について:
Windows、バッチファイルからバッチファイルを呼び出す方法あれこれ|マコトのおもちゃ箱 〜ぼへぼへ自営業者の技術メモ〜
http://piyopiyocs.blog115.fc2.com/blog-entry-280.html
- callは,呼び出し先の終了を待った後で,呼び出し元に戻る
- startは別プロセスで起動し,並行して実行する
- バッチファイル名を直接指定すると,呼び出したバッチが終了した時点で全ての処理が終わる
バッチファイ(batch,.batファイル)について質問します。 ある.. - 人力検索はてな
http://q.hatena.ne.jp/1162976244
- start b.bat はb.batを呼び出して,次に進む
- call b.bat はb.batを呼び出して,b.batが終わってから,次に進む
4.7 別バッチファイルの呼び出し
http://takeno.iee.niit.ac.jp/~shige/misc/script/bat1/node11.html
- 呼び出した子のバッチファイルが終了したら、 処理は元 (親) のバッチファイルに戻り、 その call 行の次の行に処理が移る
- exit /b [終了コード] の形式で終了コードが指定してあれば、親のバッチファイル側でそれを if errorlevel の分岐に利用できる
Windowsでのプロセス起動の仕組みは,下記を参照。
コマンドラインからプロセスを起動・終了する方法 (環境変数とレジストリについて) - 主に言語とシステム開発に関して
http://d.hatena.ne.jp/language_and_engineering/20081028/1225160338
- PATH上に存在すれば,ファイル名だけで起動できる
- AppPathのレジストリに登録されていれば,PATH環境変数に登録されていないフォルダでも,startで呼び出せる
(2−3)外部ツールの呼び出し
BATをコーディングしていて,BATでは対処しきれない,とわかったら
下記のようなコマンドを使って,別のプログラムに処理させよう。
- mshta:ワンライナーでWSHのコードを記述可能。
それぞれのノウハウは,下記を参照。
バッチ職人になろう (WindowsとLinux上での開発業務を自動化するノウハウ集) - 主に言語とシステム開発に関して
http://d.hatena.ne.jp/language_and_engineering/20110904/p1
- WSH, JScriptの項目を参照
JavaScript をコマンドラインで実行する方法 (mshta.exeの使い方) - 主に言語とシステム開発に関して
http://d.hatena.ne.jp/language_and_engineering/20081023/1224678990
コマンドプロンプトから,Win32 APIや任意のDLLを呼び出して実行しよう (コマンドプロンプトから画面キャプチャする方法の仕組みを理解) - 主に言語とシステム開発に関して
mshtaは非常に便利だ。BATだけで,いろいろできる。
- mshta.exe "javascript:JavaScriptのコード"
- mshta vbscript:execute("VBScriptのコード")
良いBATをコーディングするためには,WSHの知識も必要なのだ。
(3)ファイル操作
(3−1)for文によるスキャン
拡張子が.txtであるような全てのファイルを検索・列挙してみよう。
カレントディレクトリだけを対象とした,単純なスキャンであれば,下記のように書ける。
for %i in (*.txt) do ( echo %i )
for文+ワイルドカードで,現在のフォルダをファイル検索できる。
doの中で自由に処理できる。ここではファイル名を表示している。
※コンソール上では%iでOKだが,BATファイル中では%%iのように%を重ねることを忘れずに。
しかし,
「カレントフォルダ以下の全てのフォルダ内を,再帰的にファイル検索したい」
というニーズのほうが多いだろう。
これは,下記のようにコーディングできる。
for /F "usebackq" %i in (`dir /s /b *.txt`) do ( echo %i )
フォルダを除外して,全ての拡張子のファイルを再帰的に取得したい場合は,下記のように書ける。
for /F "usebackq" %i in (`dir /A-D /s /b *.*`) do ( rem %i がファイルパス 〜 )
forコマンドにusebackqオプションを渡せば,inの中身にコマンド出力を利用できる。
doの中では,そのコマンドの実行結果を1行ずつ処理させる。
ここではコマンドとして,ファイルの再帰検索コマンドを記述する。
もし検索の仕方をカスタマイズしたければ,forコマンドではなく,dirコマンドのオプションを自由に変えればよい。
この記法を覚えると,BATでできる事の幅が,ぐっと広がる。
Bashで言うと,$(〜) とか `〜` に相当する記法だ。
この便利さを,よく覚えてほしい。
「あるコマンドの実行結果を,別のコマンドに渡す」ための方法として,普通は「|」(パイプ)を使うだろう。
例えば
ipconfig | more
のように。
だが,「あるコマンドの実行結果を,別のコマンドに渡して,1行ずつ自由に処理する」場合は,for文が非常に便利なのだ。
後述するが,コマンドの実行結果を動的に変数に格納したりする場合にも,forが一役買う。
中級のBATコーディングにおいて,for文はパイプよりも重宝する,と言っていい。
ここでは,for文によるファイルスキャンを応用してみよう。
rem 不要なファイルを再帰的に検出して削除 for /F "usebackq" %%i in (`dir /s /b /a-D ^| findstr /V ".*\.html$" ^| findstr /V ".*\.css$" `) do ( rem 削除確認しながら削除 del /P %%i )
ファイルスキャンをした後で,スキャン結果から特定のファイルだけを除外し,その後,削除している。
これはつまり,フォルダツリー上にhtmlとcssだけを残して,他の余計なファイルが存在したら削除する,という整理バッチだ。
解説:
- inの中身のコマンドのパイプ「|」は,キャレット「^」でエスケープする必要がある。
- dirコマンドの /a-D オプションは,出力からフォルダは除外するということ。
- findstrの /V オプションは,マッチしたものを除外するという意味。
(3−2)if文による判定
ファイル操作時には,ファイル存在判定が付きもの。
なければ作る,とか,あれば削除,とか。
rem ファイルがあれば削除 if exist %TMP_FILE_NAME% ( del %TMP_FILE_NAME% )
if文についてもっと詳しく知りたければ,「if /?」して,マニュアルを参照のこと。
ifの中では,あまり複雑な判定はできない。
一応,else if と else の構文は実現可能。
バッチファイルで、if〜else - nursの日記
http://d.hatena.ne.jp/nurs/20101110/1289396618
- 括弧"("または")"の両側には必ずスペースが必要
(3−3)中間ファイルの利用
例えば,あるファイルの中味を加工して保存したい,という場合,どうするか。
加工処理の結果自体はコンソール上に表示できるけども,それをリダイレクトして,ファイルに保存しなければならない。
同一ファイル内へのリダイレクトはできないので,中間ファイルを利用することになる。
例として,ファイルの中味を逆ソートして保存し直す例:
type hoge.txt | sort /r > temp.txt type temp.txt > hoge.txt del temp.txt
面倒。だが,処理の流れとしてシンプルではある。
これがBATの限界なので,甘受する必要がある。
できるだけ一時ファイルは作らないのが望ましいが,必要な場合は,サッと作ってサッと消す。
(4)変数と演算
環境変数には,基本的に文字列を格納することになる。
ファイルパスも,日付や時間も,ぜんぶ文字列である。
これらの上手い扱い方が,BATによる情報処理を左右しうる。
(4−1)コマンド実行結果の保管
可能である。
cdコマンドの実行結果を変数に保管する場合:
rem カレントフォルダを変数に保持 for /F "usebackq" %%i in (`cd`) do ( set BAT_DIR="%%i" ) rem 変数の内容を表示 echo %BAT_DIR% pause
このように,コマンドの実行結果を別の文に渡すために,for文は多いに役立つ。
forはもともと,複数回の処理をループして実行するための制御構文だが,このように1回きりの処理にも応用できる。
(4−2)数値演算
文字列ではない演算も,やればできる。整数限定で。
カウンターなどの用途に,かろうじて使える程度だが。
setコマンドの/aオプションを使えば,算術演算が可能だ。
サンプルコード:
@echo off set /a CNT=1 rem ※算術変数は遅延展開され加算が可能 rem http://fpcu.on.coocan.jp/dosvcmd/bbs/log/cat3/4-0873.html call :routine_echo_count call :routine_echo_count call :routine_echo_count pause exit :routine_echo_count echo %CNT% 回目 rem echo 数値をインクリメントします。 set /A CNT=%CNT%+1 exit /b
実行結果:
1 回目 2 回目 3 回目 続行するには何かキーを押してください . . .
(4−3)日付や時間の加工
%date% 変数を使いやすく加工する。
日付(1ケタの数は0埋めされるので心配なく):
set today_YYYYMMDD=%date:~0,4%%date:~5,2%%date:~8,2%
時間:
set now_HHmmSS=%time:~0,2%%time:~3,2%%time:~6,2%
部分文字列の抽出で,使いやすい形式に加工できる。
これらを,バッチで生成するファイルの名称などに埋め込んでおけば便利だ。
(4−4)ファイル名の加工
%1という環境変数から拡張子だけ抜き出すには,%~x1とする。
同じような操作がいろいろ可能なので,一覧表を参照のこと。
バッチファイルの制御用コマンド [FPCU]DOS/V&Windowsコマンド・プロンプト・リファレンス
http://fpcu.on.coocan.jp/dosvcmd/batch.htm#param
- 環境変数の記法の一覧表
利用中の拡張子を抽出するバッチ (存在するファイルの拡張子の種類を,バッチで全取得する) - 主に言語とシステム開発に関して
(5)その他の処理
(5−1)スリープ
5秒待つ:
ping localhost -n 5 > nul
「コマンドプロンプトで,処理を指定時間秒だけ sleep させるために ping を使う」という裏技である。
waitコマンドとかsleepコマンドがないので,pingで代替している。
これを使ったサンプル:
コマンドラインからマウスを操作する方法 (rundll32.exeで動くDLLの作成法) - 主に言語とシステム開発に関して
http://d.hatena.ne.jp/language_and_engineering/20081117/1226943698
離席中のチャットのログを自動でメール送信してくれるソフトの作り方 - 主に言語とシステム開発に関して
http://d.hatena.ne.jp/language_and_engineering/20081207/1228642113
(5−2)ログとリダイレクト
処理の合間に,頻繁にechoでログ出力するとよい。
以下はエラー出力の扱い方について。
サンプル:
main.bat(サブバッチの出力を,ログファイルにリダイレクトしている)
call sub.bat > log.txt pause exit
sub.bat(存在しないコマンドを呼んでエラーを発生させている)
@echo off cd cdHOGE cd exit /b
実行結果:(コンソール上)
'cdHOGE' は、内部コマンドまたは外部コマンド、 操作可能なプログラムまたはバッチ ファイルとして認識されていません。 続行するには何かキーを押してください . . .
log.txt内:
D:\temp D:\temp
ふつうのリダイレクトは,標準出力(「1」)に書き込まれる。
エラーメッセージは,エラー出力(「2」)に出てくる。
なので,これだと,エラーメッセージをログに記録しておくことができない。
これを解決するためには,リダイレクタの操作を行う。Linuxと同じである。
main.bat内で,リダイレクトの書き方をちょっと変える。
call sub.bat > log.txt ↓ call sub.bat > log.txt 2>&1
これで実行すれば,log.txtには,エラーメッセージもちゃんと漏れずに記録されている。
リダイレクト先がマージされたためである。
D:\temp 'cdHOGE' は、内部コマンドまたは外部コマンド、 操作可能なプログラムまたはバッチ ファイルとして認識されていません。 D:\temp
詳しくはBashと同じなので,下記のエントリを参照。
Linux上でシェルが実行される仕組みを,体系的に理解しよう (bash 中級者への道) - 主に言語とシステム開発に関して
http://d.hatena.ne.jp/language_and_engineering/20110617/p1
- (2−4)標準入力や標準出力とは,プログラムへの入出力を抽象化・一般化したものである
- 「2>&1」は,「2の代案を1にする」という風に音読すると暗記しやすい
結びに
ここまでの情報を使いこなせば,ベターなBATをコーディングできるだろう。
コマンドが並んでいるだけの初等的なバッチではなく,
ツールないしソフトウェアとしての,立派なバッチを。。。
関連する記事:
Linux上でシェルが実行される仕組みを,体系的に理解しよう (bash 中級者への道) - 主に言語とシステム開発に関して
http://d.hatena.ne.jp/language_and_engineering/20110617/p1
ドキュメント作成を楽にするための,Excel VBA 頻出8パターン - 主に言語とシステム開発に関して
http://d.hatena.ne.jp/language_and_engineering/20090401/p1
バッチ職人になろう (WindowsとLinux上での開発業務を自動化するノウハウ集) - 主に言語とシステム開発に関して
購入: 8人 クリック: 236回
クリック: 12回
- 160 http://b.hatena.ne.jp/
- 64 http://t.co/KSha7IDmCF
- 62 http://b.hatena.ne.jp/hotentry/it
- 61 http://b.hatena.ne.jp/hotentry
- 43 http://www-ig-opensocial.googleusercontent.com/gadgets/ifr?exp_rpc_js=1&exp_track_js=1&url=http://choichoi.sakura.ne.jp/hatena_bookmark.xml&container=ig&view=default&lang=ja&country=US&sanitize=0&v=817aea3019e19ce6&parent=http://www.google.com&lib
- 39 http://www.hatena.ne.jp/
- 38 http://reader.livedoor.com/reader/
- 19 http://b.hatena.ne.jp/entry/d.hatena.ne.jp/language_and_engineering/20130502/PatternsOfMSDOSorBAT
- 19 http://www.feedly.com/home
- 18 http://www.google.co.jp/reader/view/