「ls: 引数行が長すぎます」を回避する

ls: 引数行が長すぎます

ファイルが大量にあるディレクトリをワイルドカードを使用してlsすると以下のように怒られる。

  • ダメな例
$ rm /hoge/fuga/??/*/*/hige/
ls: 引数が多すぎます 

エラーの文字列でググると、回避方法としてfindとxargsの組み合わせが一般的らしい。 http://moviesearch1.blog15.fc2.com/blog-entry-8.html

【例】
find /var/www/html/movie-search/cache/ -name "*.cash" -print0 | xargs -0 rm -rf

エラーはシェルがワイルドカードを展開してコマンドに渡す際に上限値を超える場合に発生する。

上記の例だとfindの-nameで渡しているワイルドカードはシェルでは解釈されず、findコマンドが展開を担当しているので、問題を回避できる。

「ダメな例」の場合はこの方法を使えない。なぜなら、ディレクトリ階層がネストしていて、それを-nameできれいに吸収できないからだ。 やるとして以下の様な汚い感じになるだろう。

  • 回避例1
find  /hoge/fuga/ -name "??/*/*/hige/" -print0 | xargs -0 rm -rf

直感的じゃないし、余計なディレクトリを探しに行くだろう。

更に調べると、どうやらechoとxargsの組み合わせで回避できるらしい。 http://x68000.q-e-d.net/~68user/unix/pickup?xargs

echo はシェルの内部コマンドのため ARG_MAX の制限を受けない。一方、xargs は ARG_MAX の値を把握していて、引数が ARG_MAX を越えそうになると

バッドノウハウ感があるが採用する。

echoとxargsの組み合わせで「ダメな例」を回避するには以下のようにする。

  • 回避例2
$ echo /hoge/fuga/??/*/*/hige/ | xargs rm -rf

回避例1にくらべてだいぶマシなのがわかる。

xargs -I{}を使うとき

上記の例のrmをmvに書き換えるともう一つの問題に直面する。

  • ダメな例2
$ echo /hoge/fuga/??/*/*/hige/ | xargs -I mv {} {}.bk
xargs: 引数行が長すぎます

上記ではうまくいっていたxargsだが、-I{}を使用した途端が引数が長すぎると文句を行ってきた。 これはいかに??

困ったらmanを読む。以下のように書いてある。 http://linuxjm.osdn.jp/html/GNU_findutils/man1/xargs.1.html

-I replace-str
    xargs が実行するコマンドに対してユーザが引き数 (すなわち initial-arguments) を指定したとき、その initial-arguments 中にある replace-str の部分すべてを、標準入力から読み込んだ名前で 置き換える。 なお、空白は、クォートされていない場合も、入力される項目の区切りには ならない。区切り記号は改行文字だけになるのだ。 -x と -L 1 が自動的に設定される。 

「区切り記号は改行文字だけになるのだ」 サラッと書いてあるが、驚きである。 echoはファイル名を空白区切りで返すので、すべてのファイル名を一つの文字列として扱うためエラーとなる。 手っ取り早く解決するには、空白を改行に書き換えればよい。

$ echo /hoge/fuga/??/*/*/hige/ | tr ' ' '\n' | xargs -I mv {} {}.bk

この一文だけ見ると、なぜtrがいるのかわからない謎シェルになってしまうこと請け合いである。