早いものでシステムエンジニア屋さんになってから1年が経過しました。
テクサポ時代には全く知らなかった技術を勉強することができて、会社にはとっても感謝しています。
とっても感謝はしているんですが、うちの会社はなんというか、若者を育てるノウハウが無いので、実はかなりの放任主義です。
「じゃあとりあえずやってみてね」で沖合にいきなり放り投げられます。
もちろん聞いたら色々教えてくれるんですが、そもそも初心者レベルのそのまた一歩手前くらいの僕からすると何を聞けば良いのかもわかんねえので、毎回ググりにググり倒してなんとか自力で陸まで戻っています。
そんな感じで1か月前くらいに「じゃあファイルをコピーするバッチファイル作ってみて」と放り出されてまぁまぁ苦労したので、その時に得た知見と「先に知っておきたかったこと」を織り交ぜて書いていこうと思います。
※本記事はスーパー初心者であるしもやぎがハイパー初心者めがけて書いています。正確でない表現や内容が多々見受けられるかもしれませんがどうか耐えてください。※
そもそも無理せずPowershellのコマンドでやった方が早かったりする
これはマジでホントなんですけど、そもそもコマンドプロンプト(MS-DOS?)じゃなくてPowershellのコマンドでやった方が断然上手くいく可能性が高いです。
事例①
よーし、バッチを書くぜ、まずは日付と時間を取得してそれをログの名前にするぜ、と思って調べると色んなやり方がでてくると思います。
まあなんであっても %date% と %time% とかの予め用意されてる変数を使うと思うんですが、例えばこういう感じのがでてきませんでしたか?
@rem %date%で取得した日付にはスラッシュが入っててそのままだとファイル名に使えないのでスラッシュを空白で置換しつつ取得
set yyyyMMdd==%date:/=%
@rem %time%で取得した時間にはコロンが入っててそのまだと(ry
set hhmmss=%time::=%
@rem ↑で取得した時間はミリ秒まで入っているので要らない部分を削る
set hhmmss=%hhmmss:~0,6%
@rem でも%time%で取得した日付は午前1時とかだと01とはしてくれずに「 1」みたく空白を入れられるからそれを弾いてそれからそれから...
いや、やらんやん!!!!!!
なんで日時を取得するだけでこんな面倒なことを…
Powershellのアニキ!お願いしやす!
for /f "usebackq" %%a in (`Powershell -Command Get-date -Format "yyyy-MM-dd_HH-mm-ss"`) do set logname=%%a
for文についてはまた後で解説するんですが、とにかくこれでバッチリ日時取得ができます。
コマンドプロンプトからPowershellのコマンドが実行できるんですね。それも知らなかったよ。
さらに言うと、%date% はローカルの言語設定(たぶん)によって得られる日時のフォーマットに違いがあるようで、初めの頃に自分のPC(日本語)で作ったバッチを検証環境のWindows Server(英語)に持っていったらログのファイル名が意味わからんことになってました。
英語だと %date% の先頭に曜日が入ったりするからだったんですね。テメーッ!
ログの出力
ログの話が出たので続けてログ出力について。
ログを出力するには、コマンドの最後に「 > result.log」みたいなのを付けます。
@echo off
echo フトゥー、しもやぎです。 > result.log
echo ぐふぐふ >> result.log
2行目は「echoの内容を、 result.log ファイルを新規作成してそこに書き込んで~」です。
3行目は「echoの内容を、既にある result.log ファイルに追記して~」です。
3行目を「> result.log」にしちゃうと「フトゥー、しもやぎです。」が書かれたログは上書きされて消えちゃいますので注意。
「毎行書かなきゃいけないわけ!?」と思ったアナタ、run.bat から main.bat を call してそれをログに出せば1回で済みますぜ。
call main.bat > result.log
echo もう、終わりだよ...
標準出力とエラー出力とnul
出力関係でこちらも。
そもそもコマンドプロンプトには「標準出力」と「標準エラー出力」があることを知ってましたか?
知らなかったよ僕は。取説のどこに書いてあるのそれ。
たとえば、何も考えずバッチに echo I love shimoyagi とか書いて実行したら I love shimoyagi は「標準出力」に出力されます。つまりコマンドプロンプト上で確認できます。
でも、
echo I love shimoyagi > result.log
とした場合、result.log内に I love shimoyagi が出力されるので、「標準出力」には出力されません。
つまり、コマンドプロンプト上では確認できないです。
「標準エラー出力」ってのは、コマンドがエラーになった時の出力先です。
あんまり僕も分かっていませんが、何が大事かって言うと、
call main.bat > result.log
↑このコマンドが失敗した場合、今の状態では result.log には失敗時のメッセージが出力されないってことです。
例えば、main.bat のファイル名を間違えて main1.bat としてしまった場合
call main1.bat > result.log
コマンドプロンプト上には「’main1.bat’ は、内部コマンドまたは外部コマンド、操作可能なプログラムまたはバッチ ファイルとして認識されていません。」と表示されますが、result.logは空っぽです。
何故か?「標準エラー出力」をファイルに出力する指定ができてないからです。
「標準出力」も「標準エラー出力」もまとめてログに出力する場合は、「2>&1」を付け足しましょう。
call main1.bat > result.log 2>&1
ちなみにですが、> nul を付けるとどこにも出力されなくなります。
eventcreateコマンドでイベントログを作る時にこの「成功: 種類が ‘error’ のイベントが~」ってやつがログに入っちゃうのがイヤだったんですが、> nulを付けることで解決しました。
For文の書き方
「For文というのは何かの処理を繰り返しグルグル回すためにある」という程度の理解はありましたが、実際自分で書いてみようと思うと色々事前理解が必要でした。
Forにも色々オプションがあるので、僕が今回よくお世話になった2パターンを簡単に紹介します。
パターン① Powershellのコマンドで情報を取ってきて使う
for /f "usebackq" %%a in (`Powershell -Command Get-date -Format "yyyy-MM-dd_HH-mm-ss"`) do set logname=%%a
さっき日時取得の時に使ったやつですね。
For の /f オプションを使うと「Powershellコマンドを実行した結果を取得する」ってことが可能になります。これが便利。
上記は Powershell の Get-dateコマンドで日付を取得した結果を、%%a という変数に一旦納め、それを logname という名前の変数にsetする、という内容です。
この %%a の扱いが難しかったんだよなァ!突然出てくるんだもんなァ!
パターン② 外部ファイルを読み込む
for /f "tokens=1,2 delims=," %%a in (Machines.ini) do (
echo Hostname:%%a
echo IP:%%b
)
LAB01,192.168.xxx.xxx
LAB02,192.168.yyy.yyy
これは別途用意した設定用のファイル(今回はMachines.ini)を1行ずつ読み込むパターン。
そうなんです、勝手に1行ずつ読み込んでくれるんです。それ取説のどこに(ry
内容としては .ini 内のマシンのホスト名とIPアドレスを順番にechoしていくって感じです。
これの良いところは対象のマシンが何台あろうとも .ini に追記するだけで対応できるところ。
tokens=1,2ってのは「 .ini の1行分の中身を2つの要素に分けます」という意味です。
1行目で言うと、1つ目の要素がLAB01で、2つ目の要素が192.168.xxx.xxxですね。
そしてこのfor文内において、この1が%%a、2が%%bとして扱われます。3があれば…そう、%%cですね。
今回は %%a in (Machines.ini) のように %%a からスタートすることを宣言しているので上記の通りですが、これが %%f in だった場合は、1が%%f、2が%%gとなります。アルファベット順です。
んで、じゃあ1と2をどうやって区切るのか、を指定しているのが delims=, です。
delimiter=区切り文字、今回は「,」を要素(tokens)の区切りとして指定しています。
%Errorlevel%(エラーレベル)と戻り値
コマンドが成功したかどうかを判断するためのものとしてエラーレベルっつーもんがあるらしい。
コマンドを実行した直後に echo %errorlevel% とかやってみないと目には見えないんだけど、確かにそこにある、本当に大切なオンリーワンらしい。
こいつのおかげでエラー判定が捗るんだけど、ちゃんと「本当にこのエラーレベルは直前のこのコマンドから返ってきてるやつか?」ってのはよくよく吟味した方が良さげです。
特にFor文とかIf文の中とかで使う時ね。
サブルーチン
「%shimoyagi% の中身が love だったら イイネをechoする、それ以外だったら帰ってくださいをechoする」
みたいに一連の動作を1つのサブルーチンとしてまとめて、好きな時に呼び出してラブをチェックすることのできる都合の良いやつです。
set shimoyagi=ダメ
call :love_check
pause /b
:love_check
if %shimoyagi%==love (
echo イイネ
) else (
echo 帰ってください
)
今回はサブルーチンっつー便利なものがあるよ、というレベルの話にとどめておきますが、愚直にバッチを書き進めていくより、サブルーチンを1つ作って、それを呼び出した方がスマートなことがあります。
いわゆる普通のプログラミング言語でいうところの「関数」ってやつかと思います。わっかんねーけど。
パイプライン
コマンドの実行結果をいちいち変数に格納して、呼び出して…ってやるのめんどいですよね。
そんなときにはパイプラインを使って身柄を即時引き渡しちゃいましょう。
net user DefaultAccount | findstr アカウント有効
「|」こいつがパイプライン君です。よろしくな。
上のコマンドは net user DefaultAccountの実行結果の中から「アカウント有効」って文字列が含まれる行を探して表示する、って内容です。
「|」を境に2つのコマンドが並んでいるのが分かるかと思います。
パイプライン君無しだと、net user DefaultAccountの結果を一旦どっかに格納して、その中身をfindstrで探す、みたく処理が別れちゃうので便利。
特殊文字のエスケープ
あれ?パイプライン君じゃん!^^
どしたの?元気?
………パイプライン君?
エスケープされた^パイプライン君「…」
1つ前で紹介したように記号「|」はコマンドプロンプトにおいてはパイプラインという重要な役目を任されてます。
でもそうは言っても、普通に記号として「|」を使いたい、そんな夜もあるんじゃないでしょうか?
そういうときには2人でこっそり飲み会からエスケープしましょう。
具体的には、「^|」とすることでパイプライン君はあなただけの縦棒君に変身します。
…ちょっと字面が官能的過ぎるな。
同様に () のエスケープが必要になる時もあるかと思います。たぶんPowershellコマンドをFor文で使う時とかかな。
そういうときは複数の屈強な「”」で囲んで黙らせてやりましょう。こうです。”(” “)”
なんかバッチが上手く動かないわね、と思ったら変な記号が悪さをしていないかを一度確認してみることをおすすめします。
おわり
バッチファイル君の現在は?彼女は?出身校は?
調べてみましたが、いまいちわかりませんでした!!
いかがでしたか?
より効率的なバッチファイルの作り方については現在調査中です!
また新たな情報が見つかり次第、更新します!!
あぁいうしょうもないブログだとしても半年に1回も更新しないような僕のブログよりよっぽど頑張ってるよなと思う今日この頃です。
では、僕はこれから新規テキストファイル作成 ⇒ それを削除 ⇒以下ループ の機能を組み込んだ賽の河原.batを作る作業があるので失礼します。
それでは。
コメント