あ〜！！ 現在のフォルダ以下にある全ての PNG/GIF 画像のファイル名・拡張子・相対フォルダ名を再帰的に取得して好きな文字列と結合したソート済みテキストを cmd.​exe からワンライナーでクリップボードに直接コピーしたいなぁ〜！​！​！

# え！？ 現在のフォルダ以下にある全ての PNG/GIF 画像のファイル名・拡張子・相対フォルダ名を再帰的に取得して好きな文字列と結合したソート済みテキストを cmd.​exe からワンライナーでクリップボードに直接コピー！&#x200B;？&#x200B;！&#x200B;？

### 出来らあっ！

## フォルダ構成

```txt
C:\current\  ← 現在の作業フォルダ
┣ sub\
┃ ┣ more\
┃ ┃ ┗ d.png
┃ ┗ a.png
┣ b.gif
┣ c.png
┣ not_image.txt
┗ z.gif
```

- `*.png` および `*.gif` のファイルだけを対象とする。
    - フォルダそのものや他の拡張子のファイルは対象外。
- ファイルのパスか何かから以下の情報を切り出して、いいかんじのテキストにしたい。
    - ファイル名（例：`a`）
    - 拡張子（例：`.png`）
    - 相対フォルダ名（例：`sub\`）

## ワンライナー

```bat
cmd /Q /V:ON /C "(for /R %I in (*.png *.gif) do ((set here=%~dpI) & (set rel=!here:%CD%=!) & (echo ファイル名「%~nI」、拡張子「%~xI」 … 相対フォルダ「!rel:~1!」)))" | sort | clip
```

できた。

## 結果（クリップボード）

```txt
ファイル名「a」、拡張子「.png」 … 相対フォルダ「sub\」
ファイル名「b」、拡張子「.gif」 … 相対フォルダ「」
ファイル名「c」、拡張子「.png」 … 相対フォルダ「」
ファイル名「d」、拡張子「.png」 … 相対フォルダ「sub\more\」
ファイル名「z」、拡張子「.gif」 … 相対フォルダ「」

