38
36

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

/bin/sh に慣れた人に贈る CMD バッチの書き方

Last updated at Posted at 2014-05-01

(本ページは今後更新されません。最新版は zenn.devのBookをご覧ください)

ここに書いてあるようなことはだいたい「CMD.EXE /?」「for /?」で出るんだけど、長いので sh 経験者の観点でかいつまんでみた。

コマンド出力の引用

ls | while read line ; do echo ">>$line" ; done

には

for /F %%I IN ('dir /b') DO echo ">> %%I"

が対応する(丸括弧の内側にシングルクォートがあることに注意)

逆クォート相当のものも1行程度なら

for /F %%I IN ('echo AHAHA') DO SET "OUTPUT=%%I"

と出来なくもない。

for は頑張れば AWK に近いことも出来るので、「for /?」を見ることをお勧めする。

basename と dirname

dirname 相当は次のような感じで出来る。

[c:tmp]
$ type dirname.cmd
@echo off
for %%I IN ( c:\path\to\file ) do set "DIRNAME=%%~dpI"
echo %DIRNAME%
[c:tmp]
$ dirname
c:\path\to\

basename の場合は %%~dp のところを %%~nx か %%~n にすれば OK。

%~ の後の「英小文字」は変数の中身を加工する意味があるので、変数名自身は「英大文字」にしておいた方がよい。

find -exec

これ、自分の %HOME%\bin に置いている、%HOME%以下のツリーにあるゴミファイルを片づけるバッチ。

cd %~dp0..
for /R %%I in (*.orig *~ svn-commit*.tmp .*.swp *.rej ) do (del /P "%%I")

最初の「%~dp0..」はバッチが置いてあるディレクトリの親ディレクトリの意味。

forfiles.exe (追記:2015.08.10)

最近の Windows では forfiles.exe という find 相当のコマンドも標準装備されている

forfiles /S /M *.cpp /C "cmd /c echo @path"

こちらは一度に /M オプションで指定できるパターンは一つだけのようだ。
だが、ファイル名の展開用マクロ(@path,@isdir)がいろいろ使える

ブロックIF

古い単語出た。IF や FOR は1行で書いてしまわないといけないかというと、丸括弧を使うと複数行に展開できる。

if "%1" == "a" (
    echo arg is a
    echo aaaaaaaa
) else (
    echo arg is else
    echo bbbbbbbb
)

ただし、環境変数の展開のタイミングが if 文実行前なので、そこは工夫が必要だ。
(詳しくは SET /? を参照)

(追記:2018.09.15)
SETLOCAL ENABLEDELAYEDEXPANSION を使えば「!環境変数名!」形式の遅延環境変数の展開が使える(詳しくは SETLOCAL /? のヘルプを見よう)。ただし、自分は綴りを覚えられないので、FOR の中からCALL :ラベル "%%I" を使って誤魔化すことが多い。

シェルレベルでの二重起動防止

  • ps は tasklist で代用可能
  • grep は findstr で代用可能
  • findstr の成否は ERRORLEVEL に出る
  • IF ERRORLEVEL 1 くらいは知ってるよね

あとは分かるな?

tasklist | findstr ttermpro.exe
if errorlevel 1 (
   rem 起動していない
)else(
   rem 起動してる
)

where.exe (追記:2018.09.15)

C:\Windows\System32\where.exe は which と find の両方の機能がある。

オプションなしだと、全PATHを検索する which のような機能になる。

$ where nyagos.exe
C:\Users\hymko\Share\bin64\nyagos.exe
C:\Users\hymko\Share\bin\nyagos.exe
C:\Users\hymko\go\bin\nyagos.exe

/R オプションをつけると find に近くなる。

$ where /R . *.go *.md
C:\Users\hymko\go\src\github.com\zetamatta\findo\findo.go
C:\Users\hymko\go\src\github.com\zetamatta\findo\hoge and hoge.md
C:\Users\hymko\go\src\github.com\zetamatta\findo\readme.md
C:\Users\hymko\go\src\github.com\zetamatta\findo\system_linux.go
C:\Users\hymko\go\src\github.com\zetamatta\findo\system_windows.go

タイムスタンプの取得 (追記:2018.09.15)

※ 地域設定によって変わるかどうか、要確認

分以上(まだ、お手軽・早い)

$ type f.cmd
@echo off
for %%I in (readme.md) do set "STAMP=%%~tI"
echo %STAMP%
$ f
2018/09/13 22:35

