LoginSignup
7
3

More than 5 years have passed since last update.

コマンドバッファの正味の最大長を二分探索で探ってみた

Last updated at Posted at 2017-03-05

getconf ARG_MAXの謎

コマンドバッファの最大長を参照するコマンドとして、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
$ 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 usewe are actually usingの違いは?前者は仮定法だから「拡張すればこれぐらいまで使えるけどね」みたいな意味で、後者は現在進行形だから「(拡張なしだと)今々、正味で使えるのはこれだけだよ」みたいな意味なのか・・・。

スクリプトを自作して実証値を探る

実際にやってみれば分かるさ、ということで、巨大な長さの引数(xargsが分割しないように区切り文字を含めない)を手当たり次第に作成し、xargsコマンドに渡して正解を探り当てるという経験主義的なシェルスクリプト(get_empirical_argmax.sh)を作ってみました。

といっても、POSIXが許容する最低MAX値4096から+1ずつ線形探索すると、僕のCore i7が暖房器具になってしまうので、みんな大好き二分探索を採用しました。

以下、自作スクリプトの実行結果です。最終行に、二分探索で探り当てた実証値を出力します。

get_empirical_argmax.shの使い方(引数不要)
$ ./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を含める必要があるためです。
get_empirical_argmax.sh
#!/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
7
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
3