【Windows】バッチコマンドで繰り返し作業をサボろう！②【一括ファイルダウンロード編】

# INDEX
- 1. 前回のあらすじ
- 2. 目的
- 3. やろうず！
- 4. あとがき

# 1. 前回のあらすじ
これの続き。PCの環境設定なんかはこっちを参考にしてね。終わったら戻ってきて！
https://qiita.com/neonemo/items/3f178915bf3f9de23f9c

# 2. 目的
- 【特定URL】+【ファイル名（連番）】みたいな構成のファイルを一括ダウンロードするバッチコマンドを作成する
    - `http://hoge.com/resources/1.jpg` ～ `http://hoge.com/resources/100.jpg` があったら100ファイル分ダウンロードする
- エビデンス整理とかなんで、ファイル名は4桁のゼロ埋めを行う
    - 上と同じファイルなら `0001.jpg` ～ `0100.jpg` というファイルが出来る

# 3. やろうず！
環境設定は済んでいるはずなので、今回はバッチスクリプトの中身に注力する。

## 3.1. コマンドの仕様
使い方はこんな感じで。
ファイルは `1.jpg` から始まるとして、コマンド引数では連番最後のURLを指定する事とする。

```winbatch
> dl http://hoge.com/resources/100.jpg
```

上記の場合は、`1.jpg` ～ `100.jpg`までダウンロードする事になる。
またファイルシステム上、ファイル名は文字列（つまりASCII順）なので、ファイル名をゼロ埋めで桁を合わせる事でエクスプローラ上での利便性を確保する。

| No | LastURL                           | URLから抽出したファイル名 | 保存するファイル名 |
|----+-----------------------------------+--------------------------+-------------------|
| 1  | http://hoge.com/resources/1.jpg   | 1.jpg                    | 0001.jpg          |
| 2  | http://hoge.com/resources/2.jpg   | 2.jpg                    | 0001.jpg, 0002.jpg |
| 3  | http://hoge.com/resources/100.jpg | 100.jpg                  | 0001.jpg, 0002.jpg, …, 0100.jpg |




## 3.2. バッチコマンドを作る
### 3.2.1. スクリプト全体
面倒な人はこれをコピペで。

```winbatch
@echo off
setlocal enabledelayedexpansion

rem 引数チェック
if "%1"=="" (
  call :HELP_MSG
  exit /b 0
) else (
  set LAST_IMG_URL=%1
  for /f "usebackq tokens=1,2,3" %%a in (`powershell -c "'!LAST_IMG_URL!\' -replace '^(.+)/([0-9]+)\.([0-9a-zA-Z]+)', '$1 $2 $3 '"`) do (
    set BASE_URL=%%a
    set IMG_COUNT=%%b
    set IMG_EXT=%%c
  )
)

rem ダウンロード処理
for /l %%i in (1,1,!IMG_COUNT!) do (
  set idx=000%%i
  set DL_FILE=!BASE_URL!/%%i.!IMG_EXT!
  set NEW_FILE=!idx:~-4!.!IMG_EXT!

  set WGET_CMD=powershell -c "wget -Uri '!DL_FILE!' -OutFile '!NEW_FILE!'"
  echo WGET_CMD=!WGET_CMD!
  !WGET_CMD!
)
endlocal

exit /b 0

rem ヘルプ用メッセージ
:HELP_MSG
  echo 指定したURLから連番でファイルをダウンロードする
  echo dl [LAST_IMG_URL]
  echo.
  echo LAST_IMG_URL
  echo   その画像URLの内、連番の最後のファイルURL
  echo.
  echo ex: http://hoge.com/resources/1.jpg ～ http://hoge.com/10.jpg の10ファイルをダウンロードする
  echo > dl http://hoge.com/resources/10.jpg
  echo.
exit /b
```

### 3.2.2. 解説
ソースの意図的な奴を書く。

