chmod や mv すると "bash: Argument list too long" が出るようになった

<h2>bash: /bin/chmod: Argument list too long<br>bash: /bin/mv: Argument list too long</h2>

> macOS や Linux などの `bash` で、特定のディレクトリで `chmod 0744 *` や `mv` すると、`-bash: /bin/chmod: Argument list too long` エラーが出るようになった。
>
> ちなみに、**他のディレクトリでは動く**。

```shell-session:エラー内容
$ cd ~/path/to/my/heavy/dir
$ chmod 0744 *
-bash: /bin/chmod: Argument list too long
```
```shell-session:エラー内容
$ mv ~/path/to/my/heavy/dir/*.png ~/path/to/be/moved
-bash: /bin/mv: Argument list too long
```

## TL; DR <sub><sup>（今北産業）</sup></sub>

1. <font color=darkorange>**対象ファイルの数が多い場合**</font>に <ruby>`Argument`<rt>引数大杉</rt></ruby><ruby>`list too long`<rt>エラー</rt></ruby> が出ます
2. `find` コマンドで１つ １つ `chmod` や `mv` コマンドに渡すと処理できます
3. `find` の末尾に `{} \;` を付けるのがポイント

```bash:基本構文
find . -type f -exec <コマンド> {} \;
```

```shell-session:実行例（chmodの場合）
$ cd ~/path/to/my/heavy/dir
$ find . -type f -exec chmod 0744 {} \;
```

- 上記の内訳
    - `find .` でカレント・ディレクトリ以下のファイル／ディレクトリを出力
    - `-type f` で対象をファイルに限定
    - `-exec chmod 0744` で `chmod` コマンドを指定。パーミッションは `744`。
    - `{}` で `find` から渡されたファイル名に置き換わる。ここでは `chmod` の引数として扱われる。
    - `\;` でファイル １ つ １ つに対して処理。`+` にすると一括処理になるので同じエラーになる。


`mv`（移動）の場合は、一旦 `find` のみで動作確認（パスの出力を確認）してから、残りの `-exec 〜` を付けるようにするとミスがなくていいと思います。

```shell-session:大量の特定ファイルをmvする例
$ # ちゃんと一覧を返すか確認
$ find ./path/dir/from -name "*.png" 
$ # 移動
$ find ./path/dir/from -name "*.png" -exec mv {} /path/dir/to \;
...
```

