zargs - 「argument list too long」を撃退せよ

zshのglob機能(ワイルドカード)はとてつもなく強力だ。zshユーザならほぼ間違いなくsetopt extended_globの設定を入れていると思う。extended_globしておかないと強力なglobの恩恵を受けられないからだ。たとえば**/*.hは*.hをカレントディレクトリとそのサブディレクトリを再帰的に探索し、展開する。

しかし、再帰的なglobを使っていると展開後の長さがとんでもないことになりがちである。そのため「zsh: argument list too long」(日本語なら「zsh: 引数リストが長すぎます」)のエラーを見たことがあると思う。制限きつすぎだバカヤローって叫んだ人もいるだろう。そして、仕方なくfindとxargsを使ってしまう。これはzshの制限ではなくてOSの制限なのだ!

たとえばGNU/Linuxならば、131072バイト=128KBだ。

linux/limits.h:#define ARG_MAX 131072 /* # bytes of args + environ for exec() */

$ getconf ARG_MAX
131072

普通ならばここであきらめもつくものだが、zshユーザは違う!extended_globの恩恵を受けつつも、ARG_MAXの制限を乗り越える方法を用意してある。zargsだ!xargsの強力版だと思ってくれればいい。*1使うためにはautoloadしないといけない。.zshenvに加えておこう。なぜ.zshenvかというと、エディタで使うシェルもzshにしている場合でも使えるようにするためだ。.zshrcだと対話的環境でないと使えない。

autoload zargs

zargsはこんな書式になる。

zargs [options] [--] arguments -- command

xargsと違うのは標準入力からではなくて引数から引数リストを読み込むところだ。引数リストの区切には -- を使う。そうしないと、どこまでがzargsのオプションなのか、どこまでが引数リストなのかの区別がつかないからだ。

たとえば、カレントディレクトリ以下のすべてのファイルについてls -lしたいならば、こうする。

zargs -- **/*(.) -- ls -l

この場合、zargsの直後に -- をつける必要がある。zargsのオプションは存在しないが、「-a」のような変態的ファイル名がある場合オプションと誤認されてハマってしまうからだ。そういうファイル名が存在しないことがあらかじめわかる場合は最初の--は省略できる。

なお、引数の長さ制限は外部コマンドの実行のときのみ関わってくる。zshのbuiltinコマンドだと制限はない。

$ =echo /usr/include/**/*.h|wc
zsh: 引数リストが長すぎます: /bin/echo
      0       0       0
zsh: exit 127   =echo /usr/include/**/*.h |
zsh: done       wc
$ echo /usr/include/**/*.h|wc
      1    3829  126073
$ env | wc
    175     191    7147

この例の場合ARG_MAXよりも少ないのに長すぎると言われているが、環境変数領域も含めてなのでオーバーしてしまったのだ。

*1:MS-DOS時代XCOPYコマンドの強力版にZCOPYコマンドがあったよね。