```

全行の末尾に改行文字があるので、最後は空行。


# ということで解説

せっかくなので徹底的に分解していきたい。
とりあえず複数行に戻してみよう。

## 書き下し文

```bat
cmd /Q /V:ON /C "
(
  for /R %I in (*.png *.gif) do (
    (
      set here=%~dpI
    ) & (
      set rel=!here:%CD%=!
    ) & (
      echo ファイル名「%~nI」、拡張子「%~xI」 … 相対フォルダ「!rel:~1!」
    )
  )
)
" | sort | clip
```

うーん、シンタックスハイライトが仕事できない…。
大部分が文字列として渡されてるから仕方無し。

では、個別の解説（という名の備忘録）へ。

**※** 以下、特にパスの取得については全て **`C:\current\sub\a.png`** を例として解説する。
例には必ずこの注釈[^eg-a]を付けておくのでわかると思う。
あと、変数は *`%var%`* みたいに斜体で示す。何となく。

[^eg-a]: 元は `C:\current\sub\a.png`


## `cmd /Q /V:ON /C "なんやかんや"`

`なんやかんや` の処理を cmd.exe の別インスタンスで実行する。

- `/Q`：**実行されるコマンド行自身を出力しない**。"<i>**Q**uiet</i>" 。
    - バッチファイルを書く時には `@echo off` でお馴染みのやつ。大事。  
    - これを指定しないと、出力テキストに `C:\current>((set here=C:\current\sub\ )  & (set rel=!here:C:\current=! )  & (echo ファイル名「a」、拡張子「.png」 … 相対フォルダ「!rel:~1!」 ) ) ​`[^eg-a] みたいなコマンド行自身も含まれてしまってえらいことになるので、黙っててもらう。
        - なお、今回は `do (〜)` の箇所を `do @(〜)` としても同じ結果になる。
- `/V:ON`：**遅延環境変数の展開**を有効化する。"<i>**V**ariable</i>" 。
    - バッチファイルでは `setlocal enabledelayedexpansion` でお馴染みのやつ。ちょー大事。
    - cmd.exe は、デフォルトでは変数の「展開」のタイミングが各処理のそれと並行しないことがある。特にワンライナーでは、コマンド同士を `&` 等で繋ぐ都合のせいか、その制限が更に厳しい。  
      この仕様についてはググれば怨嗟[^ensa]の声がいくらでも出てくるが、とにかくこの遅延なんちゃらを有効化すると、*`%var%`* の他に *`!var!`* という記法の変数展開が使えるようになる。  
        - なお、*`%TMP%`* みたいな既に値を持っている環境変数とかは普通に使える。
    - 今回、処理全体を `cmd` でわざわざ囲んでいる大きな理由はこれ。  
      **忌々しい。**←怨嗟の声
- `/C "なんやかんや"`：`なんやかんや` を実行して停止する。"<i>**C**arry out</i>" 。
    - 停止しない `/K` というのもあるが、今回は停止してくれないと次の `sort` や `clip` にデータが渡らないので、大人しく死んでもらう。

[^ensa]: これ「えんさ」って読むんだ……今までずっと「おんさ」だと………合唱部かよ…………。


## 書き下し文（`cmd` 内）

`cmd` に文字列として渡された実行内容を改めて書き下すと、この通り。

```bat
(
  for /R %I in (*.png *.gif) do (
    (
      set here=%~dpI
    ) & (
      set rel=!here:%CD%=!
    ) & (
      echo ファイル名「%~nI」、拡張子「%~xI」 … 相対フォルダ「!rel:~1!」
    )
  )
)
```

ありがとうシンタックスハイライト。


## `for /R %I in (*.png *.gif) do なんか`

`in (*.png *.gif)` を1件ずつ読み込んで *`%I`* に代入し、`do なんか` 内で好きなコマンドとして実行する。

-  `/R`：`in ()` としてファイルセットを受け付け、条件に該当するファイルを再帰的に検索する。"<i>**R**ecursive</i>" 。
- *`%I`*：見つかったファイルのフルパス（たぶん）が代入される変数。
    - 変数名には1文字しか使えないが、別に *`%I`* じゃなくても良い。  
      仕様が妙に柔軟で、*`%_`* とか *`%1`* とかでもいける。
    - `for` の変数参照は、上述した遅延なんちゃらを利用しなくても普通に使える。*`!I`* などとしてはいけない。
    - **※** 変数 *`%I`* をコマンド直打ちでなくバッチファイルの中に記述する場合は、パーセント記号を重ねて ***`%%I`*** などと指定すること。
- `in (*.png *.gif)`：ファイルセット。要はワイルドカードで指定するファイル名。
    - 今回のような複数条件は、単に半角スペース区切りで並べれば良い。
- `do なんか`：該当ファイルが見つかる度に `なんか` を実行する。
    - 実行内容では変数 *`%I`* を利用でき、更にバッチファイルにおける *`%~dp0`* 等でお馴染みの**引数置換構文**をこの変数に適用できる。使う場合は恐らく `~` が必須。  
      今回も3種類ぐらい使うが、詳細は追い追い。
    - **※** 変数 *`%I`* をコマンド直打ちでなくバッチファイルの中に記述する場合は、**パーセント記号を重ねて *`%%I`* などと指定**すること。ダブルチェックヨシ！


## `(set here=%~dpI)`

各ファイルがあるフォルダのフルパスを *`here`* に代入する。

- `(` 〜 `)`：コマンドを**そこで区切ってひとまとめにする**。
    - この重要性の話は余談にて。
- `set なまえ=あたい`：変数 *`なまえ`* を宣言し、`あたい` を代入する。
- *`here`*：宣言した変数。後で使う。
    - 宣言時は、変数名に `%` とか `!` とかを付けてはいけない。
- *`%~dpI`*：引数置換構文。ファイル *`%I`* が置かれているフォルダまでのフルパス `C:\current\sub\`[^eg-a] 。
    - `~`：文字列がダブルクォーテーションで囲まれている場合にそれを外す。
    - `d`：ファイルがあるドライブ文字 `C:`[^eg-a] 。
    - `p`：ファイルがあるディレクトリのパス名 `\current\sub\`[^eg-a] 。
        - 末尾にフォルダ区切り文字 `\` を含む。
    - **※** 変数 *`%I`* をコマンド直打ちでなく**バッチファイルの中に記述**する場合は、**パーセント記号を重ねて *`%%I`* などと指定すること**。トリプルチェックヨシ！&#x200B;！&#x200B;！

## `& (set rel=!here:%CD%=!)`

動的環境変数 *`%CD%`* を利用して *`!here!`* から相対パス名を取得し、変数 *`rel`* に代入する。

- `&`：1行に複数のコマンドを記述する。**ワンライナーの最高の友達**。
- `(` 〜 `)`、`set なまえ=あたい`：上述。
- *`rel`*：宣言した変数。後で使う。
- *`!here:%CD%=!`*：これ、書いてみたら通っちゃって**「え、いいの！？」**となったようなやつなので、どう説明すべきか…。
    - *`!here!`*：さっき宣言した変数。ファイルのあるフォルダまでのフルパス `C:\current\sub\`[^eg-a] を持つ。
        - 使う時にはこのように `!` や `%` で囲う。
    - *`%CD%`*：**作業フォルダのフルパス `C:\current`** を勝手に持っている動的環境変数。
        - 末尾にフォルダ区切り文字 `\` を***含まない***。
    - *`%へんすう:ア=イ%`*：変数 *`%へんすう%`* が持つ文字列中の `ア` を全て `イ` に置換した文字列を返す記法。
        - 例えば `echo %CD:c=Z%` というコマンドは `z:\zurrent` を出力する。大文字・小文字は無視するっぽい。  
          あるいは `echo %CD:t=%` とすれば、空文字への置換（＝文字列の削除）が行われて、`:\urrent` を出力する。

ということで、まぁ最後のやつは、
「*`!here!`* の中の *`%CD%`* と一致する箇所を空文字列に置換する」
＝「`C:\current\sub\`[^eg-a] から `C:\current` を削除する」
＝「相対フォルダ名 `\sub\`[^eg-a] を取得する」
という処理になる。

…なるが、cmd.exe においてそんな風に**変数を変数で置換**するのは、本来ならもっと困難を伴う。

```C&#x3A;\current\本来の困難.bat
@echo off
set var=c