- 併せて読みたい：
    - [Bashで「Argument list too long」が発生した時の対策](https://qiita.com/ma2shita/items/00e73e435e539d08c8b7) @ Qiita
        - 引数が長すぎる場合の対処法
    - [linuxのコマンドラインでargument list too longエラーが出た時の対処方法](https://mome-n.com/posts/shell-argument-list-too-long/) @ もふもふのブログ
        - 引数にはシステムごとに個数や長さに上限があり、その許容量の確認方法

## TS; DR <sub><sup>（kwsk）</sup></sub>

### １万件以上のファイルやディレクトリを `mv` もしくは `chmod` したい

ファイル数が１万件以上もあるディレクトリのファイルのパーミッションを `chmod` コマンドで一括変更しようとしました。環境は macOS Mojave と CentOS 7 の `bash` です。

```shell-session
$ cd ~/data

$ # ディレクトリ内のファイル数（1.4万件以上）
$ ls -U1 | wc -l
   14754

$ # パーミッションがファイルによってバラバラなので統一したい
$ ls -la | head -10
total 637288
drwxrwxrwx  14756 admin  staff   501704  7 21 18:53 .
drwxrwxrwx     18 admin  staff      612 10 19  2018 ..
-rw-r--r--      1 admin  staff     7346  7 10 05:47 10014788acc2adbf6c4b.json
-rwxr--r--      1 admin  staff     4571  7  7 21:34 100149cb080117f43f4c.json
-rw-r--r--      1 admin  staff     2139  7 10 13:14 1001600e8b45634fcf4e.json
-rwxr--r--      1 admin  staff     4122  7  7 00:34 1001add7479c31fbae67.json
-rwxrwxrwx      1 admin  staff      952  2  5 07:20 1001c974bd0ecc29b012.json
-rwxrwxrwx      1 admin  staff    33438  7 15 18:49 10026d96f2634ba36362.json
-rwxrwxrwx      1 admin  staff    13730  7 17 07:53 1002745259893326ccfd.json

```

作業したいディレクトリで、いつもどおり単純に `chmod 0744 *` すると `-bash: /bin/chmod: Argument list too long` エラーが出ました。

「えっ？！ 引数、なんかおかしい？」と思うも、構文を何度確認しても問題が見当たらず悩みました。

```shell-session:いたって普通の chmod
$ chmod 0744 *
-bash: /bin/chmod: Argument list too long
```

調べてみると、どうやら **`*` を指定したため処理対象のファイル一覧が、一気に引数に渡されたことが原因**のようです。

つまり、「`Argument list too long`」は「引数（`Argument`）が多い」のではなく、「引数として渡された一覧の数（`Argument list`）が多い」というエラーでした。

「macOS って UNIX なのに、たかが 1 万ちょいのファイル数扱えないの？」と思ったのですが、コマンドに渡せる引数の数や長さにはシステム上の制限があり、`getconf ARG_MAX` で許容量を確認できます。

```shell-session
$ getconf ARG_MAX
1048576
```

なんと、筆者の MacBookPro (Early 2015)では、1 MB 程度しか扱えないようです。

つまり、パスが展開されたファイル名すべてを引数として渡すことになり、「そりゃ、すぐキャパ・オーバーするわ」というわけです。

そこで、`for` ループがごとく、1 つ 1 つファイルパスを渡しながら処理する方法で対処しました。

```shell-session
$ # find で1ファイルごとに処理 （いささか時間がかかる）
$ find . -type f -exec chmod 0744 {} \;

$ # パーミッションが揃った
$ ls -la | head -10
total 637288
drwxrwxrwx  14756 admin  staff   501704  7 21 18:53 .
drwxrwxrwx     18 admin  staff      612 10 19  2018 ..
-rwxr--r--      1 admin  staff     7346  7 10 05:47 10014788acc2adbf6c4b.json
-rwxr--r--      1 admin  staff     4571  7  7 21:34 100149cb080117f43f4c.json
-rwxr--r--      1 admin  staff     2139  7 10 13:14 1001600e8b45634fcf4e.json
-rwxr--r--      1 admin  staff     4122  7  7 00:34 1001add7479c31fbae67.json
-rwxr--r--      1 admin  staff      952  2  5 07:20 1001c974bd0ecc29b012.json
-rwxr--r--      1 admin  staff    33438  7 15 18:49 10026d96f2634ba36362.json
-rwxr--r--      1 admin  staff    13730  7 17 07:53 1002745259893326ccfd.json
$ # 🎉
```

## 動作確認環境

- macOS Mojave（OSX 10.14.5）
  - bash: GNU bash, version 3.2.57(1)-release (x86_64-apple-darwin17)
  - `chmod`: BSD General Commands, July 08, 2004
- CentOS Linux 7 (Core)
  - bash: GNU bash, バージョン 4.2.46(2)-release (x86_64-redhat-linux-gnu)
  - `chmod`: GNU coreutils 8.22

## 参考文献

- [Argument list too long - Apache](https://stackoverflow.com/a/28338565/8367711) @ StackOverflow
- [“unable to execute /bin/chmod: Argument list too long” when I'm trying to use chmod command](https://elementaryos.stackexchange.com/a/1056) @ StackExchange
- [`find`コマンドで`-exec`オプションを使用する時の最後の「`{} ;`」ってなんだっけ？](https://qiita.com/legitwhiz/items/e609537fb6226081f5b5) @ Qiita
- [ディレクトリ内のファイル数をカウントする](https://qiita.com/stc1988/items/e3a1d7dccafe4ab573fa) @ Qiita
- [`find` の `-exec` optionの末尾につく `\;` と `+` の違い。](https://qiita.com/ironsand/items/0aed2ffca295706cab69) @ Qiita
- [`find`チートシート](https://qiita.com/tomozo6/items/5646d6a062950f986372) @ Qiita
- [How can I move many files without having Argument list too long?
](https://stackoverflow.com/a/34693769/8367711) @ StackOverflow
- [linuxのコマンドラインでargument list too longエラーが出た時の対処方法](https://mome-n.com/posts/shell-argument-list-too-long/) @ もふもふのブログ
- [Bashで「Argument list too long」が発生した時の対策](https://qiita.com/ma2shita/items/00e73e435e539d08c8b7) @ Qiita