少しひっかかったのでメモ。
実施環境:
[testuser@testhost ~]$ uname -a
Linux testhost 4.18.0-147.8.1.el8_1.x86_64 #1 SMP Thu Apr 9 13:49:54 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux
[testuser@testhost ~]$ echo $SHELL
/bin/bash
事象
以下のように、100万個のファイルが入っているディレクトリがあるとします。
[root@testhost ~]# ls ./test/ | wc -l
1000000
このディレクトリの中のファイルの一覧をとろうとするとき、普通にディレクトリを引数に指定すれば特に問題なく ls コマンドを実行できます。
[root@testhost ~]# ls ./test/
1.txt
10.txt
100.txt
(以下省略)
ところが、ファイルをワイルドカードで指定しようとすると、 ls コマンドはエラーとなってしまいます。
[root@testhost ~]# ls ./test/*
-bash: /usr/bin/ls: 引数リストが長すぎます
原因
なぜこのようなことが起こるのか。
原因は Linux における特殊文字の展開の順序が大きく絡んできます。
細かい説明は省きますが、 ls コマンドでワイルドカードを指定した場合、 ls コマンドが実行される前にワイルドカードが展開されてしまいます。
すなわち、
ls ./test/*
は
ls ./test/1.txt ./test/10.txt ./test/100.txt (以下省略)
として解釈され、実行されます。
一方で、 Linux においてコマンドの引数には長さの上限があります。
そのため、大量のファイルをワイルドカードを使って ls コマンドに指定した場合、引数エラーが発生してしまうのです。
無論、このエラーは ls コマンド以外でも発生しうるので注意しましょう。
[root@testhost ~]# rm ./test/*
-bash: /usr/bin/rm: 引数リストが長すぎます
対処法
対処法はいくつかありますが、今回は3つほど紹介します。
①ワイルドカードを使用しない
一番手っ取り早いのは、ワイルドカードを使用しないことです。
例えば先ほどのようにディレクトリを指定するだけなら、きちんと ls コマンドは実行できます。
[root@testhost ~]# ls ./test/
1.txt
10.txt
100.txt
(以下省略)
②コマンド側でワイルドカードを展開できるコマンドを使用する
Linux のコマンドの中には、コマンド側でワイルドカードを展開できるコマンドもいくつか存在します。
代表的なのが find コマンドです。
find コマンドの name オプションを使用すれば、ワイルドカードをコマンド側で展開することができます。
[root@testhost ~]# find ./test/ -name "*"
./test/
./test/1.txt
./test/2.txt
(以下省略)
ls コマンドを通す必要があるなら、 xargs コマンドを使用することで対応可能です。
[root@testhost ~]# find ./test/ -name "*" -type f | xargs ls
./test/1.txt
./test/10.txt
./test/100.txt
(以下省略)
③ echo コマンドを使用する
実は echo コマンドを使用することでも対応可能です。
ただし、結果は改行区切りでなくスペース区切りとなります。
[root@testhost ~]# echo ./test/*
./test/1.txt ./test/10.txt ./test/100.txt (以下省略)
なお、ここで使用している echo コマンドは exit コマンドや let コマンド等と同じ、「 bash の組み込みコマンドである echo コマンド」です。
bin 配下にある echo コマンドは ls コマンド等と同じようにエラーになります。
[root@testhost ~]# /bin/echo ./test/*
-bash: /bin/echo: 引数リストが長すぎます
補足
ちなみに、引数の長さの上限は以下のコマンドラインで確認できます。
[root@testhost test]# getconf ARG_MAX
2097152
[root@testhost test]#
なお、この「2097152」は引数の個数の上限ではなく、「引数のバイト数の上限」であることに注意が必要です。
ワイルドカードを使用せずとも、例えば以下のように非常に長い文字列を1つ与えるだけで ls コマンドはエラーとなります。
[root@testhost ~]# cat test.txt | wc -c
3154812
[root@testhost ~]#
[root@testhost ~]# ls "`cat test.txt`"
-bash: /usr/bin/ls: 引数リストが長すぎます