echo 元  : %CD%
echo 失敗: %CD:%var%=z%
echo 失敗: %%CD:%var%=z%%
call echo 成功: %%CD:%var%=z%%
pause
```

```txt:C&#x3A;\Windows\system32\cmd.exe
元  : C:\current
失敗: var
失敗: %CD:c=z%
成功: z:\zurrent
続行するには何かキーを押してください . . .
```

具体的には `call` と二重の `%` が要る。なんだそれ。

今回そういう困難とご挨拶せずに済んだのは、遅延なんちゃらである *`!here!`* とそうではない *`%CD%`* との性質の違い、もっと言えば変数としての展開タイミングの違いによるものだろう。たぶん。


## `& (echo ファイル名「%~nI」、拡張子「%~xI」 … 相対フォルダ「!rel:~1!」)`

今まで準備してきた変数を用いて、望むテキストを標準出力する。

- `&`、`(` 〜 `)`：上述。
- `echo`：メッセージを標準出力する。
    - 最終的に得られるテキストを直接生み出すのはこいつ。  
      そういう意味では最重要コマンドだろう。
- *`%~nI`*：引数置換構文。ファイル *`%I`* の拡張子抜きファイル名 `a`[^eg-a] 。
    - **※ 変数 *`%I`* をコマンド直打ちでなくバッチファイルの中に記述する場合は、パーセント記号を重ねて *`%%I`* などと指定すること。**クアドラプルチェックヨシ！&#x200B;！&#x200B;！&#x200B;！
- *`%~xI`*：引数置換構文。ファイル *`%I`* の拡張子 `.png`[^eg-a] 。
    - ***※ 変 数 &nbsp;`%I`&nbsp; を コ マ ン ド 直 打 ち で な く バ ッ チ フ ァ イ ル の 中 に 記 述 す る 場 合 は 、 パ ー セ ン ト 記 号 を 重 ね て &nbsp;`%%I`&nbsp; な ど と 指 定 す る こ と 。 ク イ ン タ プ ル チ ェ ッ ク で 今 日 も 一 日 ご 安 全 に ！ ！ ！ ！ ！ ！ ！ ！ ！ ！***
- *`!rel:~1!`*：これはまぁ、さっきの置換記法よりは難しくない。
    - *`!rel!`*：さっき宣言した変数。ファイルのある相対フォルダ名 `\sub\`[^eg-a] を持つ。
    - `%へんすう:~N%`：先頭を0文字目として、変数 *`%へんすう%`* が持つ文字列の `N` 文字目以降を切り出して返す記法。
        - 例えば `echo %CD:~1%` というコマンドは `:\current` を出力する。

ということで、最後のやつは
「相対フォルダ名 `\sub\`[^eg-a] の先頭1文字を削除した文字列 `sub\`[^eg-a]」
を表す。

先頭をなぜ消すかって、これをやらないとファイルが作業フォルダ直下にある場合に相対フォルダ名が `\` になるのがキモいので…。
そんなら *`!rel!`* の宣言代入時に消しとけよって感じになるが、実はそこには大いなる罠が待ち受けている。

```bat:空文字列の代入.bat
@echo off
echo 通常の変数の場合
set s=ABC
set s1=%s:~1%
set s99=%s:~99%
REM ↑たった3文字しか無い元文字列の、先頭99文字を削除。
REM こういうオーバーキルをした場合は空文字列が返る。
echo s「%s%」、s1「%s1%」、s99「%s99%」
echo 遅延環境変数の場合
cmd /Q /V:ON /C "(set s=ABC) & (set s1=!s:~1!) & (set s99=!s:~99!) & (echo s「!s!」、s1「!s1!」、s99「!s99!」)"
pause
```

```txt:C&#x3A;\Windows\system32\cmd.exe
通常の変数の場合
s「ABC」、s1「BC」、s99「」
遅延環境変数の場合
s「ABC」、s1「BC」、s99「!s99!」
続行するには何かキーを押してください . . .
```

**遅延環境変数の展開においては、変数に空文字列が直接代入されているとなんか上手くいかん**らしい。いかんならしゃーない。
これを防ぐために、変数 *`!rel!`* にはあえて無駄な1つの頭文字を持たせておいて、いざ表示という段においてのみそれを必ず1文字削る、という手順を経ている。

もし本当に要らないなら、宣言と代入の時点で `set rel=!here:%CD%\=!`[^relHead] とすれば容易に消しておけるのだ。

[^relHead]: 元は `set rel=!here:%CD%=!`


## もう一息

```bat
cmd /Q /V:ON /C "なんやかんや" | sort | clip
```

`(` `)` が生い茂り `!` `%` が降り注ぐ、`"なんやかんや"` の縄張りを今抜けた。
ゴールは目と鼻の先だ。


## `あれやこれや | sort`

`あれやこれや` の処理が一通り済んだら、その内容をソートして標準出力する。

- `あれやこれや`：行う処理。
- `|`：標準出力を次のコマンドにパイプする（渡す）。
- `sort`：受けたテキストの行を辞書順に並べ替えて標準出力する。

どうってことはない。


## `てんやわんや | clip`

`てんやわんや` の処理が一通り済んだら、その内容をクリップボードにコピーする。

- `てんやわんや`：行う処理。
- `|`：上述。
- `clip`：受けたテキストをクリップボードにコピーする。

どうってことなさすぎる。

&#x200B;
てなわけで**完了！&#x200B;！&#x200B;！&#x200B;！&#x200B;！&#x200B;！&#x200B;！**


## まとめ

```txt:フォルダ構成
C:\current\  ← 現在の作業フォルダ
┣ sub\
┃ ┣ more\
┃ ┃ ┗ d.png
┃ ┗ a.png
┣ b.gif
┣ c.png
┣ not_image.txt
┗ z.gif
```

```txt:結果（クリップボード）
ファイル名「a」、拡張子「.png」 … 相対フォルダ「sub\」
ファイル名「b」、拡張子「.gif」 … 相対フォルダ「」
ファイル名「c」、拡張子「.png」 … 相対フォルダ「」
ファイル名「d」、拡張子「.png」 … 相対フォルダ「sub\more\」
ファイル名「z」、拡張子「.gif」 … 相対フォルダ「」

