今、話題の人工知能(AI)などで人気のPython。初心者に優しいとか言われていますが、全然優しくない! という事を、つらつら、愚痴っていきます

025.新春蔵出し! 平成バッチあるある10選

»

初回:2019/01/23

00.やめられない、とまらない!?

P子「テーマをパクること?」(※1

 違います。...とも言い切れませんけど。

 まあ、『バッチ』ネタだけに、テーマを『パッチ』ワークしました。(※2

 UNIX/LINUX系は、CUIから来ているためシェルで結構いろんなことができますが、Windowsは、GUIが主体なのでバッチ機能は弱いです。なので、バッチコマンドでプログラミングするなんて考えられません。ただ、GUIでうまく実行できないことや、夜間や24時間、ずっと動いてほしい時などバッチに頼らざるを得ない場合もあります。

 そこで今回は、最小限のプログラムでちょっと便利なバッチ処理を平成最後の蔵出しあるある企画として、10選してみました。

 【目次】
 01_現在時刻の取得
 02_フォルダコピー
 03_ファイル名一覧
 04_バッチのエスケープ
 05_ブラウザ起動
 06_拡張子の一括変換
 07_最終変更時刻更新
 08_空フォルダ削除
 09_更新日付のファイル名
 10_連番リネーム

01_現在時刻の取得

 バッチで実行時の日付や時間を利用してログファイルや出力ファイル名に使いたいことがあると思います。これが結構難しいのです。というのは、日付や時刻には、"/" や ":" などの余計な記号が付くのでそのままではファイル名に使用できません。

 ①変数の部分文字列を連結します。
時刻については、0~9時の場合は、"00"ではなく、" 0"になるので、スペースを"0"に置き換える処理が必要になります。
%date:~n,m% で先頭からn文字目から、m文字分を抜き出します。
下記のサンプルでは、type nul で、空のファイルを作成しています。

@echo off 
set YMD=%date:~0,4%%date:~5,2%%date:~8,2% 
set HMS=%time: =0% 
set HMS=%HMS:~0,2%%HMS:~3,2%%HMS:~6,2% 
type nul > %YMD%%HMS%.txt

 ②変数の文字列置換を行います。
日付は、"/" を、時刻は、":" を削除します。
時刻については、スペースを"0"に置き換える処理と、6桁だけ切り出す処理が必要です。%date:/=% で日付の"/"を空文字と置き換え(つまり削除)します。

@echo off
set YMD=%date:/=%
set HMS=%time: =0%
set HMS=%HMS::=%
set HMS=%HMS:~0,6%
type nul > %YMD%%HMS%.txt

02_フォルダコピー

 フォルダのコピーなら、GUIで簡単にできます。ファイルのタイムスタンプもそのまま受け継がれます。同様の処理をバッチで行うなら、xcopy が有名でしょう。ところが、フォルダのタイムスタンプも含めたコピー方法は、robocopy というコマンドになります。

@echo off
robocopy ≪コピー元フォルダ≫ ≪コピー先フォルダ≫ /E /DCOPY:T

03_ファイル名一覧

 ファイル名の一覧が欲しい場合があります。GUIでは、エクスプローラを開き、必要なファイルを選択し、Shift+右クリックでパスのコピーというメニューが出るので、それを選択するとクリップボードにファイル名がコピーされます。日付順に並び替えたり、右上の検索カラムで条件を絞り込んだ結果でもコピーできますので便利です。右クリックでメニューを出す場合、選択ファイルの一番上で実行しないと並びが変わってしまいます。ホームにもパスのコピーメニューがありますので、そちらでも構いません。

 ここでは、where コマンドの例を挙げます。

 複数の拡張子でフォルダを再帰的に検索した結果を、ファイルに書き出します。

@echo off
where /R . *.png *.gif *.jpeg >out2.txt

 ファイルのサイズ、最終変更日および時刻も表示し、最終変更日以降でソートした結果を、クリップボードにコピーします。

@echo off
where /T /R . *.png *.gif *.jpeg |sort /+14 | clip

 さらに、正規表現でフォルダを絞り込みます。

@echo off
where /T /R . *.png *.gif *.jpeg | findstr "src.*\jsp" |sort /+14 | clip

04_バッチのエスケープ

 先の whereコマンドで、/T で、サイズ、最終変更日および時刻を表示できますが固定長で、並び順も変えられません。そこで、デリミタ(スペース)で分割してトークンに割り振ります。
その時、1行が長くなるので折り返したいのと、for /f in () 構文で、inの中で実行するコマンドにフィルターを入れる場合、エスケープが必要です。
改行を入れる場合の行末や、パイプのエスケープ文字は、"^" になります。

@echo off
type nul > out.txt
for /f "tokens=1,2,3,4* delims= " %%A ^
in ('where /T /R . *.png *.gif *.jpeg ^| findstr "src.*\jsp"') do (
echo %%B %%C %%A %%D >> out.txt
)

05_ブラウザ起動

 バッチファイルから別のプログラムを呼び出す場合、startcall があります。call は、同じプロセスで開始され、前の処理が終了すると、続いて次の処理が実行されます。start は、新しいプロセスとして開始され、並列実行されます。
ここでは、Internet ExplorerやFirefoxを、起動するバッチのサンプルです。複数のページを一気に立ち上げておきたい場合に便利です。

@echo off
set IE="C:\Program Files\Internet Explorer\IEXPLORE.EXE"
set FF="C:\Program Files (x86)\Mozilla Firefox/firefox"
set URL=http://el.jibun.atmarkit.co.jp/pythonlove/
start /B cmd.exe /C %%FF%% %%URL%%
start /B cmd.exe /C %%IE%% %%URL%%

06_拡張子の一括変換

 拡張子を一気に変更したい場合、GUIではうまい手立てがありません。
rename コマンドは、ファイル名の部分一致などの機能があり便利ですが、フォルダ間の移動はできません。あくまで同一フォルダ内での処理のみなので、フォルダ階層内のすべてのファイルを処理する場合は、for /D /R で再帰的にフォルダを検索し、cd コマンドで処理対象フォルダにチェンジしてから、rename を実行します。

@echo off
for /D /R %%A in (*) do (
cd %%A
rename *.txt *.jsp
)

07_最終変更時刻更新

 すべてのファイルの更新時刻を現在時刻に変更したい場合があります。通常は、PowerShell で行うサンプルが多いのですが、copy コマンドの特殊な使い方で更新可能です。
(参照:http://www.atmarkit.co.jp/ait/articles/1809/13/news031.html
条件として、同一フォルダ内での更新で、任意の時間ではなく、現在の時刻に更新するのみです。
copy コマンドで、"+" 記号でファイルを「結合」できます。結合されたファイルの最終変更時刻は、実行時の時間となることを利用します。結合されるファイルを自身に、結合するファイルを、空ファイルにします。

@echo off
for /D /R %%A in (*) do (
cd %%A
for %%B in (*) do (
copy /b %%B+
)
)

08_空フォルダ削除

 色々な条件で、空のフォルダが残る場合があります。これを手軽に削除するには、とりあえず rmdir してみることです。(ちょっと危険な香りが...)
ファイルが既に存在している場合は削除できません。このバッチでは、空フォルダを削除した結果、上位のフォルダが空フォルダになった場合は、それも削除しなければいけないので、何回か繰り返し実行する必要があります。
このサンプルでは、最大10回繰り返すのですが、ファイル数をカウントして、個数が減らない場合に終了するという処理を入れています。

 バッチでは、変数はその変数がある行に入った瞬間にすべて展開されるため、if や for など、カッコで囲った中身も1行として一気に展開されるため、処理が反映されません。
この、変数を読み込むタイミングを遅らせる変数の事を、遅延環境変数といい、変数を囲む%記号を!記号に変える必要があり、しかも、この変数を利用するには、setlocal enabledelayedexpansion 宣言が必要です。

(参照:https://qiita.com/sawa_tsuka/items/c7c477cacf8c97792e17

@echo off
setlocal enabledelayedexpansion
set old=0
for /l %%n in (1,1,10) do (
set cnt=0
for /D /R %%A in (*) do (
rmdir /Q %%A 2>nul
set /a cnt=!cnt!+1
)
if !cnt! == !old! ( goto END )
set old=!cnt!
)
:END
endlocal

09_更新日付のファイル名

 今までの処理を総合すると、ファイル名をファイルの更新日付にリネームする方法もわかると思います。
 問題は、ファイルの更新日付は秒まで取得できないのと、同一時間に作成されたファイルなら、ファイル名が重複するという事です。秒の単位は、cnt=100 で、変更予定のファイルが既に存在していれば、cntを+1 していきます。100 にしているのは、2文字目以降を追加することで、"00" を作成しています。

@echo off
setlocal enabledelayedexpansion
for /D /R %%A in (*) do (
cd %%A
set cnt=100
for /f "delims=;" %%B in ('dir /b /od *.png *.gif *.jpeg') do (
set UPD=%%~tB
set UPD=!UPD: =0!
set FNM=!UPD:~0,4!!UPD:~5,2!!UPD:~8,2!!UPD:~11,2!!UPD:~14,2!
set PLS=!cnt:~1,2!%%~xB
if exist !FNM!!PLS! (
set /a cnt=!cnt!+1
) ELSE (
set cnt=100
)
set PLS=!cnt:~1,2!%%~xB
rename %%B !FNM!!PLS!
)
)
endlocal

10_連番リネーム

 エクスプローラで、時間順に並べた複数のファイルを選択し、先頭のファイルの名前を変更します。すると、選択したファイル名に、(1)~の連番が振られます。この、()付きの連番では、ちょっと不便な場合があります。そこで、純粋な連番にリネームする方法です。
 サンプルでは、for /D /R で、再帰的にフォルダを検索しています。

(参照:http://xoxopigs.com/cmdline-numbering

@echo off
setlocal enabledelayedexpansion
for /D /R %%A in (*) do (
cd %%A
set cnt=10000
for /f "delims=;" %%B in ('dir /b /od *.png *.gif *.jpeg') do (
set /a cnt=!cnt!+1
rename %%B !cnt:~1,4!%%~xB
)
)
endlocal

☆ 最後に

 バッチでプログラムを組むのは時間がかかりますし、良いことはほとんどありません。正直、多少手間がかかっても、(私は)Javaで組む方が精神的なストレスがかからない分楽です。

 Javaの代わりに、Python でサクサクっと作れるようになれれば、作業効率は向上すると思いますが、正直、Python もよくわからない言語なので、どうするのが良いのかイマイチ決めかねています。

P子「Pythonで組んでもらえれば、私の出番も増えるかしら?」

 そう言ってもらえるなら、ちょっと増やしていきましょうか?

ほな、さいなら

======= <<注釈>>=======

※1 P子「テーマをパクること?」
 P子とは、私があこがれているツンデレPythonの仮想女性の心の声です。

※2 テーマをパッチワーク
 パッチとは、継ぎ接ぎ(つぎはぎ)の意味

Comment(3)

コメント

湯二

パクリ可!

仕事でバッチファイルは結構使うので使用させていただきます。

匿名

バッチファイルは簡単なものならいいけど、数十行になると苦痛

ちゃとらん

> 数十行になると苦痛

御意!

ある手順を繰り返し処理してくれる別の何かがあれば、良いのですが…

コメントを投稿する