- note -

Batch

2/10

batのイディオム集   - Last modified:2015/3/20

基本的な構文や常套句、小技等を思いつくまま集めてみました。
便利関数とかはイディオムの域を超えていますが、
めんどうなのでそれらも全てここに載せます。
(やる気が続けば)随時追加していく予定です。

おまじない

@echo off
setlocal
batを書くときのおまじないです。
echoをoffにして変数のローカル化をするだけですが、
ほぼ全てのbatで無条件に書いていい、或いは書くべきものだと言えます。
遅延展開を有効にする場合はsetlocal enabledelayedexpansionと書きます。
ラインエディタ … 16~32bitのWindowsにはedlinというラインエディタがある。

簡易ラインエディタ

copy con hoge.txt > nul
終了するときはCtrl + Zを行頭で入力してEnterを押します。
/bオプションをつければ行頭ではなく任意の位置で編集を終了できます。

改行なし出力

set /p =foo<nul
set /p =bar<nul

--- 実行結果 ---
foobar
fooやbarの位置に指定した文字列は、echoと違い改行を行末に付加することなく出力されます。

空ファイル作成

fsutil file createnew hoge.txt 0
copy nul hoge.txt
c:>hoge.txt
上の3つは全てカレントディレクトリにサイズが0のhoge.txtというファイルを作成します。
3つ目は、カレントドライブがC:であることを仮定した場合。

プロンプト表示を2行に

prompt $P $T$_$G
個人的によく使っているプロンプト表示設定です。

カレントディレクトリをエクスプローラで開く

start.
そのまんまです。

パスの通っているコマンドの場所を表示

>for %I in (cmd.exe) do @echo;%~$path:I
C:\Windows\System32\cmd.exe
いわゆるwhich。

無限ループ

@echo off
setlocal enabledelayedexpansion
set/p _=3.141592<nul
for /l %%I in (0,0,0) do set/p _=!random:~-1!<nul
gotoでも出来ますが、for /lならラベル不要でお手軽です。

標準入力の受け取り

iniファイル読み込み

for /f "delims=^= tokens=1,*" %%I in (hoge.ini) do (
  if %%I == key (
    set value=%%J
  )
)
簡易的なiniファイル読み込み処理です。
セクションも考慮する場合はフラグを追加する必要があります。

16進数変換

cmd /c exit /b 65
echo %=ExitCode:~6%

--- 実行結果 ---
41
10進数 → 16進数の変換は上記の方法で出来ます。 簡単ですね。

ASCIIコード変換

@echo off
setlocal enabledelayedexpansion

for /l %%I in (0,1,255) do (
  cmd /c exit /b %%I
  if not "!=ExitCodeAscii!" == "" echo;!=ExitCode:~6!=!=ExitCodeAscii!
)

--- 実行結果 ---
20=
21=!
22="
  ・
  ・
  ・
7D=}
7E=~
表示可能な文字(0x20~0x7e)なら、ASCIIコードを文字に変換できます。

関数の実現

@echo off
setlocal enabledelayedexpansion
echo hoge
set foo=bar
call :sub foo
echo %errorlevel%
exit /b
:sub
echo %1
echo !%1!
exit /b 42

--- 実行結果 ---
hoge
foo
bar
42
ラベル名を引数にしてcallコマンドを使うと、gotoと同じようにそのラベルまでジャンプします。
gotoと違い、callを使えば引数が指定でき、ジャンプ先でexit /bを使えば
ジャンプ元のcallに戻れるので、これを利用して擬似的な関数(サブルーチン)が実現できます。

デバッグ関数

:debug
set /p debug=debug:
%debug%
goto debug
callで呼び出して変数の値などを確認できます。
呼び出し元に戻る場合はexit /bと入力します。

文字数カウント関数





!EMPTY! … EMPTYは存在しない変数名ならなんでもよい。
:GetLength
setlocal enabledelayedexpansion
for /l %%I in (0,1,8192) do if !%1:~%%I^,1! == !EMPTY! exit /b %%I
exit /b 0
引数 … 引数として受け取るのは変数の名前。 引数として受け取った変数の文字数をカウントします。
文字数はerrorlevelに格納されて呼び出し元に戻ります。
:GetLength
setlocal enabledelayedexpansion
set l=0
:gll
if !%1:~%l%^,1!==!! exit/b %l%
set/a l+=1
goto gll
for /lではループ中にexitしても残ループ回数分の式展開が行われてしまうようなので、
このようにgotoを使った方がいいかもしれません。

パディング関数

@echo off
setlocal

set hoge=517

echo original :%hoge%
call :Padding hoge 8 0
echo 0 padding:%pad_result%
call :Padding hoge 8 1
echo s padding:%pad_result%
exit /b

:Padding
call :GetLength %1
if %errorlevel% geq %2 (
  call set pad_result=%%%1%%
  exit /b
)
set p0_padStr0=0000000000000000
set p0_padStr1=                
set /a p0_padLength=%2-%errorlevel%
call set pad_result=%%p0_padStr%3:~0,%p0_padLength%%%%%%1%%
exit /b

:GetLength
setlocal enabledelayedexpansion
for /l %%I in (0,1,8192) do if !%1:~%%I^,1! == !EMPTY! exit /b %%I
exit /b 0

--- 実行結果 ---
original :517
0 padding:00000517
s padding:     517
レフトパディングを設定します。
桁数 … 最大16桁まで。 第一引数は対象の変数、第二引数は桁数、第三引数は0なら0パディング、1なら空白パディングという感じです。
パディング処理の実行結果はpad_resultに格納されるので、Padding呼び出し後にこれを参照します。
内部でGetLengthを使用しているので、併せて定義しておく必要があります。