```

```bat:ワンライナー
cmd /Q /V:ON /C "(for /R %I in (*.png *.gif) do ((set here=%~dpI) & (set rel=!here:%CD%=!) & (echo ファイル名「%~nI」、拡張子「%~xI」 … 相対フォルダ「!rel:~1!」)))" | sort | clip
```

```bat:書き下し文
cmd /Q /V:ON /C "
(
  for /R %I in (*.png *.gif) do (
    (
      set here=%~dpI
    ) & (
      set rel=!here:%CD%=!
    ) & (
      echo ファイル名「%~nI」、拡張子「%~xI」 … 相対フォルダ「!rel:~1!」
    )
  )
)
" | sort | clip
```

めでたし。


# 余談

以下余談。


## 心の声（パスに半角スペースや `%` や `!` が交ざってても大丈夫なの…？）

```txt:フォルダ構成
C:\cu !r!r%e% nt\  ← 現在の作業フォルダ
┣ %CD% !CD!\
┃ ┣ !rel! %rel%\
┃ ┃ ┗ dracula %Is !here!.png
┃ ┗ a縺薙ｓ縺ｫ縺｡縺ｯ.png
┣ b.gif                                   .exe
┣ c ,.'`+-=^~_()[]{};!#$%&@.png
┣ not_image.txt
┗ z%% o%m%b%%Ie.gif
```

**いっけなーい🔪 殺意殺意💦
ある日突然、平和なディレクトリに悪意の塊が攻めてきてもう大変💦
一体私達、これからどうなっちゃうの〜😭😭！？**

```txt:結果（クリップボード）
ファイル名「a縺薙ｓ縺ｫ縺｡縺ｯ」、拡張子「.png」 … 相対フォルダ「%CD% \」
ファイル名「c ,.'`+-=~_()[]{};!#$%&@」、拡張子「.png」 … 相対フォルダ「」
ファイル名「dracula %Is C:\cu !r!r%e% nt\%CD% C:\cu !r!r%e% nt\\%CD% \ %rel%\」、拡張子「.png」 … 相対フォルダ「%CD% \\%CD% \ %rel%\」
ファイル名「z%% o%m%b%%Ie」、拡張子「.gif」 … 相対フォルダ「」

