getconf ARG_MAXの謎
コマンドバッファの最大長を参照するコマンドとして、getconf ARG_MAX
のことは知っていたのですが、実際に試してみると
$ getconf ARG_MAX
2097152
$ printf '%02097152d' 0 | xargs >/dev/null
xargs: argument line too long
$ printf '%02097000d' 0 | xargs >/dev/null
xargs: argument line too long
$ printf '%01097000d' 0 | xargs >/dev/null
xargs: argument line too long
$ printf '%01000000d' 0 | xargs >/dev/null
xargs: argument line too long
かなり少なく見積もっても(1/2以下にしても!)、「引数が長すぎる」という怒られが発生しました。
その後、xargs --show-limits
で詳細が確認できると知り、やってみたのですが
$ xargs --show-limits -r -a /dev/null
Your environment variables take up 437 bytes
POSIX upper limit on argument length (this system): 2094667
POSIX smallest allowable upper limit on argument length (all systems): 4096
Maximum length of command we could actually use: 2094230
Size of command buffer we are actually using: 131072
Maximum parallelism (--max-procs must be no greater): 2147483647
Execution of xargs will continue now, and it will try to read its input and run commands; if this is not what you wanted to happen, please type the end-of-file keystroke.
どれがどれか分からん!/(^o^)\
Maximum length of command we could actually use: 2094230
Size of command buffer we are actually using: 131072
とくにこの部分など、we could actually use
とwe are actually using
の違いは?前者は仮定法だから「拡張すればこれぐらいまで使えるけどね」みたいな意味で、後者は現在進行形だから「(拡張なしだと)今々、正味で使えるのはこれだけだよ」みたいな意味なのか・・・。
スクリプトを自作して実証値を探る
実際にやってみれば分かるさ、ということで、巨大な長さの引数(xargs
が分割しないように区切り文字を含めない)を手当たり次第に作成し、xargs
コマンドに渡して正解を探り当てるという経験主義的なシェルスクリプト(get_empirical_argmax.sh
)を作ってみました。
といっても、POSIXが許容する最低MAX値4096
から+1
ずつ線形探索すると、僕のCore i7が暖房器具になってしまうので、みんな大好き二分探索を採用しました。
以下、自作スクリプトの実行結果です。最終行に、二分探索で探り当てた実証値を出力します。
$ ./get_empirical_argmax.sh
Your environment variables take up 437 bytes
POSIX upper limit on argument length (this system): 2094667
POSIX smallest allowable upper limit on argument length (all systems): 4096
Maximum length of command we could actually use: 2094230
Size of command buffer we are actually using: 131072
Maximum parallelism (--max-procs must be no greater): 2147483647
Execution of xargs will continue now, and it will try to read its input and run commands; if this is not what you wanted to happen, please type the end-of-file keystroke.
--------
Empirical size of command buffer : 131072
というわけで、以下の行に示される値(131072
)が、コマンドバッファの正味の最大長を示していると考えてよさそうです。
Size of command buffer we are actually using: 131072
get_empirical_argmax.sh
以下に、作成したスクリプトを載せておきます。
-
xargs --no-limits
の結果から、POSIXが許容する最低MAX値と、上限MAX値を取得し、その範囲に二分探索を再帰的に適用します。 - 最終的に得られた値に
6
を加算した値を結果としています。これは、xargs
は世話するコマンドが指定されない場合、デフォルトでecho
コマンドを使用するため、コマンドバッファの正確なサイズとしては文字列echo
の長さと、コマンドライン末尾のナル文字\0
の長さの合計である6
を含める必要があるためです。
#!/usr/bin/env bash
set -u
# ======================================================================
#
# is_beyond_argmax() - test argmax is beyond limit
#
# ======================================================================
is_beyond_argmax(){
! printf "%0${1}d" 0 | xargs >/dev/null 2>&1
return $?
}
# ======================================================================
#
# get_empirical_argmax() - search for the argmax with binary search.
#
# ======================================================================
get_empirical_argmax()
(
typeset -r _min=$1
typeset -r _max=$2
is_beyond_argmax ${_min} && { \
printf "$((_min - 1))"
return
}
typeset -r _median=$((_min + (_max - _min) / 2))
if is_beyond_argmax ${_median}; then
printf $(get_empirical_argmax ${_min} ${_median})
else
printf $(get_empirical_argmax $((_median + 1)) $_max)
fi
)
# ======================================================================
#
# Main
#
# ======================================================================
xargs --show-limits -r -a /dev/null
printf '\n'
# POSIX smallest allowable upper limit
typeset -r min=$(LC_ALL=C LANG=C xargs --show-limits -r -a /dev/null 2>&1 \
| grep -i '\s*POSIX\s*smallest\s*allowable\s*upper\s*limit\s*on\s*argument\s*length\s*(\s*all\s*systems\s*)\s*:' \
| sed 's/\s//g' \
| cut -d':' -f2
)
# POSIX upper limit
typeset -r max=$(LC_ALL=C LANG=C xargs --show-limits -r -a /dev/null 2>&1 \
| grep -i '\s*POSIX\s*upper\s*limit\s*on\s*argument\s*length\s*(\s*this\s*system\s*)\s*:' \
| sed 's/\s//g' \
| cut -d':' -f2
)
[ -z "${min}" ] || [ -z "${max}" ] && {
exit 1
}
is_beyond_argmax ${min} || ! is_beyond_argmax $((max + 1)) && {
exit 1
}
echo '--------'
# 6 is the sum of string size of "echo " and null terminating character.
printf 'Empirical size of command buffer :%d\n' \
"$(($(get_empirical_argmax ${min} ${max})+6))"
exit 0