秒まで(forfiles版)

$ type m.cmd
@echo off
for /F %%I in ('forfiles /P "%~dp1." /M "%~nx1" /C "cmd.exe /c echo @fdate-@ftime"') do set "STAMP=%%I"
echo %STAMP%
$ m readme.md
2018/09/13-22:35:40

※ 0時~9時までは、時部分が一桁になるので注意が必要

秒まで(where版)

$ type x.cmd
@echo off
for /F "tokens=2,3" %%I in ('where /R . /T "%1"') do set STAMP=%%I-%%J
echo %STAMP%
$ x readme.md
2018/09/13-22:35:40

※ 0時~9時までは、時部分が一桁になるので注意が必要

0~9時のタイムスタンプに対する対策(修正:2018.09.24)

where でも forfiles でも時間部分が一桁の場合、タイムスタンプの比較が不具合を起こす。やむをえないので正規表現で判別するというベタな方法で対応する。

for /F "tokens=2,3" %%I in ('where /R . /T "%~1"') do set STAMP=%%I_%%J
echo %STAMP% | findstr _[0-9]: > nul && set STAMP=%STAMP:_=_0%

文字列置換 (追記:2018.09.24)

bash だと

$ FOO=abcdef
$ echo ${FOO/cd/12}
ab12ef

という置換が使える。CMD.exe でも似たようなことが出来る。

$ set FOO=abcdef
$ echo %FOO:cd=12%
ab12ef

改行しない出力 (追記:2020.01.19)

DOSコマンドで、好きな形式に「パスのコピー」する - Qiitaより

echo -n MESSAGE 相当の改行しない echo は

SET /P ="MESSAGE" < NUL

で実現できるようだ。この SET は本来 SET /P VAR=PROMPT という形式で、PROMPT を表示した後、ユーザが入力した値を環境変数 VAR に設定するコマンドだが、環境変数名は省略できる。さらに標準入力を NUL でリダイレクトすることで、ユーザ入力自体も素通りさせられる。

今日の日付の取得 (追記:2020/04/03)

地域を日本に設定している場合、%DATE%yyyy/mm/dd 形式の日付を得られる。スラッシュを削除して yyyymmdd 形式で得る場合、%DATE:/=% とすればよい。

地域設定関係なく、yyyymmdd 形式で得たい場合、以下のコマンドで、

$ wmic os get LocalDateTime
LocalDateTime
20200403103451.442000+540

と得られるので、

for /F %%I in ('wmic os get LocalDateTime ^| findstr ^^[1-9]') do set DT=%%I
echo %DT:~0,8%

といった方式で 20200403 といった文字列が表示される。
(wmic については…ちょっと出典失念)

複数行 echo を括弧でまとめる (追記 2020/10/09)

( echo (arxload "%ROOT%/hoge1.arx"^)
  echo (arxload "%ROOT%/hoge2.arx"^)
  echo (arxload "%ROOT%/hoge3.arx"^)
) > "%CADDIR%\hoge.scr"
  • 複数の echo を丸括弧でまとめて、一つのリダイレクトで出力させることができる。
  • echo 中で丸括弧の閉じる方を出力する場合は ^ でエスケープする必要がある

パスの末尾の余分な \ を削除する (追記 2020/10/09)

echo %CADDIR%| findstr "\\$" >nul
if not errorlevel 1 set "CADDIR=%CADDIR:~0,-1%"
  • パイプ記号 | の直前に空白を入れると機能しなくなるので、注意!

バッチファイルでサブコマンド (追記:2020.10.18)

call :"%~1" のように、ラベル名を二重引用符で囲むと、サブコマンド名が渡されなかった時も処理できる。

make.cmd
setlocal
call :"%~1"
endlocal
exit /b

:""
:"all"
    go fmt
    for %%I in (386 amd64) do (
        set GOARCH=%%I
        if not exist cmd\%%I mkdir cmd\%%I
        go build -o cmd\%%I\findo.exe -ldflags "-s -w"
    )
    exit /b

:"package"
    for %%I in (386 amd64) do zip -j findo-%%I-%DATE:/=%.zip cmd\%%I\findo.exe
    exit /b

:"upgrade"
    for /F %%I in ('where findo') do copy /-Y findo.exe "%%I"
    exit /b
38
36
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
38
36

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?