```

答：ダメ。

### 大丈夫な例

- `a縺薙ｓ縺ｫ縺｡縺ｯ`
    - aのファイル名
- `c ,.'``+-=~_()[]{};!#$%&@`
    - cのファイル名
- `z%% o%m%b%%Ie`
    - zのファイル名
- `.png`
    - a、c、dの拡張子
- `.gif`
    - zの拡張子

※ bは `.exe` 面に堕ちたのでヒットしない。

### ダメな例

- `%CD% !CD!\` → `%CD% \`
    - aの相対フォルダ名
- `dracula %Is !here!.png` → `dracula %Is C:\cu !r!r%e% nt\%CD% C:\cu !r!r%e% nt\\%CD% \ %rel%\`
    - dのファイル名
- `%CD% !CD!\!rel! %rel%\` → `%CD% \\%CD% \ %rel%\`
    - dの相対フォルダ名

半角スペースとか単なる変な記号とか `%` の変数名とかはやりすごせても、`!` の方の変数名（しかも実在するやつ）を突かれちゃうと無理な模様。
バッチファイルだったら `setlocal` の範囲を最低限にすることで多少はコントロールできるらしいんだけど、ワンライナーだと多分、さすがにどうしようもないかな……。


## 心の声（ルート直下にあるファイルについては `… 相対フォルダ「」` なんて表示自体要らないのでは…？）

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">フリー素材です <a href="https://t.co/nW2NmVYLPa">pic.twitter.com/nW2NmVYLPa</a></p>&mdash; 森の子リスのミーコの大冒険 (@Phroneris) <a href="https://twitter.com/Phroneris/status/1070671353559171072?ref_src=twsrc%5Etfw">December 6, 2018</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

```bat:ワンライナー
cmd /Q /V:ON /C "(for /R %I in (*.png *.gif) do ((set here=%~dpI) & (if !here!==%CD%\ (set rel=\) else (set rel=\ … 相対フォルダ「!here:%CD%\=!」)) & (echo ファイル名「%~nI」、拡張子「%~xI」!rel:~1!)))" | sort | clip
```

```bat:書き下し文
cmd /Q /V:ON /C "
(
  for /R %I in (*.png *.gif) do (
    (
      set here=%~dpI
    ) & (
      if !here!==%CD%\ (set rel=\) else (set rel=\ … 相対フォルダ「!here:%CD%\=!」)
    ) & (
      echo ファイル名「%~nI」、拡張子「%~xI」!rel:~1!
    )
  )
)
" | sort | clip
```

```txt:結果
ファイル名「a」、拡張子「.png」 … 相対フォルダ「sub\」
ファイル名「b」、拡張子「.gif」
ファイル名「c」、拡張子「.png」
ファイル名「d」、拡張子「.png」 … 相対フォルダ「sub\more\」
ファイル名「z」、拡張子「.gif」

