プログラムのステップ数をカウントするバッチファイルをささっと作る

# 0. はじめに

ある日，チームリーダーから過去のプロジェクトの開発規模を知りたいので，ソースコードのステップ数を調べてくれという依頼が来た。問題はその依頼を受けた同僚で，なんとファイルを一つずつテキストエディタで開いて行数を数えようとしたので慌てて止めさせた。せめてファイルが全部でいくつあるのか数えてからやろうね。

で「30分待ってくれ」とお願いして急いで作ったのが今回のバッチファイルである。

# 1. 仕様案（お品書き）

ステップ数をカウントするフリーソフトなんて，その辺に転がっていそうな気がするが，情シス部門を通すヒマがないので自作する。
- 誰でも作れる（使える）ようにコマンドプロンプトから起動するバッチファイルとする。
- 複数のファイル名を指定でき，ワイルドカードも使うことが出来る。
- サブディレクトリ以下を再帰的に検索することができる。
- 単純な行数をカウントするものとする。空行やコメント行もカウントする。
- 最後にステップ数の合計を表示する。

# 2. 基本技術

### 行数のカウント方法

テキストファイルの行数をカウントするためには `find` コマンドを使う方法が知られている。詳しくは参考文献を参照されたい。

問題は最終行が改行のみの場合，一行だけ少なくカウントされてしまうことや，出力形式が無意味に複雑（謎のハイフンとファイル名と行数）なので，この出力をパースするのが少し面倒である。パイプで `find` コマンドに引き渡してやれば両者の問題を解決できる。

```:find コマンドの実行例
c:\qiita>find /c /v "" sample.txt

---------- SAMPLE.TXT: 100

c:\qiita>find /c /v "" < sample.txt
100

c:\qiita>type sample.txt | find /c /v ""
101
```

ちなみに `sapmle.txt` は下記のような内容である。最後の101行目は改行のみとなっている。

```plantuml
<style>
root {
  margin 0
  padding 0
}
title {
  roundCorner 16
  padding 0 16 16 16
  horizontalAlignment left
  backgroundColor #202020
  fontColor #e3e3e3
  fontStyle normal
  fontSize 14
  fontName monospaced
}
</style>
sprite TITLE <svg width="640" height="28">
<path d="M0 0 v28 a4 4 0 0 0 4 4 h106 a4 4 0 0 0 4 -4 v-28" fill="#606060">
<text x="8" y="24" font-size="16" fill="white">SAMPLE.TXT</text>
</svg>
title
<$TITLE>

<color:cyan>000001│</color>天智天皇<color:yellow>････</color>秋の田のかりほの庵の苫を荒みわがころも手は露に濡れつつ<color:yellow>↲</color>
<color:cyan>000002│</color>持統天皇<color:yellow>････</color>春すぎて夏来にけらし白たへのころもほすてふあまの香具山<color:yellow>↲</color>
<color:cyan>000003│</color>柿本人麻呂<color:yellow>･･</color>あしびきの山鳥の尾のしだり尾のながながし夜をひとりかも寝む<color:yellow>↲</color>

～中略～

<color:cyan>000098│</color>従二位家隆<color:yellow>･･</color>風そよぐならの小川の夕暮はみそぎぞ夏のしるしなりける<color:yellow>↲</color>
<color:cyan>000099│</color>後鳥羽院<color:yellow>････</color>人も惜し人も恨めしあぢきなく世を思ふゆゑにもの思ふ身は<color:yellow>↲</color>
<color:cyan>000100│</color>順徳院<color:yellow>･･････</color>ももしきや古き軒ばの忍ぶにもなほあまりある昔なりけり<color:yellow>↲</color>
<color:cyan>000101│</color><color:yellow>↲</color>
end title
```

### サブディレクトリ以下を再帰的に検索する方法 