#### お約束
```winbatch
@echo off
```
- これを付けないとバッチ内で呼んでいるコマンドもすべて画面に出てきて残念な感じになる。動作影響はないのにほぼ必須という困った奴

#### 引数チェック
```winbatch
if "%1"=="" (
    call :HELP_MSG
    exit /b 0
) else (
    ～（略）～
```
- `%1`は起動引数が入ってくる。バッチコマンドの仕様として「引数無しは処理はしない」と自分が決めているので真の時の処理はヘルプメッセージへの疑似サブルーチン呼び出し後、バッチを終了している
    - `%1`～`%9`まで番号指定で引数が取れる。`%0`はバッチファイル名。
    - `%10`は取れないのでその場合は [shiftコマンド](https://docs.microsoft.com/ja-jp/windows-server/administration/windows-commands/shift) で引数の位置`%10`のやつを`%9`等にしてずらしながら取得すればいい
        - まあ、そんな設計するくらいなら設定ファイルを使う事を考えた方が良いんじゃないかとは思う
- ちなみに `"%1"` こんな感じにでダブルコーテーションで括るのは半角スペース混入で評価式が崩れてスクリプトが異常終了させない配慮である。バッチってSQLインジェクション的な奴の宝庫なんで。


#### 引数で貰ったURLを分解する
```winbatch
set LAST_IMG_URL=%1
for /f "usebackq tokens=1,2,3" %%a in (`powershell -c "'!LAST_IMG_URL!\' -replace '^(.+)/([0-9]+)\.([0-9a-zA-Z]+)', '$1 $2 $3 '"`) do (
  set BASE_URL=%%a // ← ここには 'https://hoge.com/resources' みたいなのが入る
  set IMG_COUNT=%%b // ← ここには '10' みたいなのが入る
  set IMG_EXT=%%c // ← ここには '.jpg' みたいなのが入る
)
```
- うわ、このバッチキモ…。じゃなくて肝。`LAST_IMG_URL`は引数で指定したURLがそのまま入っている
- 次のfor文の中で、powershellを使った正規表現を用いて以下にパラメータを格納している
    - BASE_URL : URL全体からファイル名を取り除いた残り
    - IMG_COUNT : ファイルの数。つまり連番の最後
    - IMG_EXT : ファイルの拡張子。`IMG_COUNT`を使ったループ処理と合わせてファイル名を作る為に使う
- `usebackq`を使うと下記の処理A部分のコマンド実行結果を受け取るようになる。つけない場合はテキストファイルやカウンタ定義を直接行う。
    - `for /f "usebackq" %%a in (～処理A～) do (～処理B～)`
- `tokens`を使うとスペースで区切られた文字列をバッチの変数（%%a, %%b, %%c, …）にアルファベット順に割り当てられる。ちなみに変数名の書式は特殊で`%%`+`アルファベット1文字`というものだ。
    - その性質を使って正規表現置換後の文字を`$1 $2 $3 `といった具合にスペース区切りにしている
        - ちなみに正規表現置換後の末尾にはバックスラッシュがゴミとしてついてしまうので、**あえて末尾に半角スペースを入れている**。これにより、`%%c`にバックスラッシュが混入するのを防いでいる

#### ダウンロード処理をする
```winbatch
for /l %%i in (1,1,!IMG_COUNT!) do (
  set idx=000%%i
  set DL_FILE=!BASE_URL!/%%i.!IMG_EXT!
  set NEW_FILE=!idx:~-4!.!IMG_EXT!

  set WGET_CMD=powershell -c "wget -Uri '!DL_FILE!' -OutFile '!NEW_FILE!'"
  echo WGET_CMD=!WGET_CMD!
  !WGET_CMD!
)
```
- for文は、1から順番に回るだけ。特に難しい事はないと思う
- `set idx=000%%i`とその2行後の`set NEW_FILE=!idx:~-4!.!IMG_EXT!` はゼロ埋めの肝。頭に十分な桁数の0をくっつけたら、**お尻から4桁取ったらゼロ埋め文字列の出来上がり**だぜ
    - 変数にコロンチルダ`:~`を付けると色んな変数の取り方が出来る。ここが参考になった
        - バッチファイルでファイルパスからファイル名や拡張子を自由に取り出す方法 - みちしるべ
            - https://orangeclover.hatenablog.com/entry/20101004/1286120668
- 変数が`%hoge%`ではなく`!hoge!`になっているのは[setlocalで遅延環境変数](https://docs.microsoft.com/ja-jp/windows-server/administration/windows-commands/setlocal)を使っているから。for文使いたいならほぼ必須
- `WGET_CMD`には、PowerShellで使えるwgetコマンド（正式名:Invoke-Webrequest）を組み込んでダウンロードさせている
    - ちなみに進捗代わりに実行したコマンドをechoで画面に出している

### 3.3. 動作確認をする
これは、前回の記事を参考にやってみて下し。
上に貼ったコードだとこんな感じになるはず。

```winbatch
> dl https://hoge.com/resources/10.jpg
WGET_CMD=powershell -c "wget -Uri 'http://hoge.com/resources/1.jpg' -OutFile '0001.jpg'"
WGET_CMD=powershell -c "wget -Uri 'http://hoge.com/resources/2.jpg' -OutFile '0002.jpg'"
WGET_CMD=powershell -c "wget -Uri 'http://hoge.com/resources/3.jpg' -OutFile '0003.jpg'"
WGET_CMD=powershell -c "wget -Uri 'http://hoge.com/resources/4.jpg' -OutFile '0004.jpg'"
WGET_CMD=powershell -c "wget -Uri 'http://hoge.com/resources/5.jpg' -OutFile '0005.jpg'"
WGET_CMD=powershell -c "wget -Uri 'http://hoge.com/resources/6.jpg' -OutFile '0006.jpg'"
WGET_CMD=powershell -c "wget -Uri 'http://hoge.com/resources/7.jpg' -OutFile '0007.jpg'"
WGET_CMD=powershell -c "wget -Uri 'http://hoge.com/resources/8.jpg' -OutFile '0008.jpg'"
WGET_CMD=powershell -c "wget -Uri 'http://hoge.com/resources/9.jpg' -OutFile '0009.jpg'"
WGET_CMD=powershell -c "wget -Uri 'http://hoge.com/resources/10.jpg' -OutFile '0010.jpg'"

D:\temp
> dir
 ドライブ D のボリューム ラベルは Application です
 ボリューム シリアル番号は xxxx-xxxx です

D:\temp のディレクトリ

2020/12/27  01:23    <DIR>          .
2020/12/27  01:23    <DIR>          ..
2020/12/27  01:23           100,000 0001.jpg
2020/12/27  01:23           100,000 0002.jpg
2020/12/27  01:23           100,000 0003.jpg
2020/12/27  01:23           100,000 0004.jpg
2020/12/27  01:23           100,000 0005.jpg
2020/12/27  01:23           100,000 0006.jpg
2020/12/27  01:23           100,000 0007.jpg
2020/12/27  01:23           100,000 0008.jpg
2020/12/27  01:23           100,000 0009.jpg
2020/12/27  01:23           100,000 0010.jpg
              10 個のファイル           1,000,000 バイト
               2 個のディレクトリ  1,740,894,068,736 バイトの空き領域
```

# 4. あとがき
今回はどうだっただろうか。リソースに連番を振るシステムは最近減ってると思うので、内部システム以外で連番ファイルを一括ダウンロードみたいな事は出来ないと思うけど、コマンドだけでファイルのダウンロードが出来るってのは意外と知らない人多いんじゃないかなと思った次第。
何より**いちいちブラウザで開いて、マウス右クリックして保存**みたいな発想しか出来なかった人には一石投じられたと思う。思いたい。
PowerShellを内部的に使うかどうかも迷ったけど、バッチで隠蔽しているしまあいいかなという事で大目に見て下し！

以上！