```

`if` 文最高！


## 心の声（`cmd "なんやかんや"` の中に更なる `"` があったら…？）

**うるせえなこいつ…。**

今回は関係無いけど、一応。
もし `cmd` の実行内容が入れ子のダブルクォーテーションを持っていても、かしこい cmd.exe は実行内容の最初の `"` と最後 の `"` の2つだけを削除してから実行する（少なくとも1段階の入れ子を許容する）ので問題にならない。

```bat
cmd /C "echo Call me "Taxi"."
REM > Call me "Taxi".
```

かしこい。


## `cmd "なんやかんや"` で処理全体を囲むもう1つの理由

大きな理由は遅延なんちゃらのため、と既に述べた。
実はもう1つ小さな理由があり、それは **クリップボードにコピーした時の各行末のスペース除去**と**コード自体の見やすさ**を両立するため。

ここにコマンドがあるじゃろ。

```bat
echo val1 val2 | clip
```

これを実行すると、クリップボードにコピーされるテキストはこうじゃ。

```txt:※説明のために、半角スペースを[sp]で置換してお見せしています。
val1[sp]val2[sp]
```

…見ての通り、末尾に余計なスペースがくっつく。
目的によってはそれを許容できることもあるが、今回はなんかキモいからお断り申し上げたい。
なんでこうなるのかというと、多分 `echo` は末尾のスペースをも文字列の一部として貪欲に捕捉してしまうのだろう。
故に、こう書けば一応は解決する。