`for /R` コマンドを使う方法もあるが，`dir` コマンドの出力を使ったほうが速いことが知られている。詳しくは筆者の[別記事](https://qiita.com/TETSURO1999/items/4d415dff63ac856cd525)を参照のこと。

```
for /F "usebackq tokens=*" %%I in ( `dir /s /b /a-d 検索パターン 2^>NUL` ) do (
  for /F "usebackq" %%J in ( `type "%%~I" ^| find /c /v ""` ) do set LINE=%%J
  echo %%~I	!LINE!
)
```

# 3. 実装コード

実装コードを以下に示す。なお，ファイルのサイズ（バイト単位）も出力し，最後に合計も出力するようにした。

```:istep.cmd
@echo off
rem ----------------------------------------------------------------------------
rem テキストファイルの行数をカウントします
rem ----------------------------------------------------------------------------
setlocal enabledelayedexpansion
set ROPT=0
rem ------------------------------------------------------------------------
rem オプション解析
rem ------------------------------------------------------------------------
set ARGS=
if "%~1"=="" goto USAGE
:LOOP
  set OPT=%~1
  if /I "%OPT%"=="/R" set ROPT=1&&goto ENDIF
  if "%OPT:~0,1%"=="/" goto ERROR
  if defined ARGS (
    set ARGS=%ARGS% "%~1"
  ) else (
    set ARGS="%~1"
  )
  :ENDIF
  shift
  if "%~1"=="" goto BREAK
goto LOOP
:ERROR
echo オプション %OPT% には対応していません！
exit /b
:BREAK
if not defined ARGS (
  echo ファイル名を指定して下さい！
  exit /b
)
rem ------------------------------------------------------------------------
rem ステップ数／ファイルサイズのカウント
rem ------------------------------------------------------------------------
set DIROPT=/b /a-d
if "%ROPT%"=="1" (
  set DIROPT=/s %DIROPT%
)
set ALL_LINE=0
set ALL_SIZE=0
echo ファイル名	行数	サイズ
for /F "usebackq tokens=*" %%I in ( `dir %DIROPT% %ARGS% 2^>NUL` ) do (
  set SIZE=%%~zI
  for /F "usebackq" %%J in ( `type "%%~I" ^| find /c /v ""` ) do set LINE=%%J
  echo %%~I	!LINE!	!SIZE!
  set /a ALL_LINE=ALL_LINE+LINE
  set /a ALL_SIZE=ALL_SIZE+SIZE
)
echo 合計	%ALL_LINE%	%ALL_SIZE%
exit /b
:USAGE
rem ----------------------------------------------------------------------------
rem ヘルプメッセージ
rem ----------------------------------------------------------------------------
echo テキストファイルの行数をカウントします。
echo.
echo iSTEP(.CMD) [/R] [ドライブ:][パス][ファイル名] ...
echo.
echo /R  ファイルを再帰的に検索します。
exit /b
```

# 4. 実行例

引数なしで実行するとヘルプメッセージを表示する。

```:引数なしで実行した場合
c:\qiita>istep
テキストファイルの行数をカウントします。

iSTEP(.CMD) [/R] [ドライブ:][パス][ファイル名] ...

/R  ファイルを再帰的に検索します。
```

ファイル名の指定にはワイルドカードが使える。ファイル名と行数，サイズ間の区切り文字はタブである。

```:実行例
c:\qiita>istep *.cmd
ファイル名	行数	サイズ
HCASE.CMD	141	4169
HCASE2.CMD	157	4597
LCASE.CMD	82	2500
LCASE2.CMD	94	2817
UCASE.CMD	82	2506
UCASE2.CMD	94	2817
UCASE3.CMD	126	3654
ZCASE.CMD	165	4832
ZCASE2.CMD	180	5254
合計	1121	33146
```

# 5. おまけ

サンプルとして業務用のソースコードを掲載する訳にはいかないので，誰でも知っている題材として百人一首を例題にした。99首目の後鳥羽院とは後鳥羽上皇のことで「鎌倉殿の13人」での尾上松也氏の好演が思い出される。悲壮感漂う内容からして隠岐の島への流罪後に歌ったものかと思いきや，承久の乱の前とは意外だった。


# 6. 参考文献

本記事で採用した行数カウント方法は参考文献[8]による。

0. [Windowsコマンドでファイル行数を数える - Qiita](https://qiita.com/torentchan/items/34b54c9ce6a8627668a8)
0. [windowsでファイルの行数を取得する方法 - Qiita](https://qiita.com/YamasakiKenta/items/239b025e3a574bd352aa)
0. [行数カウントしてって頼まれてサボった話 - Qiita](https://qiita.com/_shun/items/c39449bbea2cab1092d3)
0. [ファイルの行数をカウントして変数に入れるワンライナーbat - Qiita](https://qiita.com/knktknk/items/926720236389bf809460)
0. [Windows標準機能(cmd)で、フォルダ内の各テキストファイル・CSVファイルの行数カウントする。 - Qiita](https://qiita.com/sakaki_developer/items/d71c277a8ab916ece4a7)
0. [wc コマンド for windows - Qiita](https://qiita.com/asterisk9101/items/098c95e5329b2f19b09b)
0. [PowerShellステップカウンタ - Qiita](https://qiita.com/Kosen-amai/items/382b18e8352d2343d3f3)
0. [【bat】Windowsのバッチファイルネタ帳 - Qiita](https://qiita.com/piromaru/items/9ee33aba8e5a5adfa1f4)
0. [後鳥羽天皇 - wikipedia](https://ja.wikipedia.org/wiki/後鳥羽天皇)
0. [ちょっと差がつく『百人一首講座』- 小倉山荘](https://ogurasansou.jp.net/columns/hyakunin/2017/10/17/1411/)
0. [一日一首 - 全日本かるた協会](https://www.karuta.or.jp/karuta-everyday/3071/)