配列の実現

@echo off
setlocal enabledelayedexpansion
set line=-1
for /f %%I in ('echo hoge^&echo moge^&echo piyo') do (
  set /a line+=1
  set array[!line!]=%%I
)
for /l %%I in (0,1,%line%) do (
  echo array[%%I] = !array[%%I]!
)

--- 実行結果 ---
array[0] = hoge
array[1] = moge
array[2] = piyo
setコマンド使用時、変数名にカウンタ変数の値を埋め込むことによって擬似的な配列を実現できます。

callを利用した多重展開

@echo off
setlocal
set line=-1
for /f %%I in ('echo hoge^&echo moge^&echo piyo') do (
  set /a line+=1
  call set array[%%line%%]=%%I
)
for /l %%I in (0,1,%line%) do (
  call echo array[%%I] = %%array[%%I]%%
)

--- 実行結果 ---
array[0] = hoge
array[1] = moge
array[2] = piyo
通常、複合文(()や&で連結された文)のブロック内では
ブロック単位で一気に構文解析が … このあたりの詳しい説明はset /?で見ることができる。 ブロック単位で一気に構文解析が行われるため、ブロック内で変数の内容を変更しても
同一のブロック内ではその変更結果は反映されません。
しかしコマンドをcallつきで呼び出すと実行時に構文解析が再度行われるため、これを利用すれば
()内であっても遅延展開を使用することなく変更結果を反映できるようになります。
上のコードは配列実現の例を、遅延展開から多重展開に書き換えたものです。

デフォルト入出力先の変更

echo … 例ではechoを使用しているが、どんなコマンドでも可能。
以降の標準出力を非表示にする場合
echo 1>nul 3>nul

以降の標準エラーを非表示にする場合
echo 2>nul 3>nul

以降の標準出力と標準エラーを非表示にする場合
echo 1>nul 2>nul 3>nul 4>nul

echo 1>nul 2>nul 3>nul 4>nulから元に戻す場合
echo 1>nul 2>nul 5>con 6>con
未使用のディスクリプタに退避 … 9までいくと、もう戻せなくなったりする。 リダイレクトを行うと元のリダイレクト先が3以降の未使用のディスクリプタに退避されます。
ここで退避先のディスクリプタもリダイレクトを行うと、
それ以降のデフォルト出力先を切り替えることができます。
元に戻す場合は同じ操作を、上書きした番号よりさらに大きい番号をconにリダイレクトして行います。

sleepの実現

sleepの実現を参照

様々なコメント

selfcall

@echo off
setlocal

if "%~1" == "selfcall" goto %2

call %~f0 selfcall hoge
rem 或いは cmd /c %~f0 selfcall hoge
exit /b

:hoge
rem 処理
exit /b
batで小細工をしたいときに重宝します。
イディオムというよりはバッドノウハウですね。

batの関数内で標準出力に出力した内容をfor /fにかける

@echo off
setlocal enabledelayedexpansion

if "%~1" == "selfcall" goto %2

for /f %%I in ('call %~f0 selfcall hoge') do (
  set /a mod3=%%I%%3
  set /a mod5=%%I%%5
  if        !mod3! == 0 ( echo Fizz
  ) else if !mod5! == 0 ( echo Buzz
  ) else                  echo %%I
)
exit /b

:hoge
for /l %%I in (1,1,5) do echo %%I
exit /b

--- 実行結果 ---
1
2
Fizz
4
Buzz
selfcallをfor /fにかけるだけです。
こうすれば標準出力は一種のキューと見なせるので、
実行速度に目を瞑れば汎用性はそれなりにありそうです。

任意のbatコンテキストからexit

@echo off
setlocal

if "%~1" == "selfcall" goto %2

cmd /c "%~f0 selfcall main"
echo;bat end
exit /b

:main
call :sub
echo;main end
exit

:sub
echo;sub end
exit

--- 実行結果 ---
sub end
bat end
cmd /cのselfcallでbatコンテキストを別プロセス化しておきます。
こうすることで、それ以降任意タイミングのexitによりcmd /cの呼び出し元まで処理を戻すことができます。

第4オクテットの全範囲に一括ping

@echo off
setlocal enabledelayedexpansion

if "%~1" == "" exit /b
if %~1 == selfcall goto %2

set ip=%1
for /f "delims=" %%I in ('%~f0 selfcall run ^| sort') do (
  set l=%%I
  echo;!l: =!
)
exit /b

:run
for /l %%I in (0,1,255) do (
  set s=
  if %%I leq 99 set s= 
  if %%I leq 9 set s=  
  start /b cmd /c "ping -n 1 -l 1 -w 2000 %ip%.%%I >nul && echo;%ip%.!s!%%I"
)
ping -n 3 localhost >nul
exit /b
一括ping!便利!
ppping.batとでも保存して
>ppping 192.168.0
みたいにやるだけです。
ちょっと修正すれば第3オクテットを可変に、とかもOK。

おまけ

prompt $T$_ 0>nul 3>nul
0.
[少年DOS]
知らなかった小技から複合テクニックまでとても参考になりました!!
1.
[No name]
改行なし出力の意味がわかりません
2.
[OGN]
!=ExitCode:~6! とか !=ExitCodeAscii! とか どこに載ってる情報なんですか?