```bat:※説明のために、結果の半角スペースを[sp]で置換してお見せしています。
echo val1 val2| clip
REM > val1[sp]val2
```

…が、いや今度はコード側がキモくない？&#x200B;？&#x200B;？

ワンライナーはただでさえ呪文のようになりがち。
それを少しでも見やすくするために、コマンド同士をどうのこうのする `|` とか `&` とか `(カッコ)` とかの前後にはできればスペースを挟んで記述したい、というのが個人的な願い。
cmd.exe はほんとそういうとこが**忌々しい。**←怨嗟の声

ということで試行錯誤すると、こんな風になった。

```bat:※説明のために、結果の半角スペースを[sp]で置換してお見せしています。
REM *commands*                 *results*
echo 1 test | clip             REM > 1[sp]test[sp]
echo 2 test| clip              REM > 2[sp]test
(echo 3 test) | clip           REM > 3[sp]test[sp]
(echo 4 test)| clip            REM > 4[sp]test[sp]
cmd /C "echo 5 test" | clip    REM > 5[sp]test[sp]
cmd /C "echo 6 test"| clip     REM > 6[sp]test
cmd /C "(echo 7 test)" | clip  REM > 7[sp]test
cmd /C "(echo 8 test)"| clip   REM > 8[sp]test
```

見ての通り、結果のスペース除去とコードのスペース空けを両立できるのは7番目の記法のみとなる。
特に `(カッコ)` の発見の意義は大きく、このおかげで今回のワンライナーは `(カッコ)` まみれとなったのであった。


### ちなみに

そんなに頑張らなくても、3番目の記法と**適当な一時ファイルを経由**すれば、末尾のスペースは**勝手に消える**。

```bat:※説明のために、結果の半角スペースを[sp]で置換してお見せしています。
(echo val1 val2) > %TMP%\tmp.txt & clip < %TMP%\tmp.txt & del %TMP%\tmp.txt
REM > val1[sp]val2
```

**うるせえ！&#x200B;！&#x200B;！ クリップボードに直接渡せるのがロマンなんだよ！&#x200B;！&#x200B;！&#x200B;！&#x200B;！&#x200B;！**
…そういうことにしておいてください。
っていうかなんでファイルか否かで出力差が出るんだよ……何なんだよほんと……。


## `for /R 〜` の代替手段

今回は一番短い `for /R 〜` を採用したが、他にもある。

- `for /F "delims=" %I in ('dir /A-D/B/S *.png *.gif') do なんか`
    - 機能が多いので、やりたいことが変わっても柔軟に対応しやすい。
        - その分めんどい。
    - ファイルではなくフォルダだけを得ようとした場合、以下の通り、今回の手法とは少し違う結果になる。

```:C&#x3A;\Windows\system32\cmd.exe
C:\current> for /R %I in (.) do @echo %I
C:\current\.
C:\current\sub\.
C:\current\sub\more\.

C:\current> for /F "delims=" %I in ('dir /A:D/B/S') do @echo %I
C:\current\sub
C:\current\sub\more
```

- `for /F "delims=" %I in ('where /R %CD% *.png *.gif') do なんか`
    - 単にファイルだけを検索してそのフルパスだけを返すので、わかりやすい。
        - その分自由度が低い。  
          少なくとも、フォルダ名を得ることはできない。

他には `forfiles` というのもあり、こいつは相対フォルダ名もデフォルトで取得できるので可能性は感じる。
しかし、触った感じでは以下のように自由以上の不自由に直面したのでやめた。

- 複数条件が指定不能。
- 出力が何もかもダブルクォーテーションで囲まれている。
- `echo` の出力内容にスペースが挟まるだけで問題が起こる。


## その他

- `cmd` 等の各種コマンド、`/Q` 等の各種パラメーター、`%~dp0` 等の引数置換構文、そして変数の命名では、大文字・小文字を区別しない。  
  ただし、`for` の1文字変数だけはそれを区別する。なんで…？

```txt:C&#x3A;\Windows\system32\cmd.exe
C:\current>set a=12345

C:\current>eChO %A%
12345

C:\current>FOR /r %I in (*.gif) DO @ECHO %~NXI %~NXi
b.gif %~NXi
z.gif %~NXi
```

- コマンドをバッチファイルで試す時は **UTF-8 で保存しない**。（2敗）
- 筆者環境：Windows 7 Professional 64-bit


# 参考

† 圧倒的感謝 †

### Qiita

- [上司「このフォルダーにあるファイルのリスト作って」 > #Q1．任意のフォルダーにあるファイルの一覧を作成 - Qiita](https://qiita.com/kshibamo/items/e0dd250333b70197c89c#q1%E4%BB%BB%E6%84%8F%E3%81%AE%E3%83%95%E3%82%A9%E3%83%AB%E3%83%80%E3%83%BC%E3%81%AB%E3%81%82%E3%82%8B%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%81%AE%E4%B8%80%E8%A6%A7%E3%82%92%E4%BD%9C%E6%88%90)
  - これのお陰でシンプルな `for /R 〜 in (*.*)` に辿り着けた。MVP。
- [Windows バッチファイルで格納先フォルダ名を取得 - Qiita](https://qiita.com/k1tajima/items/ebf4145367ef268dc124)
  - `%CD%` なる動的環境変数をここで初めて知った。  
    最初はそのまんまこの方法でサブフォルダ名を取得しようとしていたが、いっそ `%CD%` で丸ごと置換しちゃえばええやんと思い付き、やってみたら成功したのでヨシ！
- [バッチファイルでの試行錯誤を回避するためのメモ > #変数を使用した文字列置換 - Qiita](https://qiita.com/yz2cm/items/8058d503a1b84688af09#%E5%A4%89%E6%95%B0%E3%82%92%E4%BD%BF%E7%94%A8%E3%81%97%E3%81%9F%E6%96%87%E5%AD%97%E5%88%97%E7%BD%AE%E6%8F%9B)
  - あわやヨクナシ！  
    今回はなんか回避してたけど、まぁ回避できない時は改めてこちらの方法でやれば良いだけのこと。

### Stack Overflow

- [Batch: Pipe command output to variable and compare - Stack Overflow](https://stackoverflow.com/questions/46323963/)
  - 直接は活用しなかったが、cmd.exe に何ができて何ができないのかがまだよくわかってない時に、ワンライナーでの変数利用に関する「何ができないのか」の一部を示してくれた。
- [CMD string variable multi-line output without additional spaces and carriage returns - Stack Overflow](https://stackoverflow.com/questions/51371391/)
  - これも直接活用したかというとアレだが、`| clip` の行末スペースに困る人が他にもいて、それを一時ファイル無しで解決してる人もちゃんといる、という事実が光になった。

### 個人サイト系、他

- [コマンドプロンプト - 忘れるので](http://savannahmonitor.blog10.fc2.com/blog-entry-42.html)
  - 多分一番最初に見つけた方法がこの `for /F 〜 in ('dir /B/A-D *.*')` だった。  
    この記事に限らず、ググればググるほどこの方法があちこちで見つかったので、最初はこれが唯一の解だと思っていた。
- [コマンドプロンプトのワンライナーでズンドコキヨシ - くんすとの備忘録](https://kunst1080.hatenablog.com/entry/2016/03/13/135807)
  - ワンライナーでの変数使用例として、普通にためになってしまった。
- [BATファイルで文字列の切り出し - アプ研](https://www.upken.jp/kb/kZwpzAqblKfZDjtMXuWuwioeExKNdE.html)
  - 単純にして明快。文字列切り出しの基礎の全てがここにある。
- `cmd /?` 等のヘルプ機能
  - 1次ソース万歳。

&#x200B;

おわり