为啥在传递引用的参数时会得到“/bin/sh:参数列表太长”?

Posted

技术标签:

【中文标题】为啥在传递引用的参数时会得到“/bin/sh:参数列表太长”?【英文标题】:Why do I get "/bin/sh: Argument list too long" when passing quoted arguments?为什么在传递引用的参数时会得到“/bin/sh:参数列表太长”? 【发布时间】:2012-07-13 14:00:35 【问题描述】:

一个命令行可以传递给sh -c ''多长时间? (在 bash 和 bourne shell 中)

这个限制远低于操作系统的限制(在现代 Linux 的情况下)。

例如:

$ /bin/true $(seq 1 100000)
$ /bin/sh -c "/bin/true $(seq 1 100000)"
bash: /bin/sh: Argument list too long

我该如何规避这个问题?

更新

我想指出getconf 在这里帮不上忙(因为这不是系统限制):

$ seq 1 100000 | wc -c
588895
$ getconf ARG_MAX
2097152

更新 #2

现在我明白了这里的意义所在。这不是 shell 限制,而是系统限制,而是针对每个参数的长度,而不是针对整个 arglist。

$ /bin/true $(seq 1 100000)
$ /bin/true "$(seq 1 100000)"
bash: /bin/true: Argument list too long

感谢 CodeGnome 的解释。

【问题讨论】:

相关:Does "argument list too long" restriction apply to shell builtins? 另见Argument list too long error for rm, cp, mv commands 【参考方案1】:

创建一个以#!/bin/sh 作为第一行的文件,然后将其余命令放在后续行中? :)

更重要的是,您还可以使用 -s 选项从 STDIN 读取命令,这样您就可以生成长命令行并将其输入到 /bin/sh -s

【讨论】:

即使没有-s,你也可以这样做;对我知道那个;可能这是最好的解决方案,除非您需要使用 shell 标准输入来处理其他事情 我认为 CodeGnome 找到了解决方案。看看他的回答。 排序 - 除了仍然存在最大命令行长度,并且“for $(生成大量参数的命令);do;done”结构是一个可以超过该长度的命令行长度。在 STDIN 上输入命令(无论是 -s 还是 for arg in $(</dev/stdin))确实是获得实际无限“参数”支持的唯一方法。【参考方案2】:

TL;DR

单个参数必须短于 MAX_ARG_STRLEN。

分析

根据this link:

并且作为自 2.6.23 以来的附加限制,一个参数不得长于 MAX_ARG_STRLEN (131072)。如果您生成像“sh -c 'generated with long arguments'”这样的长调用,这可能会变得相关。

这正是 OP 确定的“问题”。虽然允许的参数数量可能非常大(请参阅getconf ARG_MAX),但当您将带引号的命令传递给 /bin/sh 时,shell 会将带引号的命令解释为单个字符串。在 OP 的示例中,正是这个单个字符串超出了 MAX_ARG_STRLEN 限制,而不是扩展参数列表的长度。

具体实现

参数限制是特定于实现的。但是,this Linux Journal article 建议了几种解决方法,包括增加系统限制。这可能并不直接适用于 OP,但它在一般情况下仍然有用。

做点别的事情

OP 的问题实际上并不是真正的问题。问题是强加一个不能解决现实问题的任意约束。

您可以使用循环轻松解决这个问题。例如,使用 Bash 4:

for i in 1..100000; do /bin/sh -c "/bin/true $i"; done

工作得很好。它肯定会很慢,因为您在每次通过循环时都会生成一个进程,但它肯定会绕过您遇到的命令行限制。

描述你真正的问题

如果循环无法解决您的问题,请更新问题以描述您实际尝试使用非常长的参数列表解决的问题。探索任意行长限制是一项学术活动,而不是 Stack Overflow 的主题。

【讨论】:

这不是系统限制!这是外壳限制! 我在带有2.6.31的系统上没有MAX_ARG_STRLEN @DennisWilliamson:我也是(3.2.0-rc7-686-pae);但无论如何,参数长度是重点。唯一的问题是如何找到它。 @CodeGnome:有些系统没有MAX_ARG_STRLEN(我和丹尼斯的系统)。所以我们需要找到另一种方法来找到这个值。 @DennisWilliamson execve(2) 说“在内核 2.6.23 及更高版本上……每个字符串的限制是 32 页(内核常量 MAX_ARG_STRLEN),最大字符串数是 0x7FFFFFFF。”因此,您的系统似乎确实有它,但它是一个内核常量。【参考方案3】:

在我的操作系统上,它是二分法获得的最大长度

/bin/sh -c "/bin/true $(perl -e 'print"a"x131061')"

所以它给出 131071

但是没有理由让一行这么长;如果是因为参数太多,可以用“$@”代替;例如

/bin/sh -c 'command "$@"' -- arg1 arg2 .. argn

【讨论】:

【参考方案4】:

我没有收到该错误消息。我的秘密?单引号:

/bin/sh -c '/bin/true $(seq 1 100000)'

如果我使用双引号,每个 shell 都会出现该错误:

$ /bin/sh -c "/bin/true $(seq 1 100000)"
-bash: /bin/sh: Argument list too long
$ /bin/bash -c "/bin/true $(seq 1 100000)"
-bash: /bin/bash: Argument list too long
$ /bin/ksh -c "/bin/true $(seq 1 100000)"
-bash: /bin/ksh: Argument list too long
$ /bin/zsh -c "/bin/true $(seq 1 100000)"
-bash: /bin/zsh: Argument list too long

当使用双引号时,参数列表在当前 shell 中被扩展,这一事实证明了 Bash 是发出错误“-bash: ...”的那个,而不管使用哪个 shell 来运行命令。顺便说一下,在我的系统上,sh 是 Dash。

即使对于其他“主机”外壳也是如此:

$ dash
$ /bin/bash -c '/bin/true $(seq 1 100000)'
$ /bin/bash -c "/bin/true $(seq 1 100000)"
dash: /bin/bash: Argument list too long

病人:医生,我这样做会很痛。” 医生:不要那样做。

【讨论】:

丹尼斯,单/双引号不是重点。我在这里使用$(seq 1 100000) 仅用于演示目的;你如何产生这个长字符串并不重要;当然我在这里需要双引号,因为我需要很长的字符串。主要问题是sh -c ''的论点可以持续多久 @IgorChubin:答案是,如果它很重要,那么你做错了。顺便说一句,在我的系统上,它是131071。 @IgorChubin:见“做点别的”here。 真是个好主意 :) 但无论如何,我想 CodeGnome 已经找到了正确的答案。真正的重点是参数的长度。 为了完整性:/bin/bash -c '/bin/true "$(seq 1 100000)"' 也因为 shell 如何标记字符串而中断。 +1 用于解决这个 X/Y 问题中的 Y,不过,我认为发布相关问题的解决方案很有用。

以上是关于为啥在传递引用的参数时会得到“/bin/sh:参数列表太长”?的主要内容,如果未能解决你的问题,请参考以下文章

为啥在通过 const 引用传递临时值时调用复制构造函数?

为啥在 GridSearchCV 中使用 StandardScaler 时会得到不同的结果?

在 C# 中传递数组参数:为啥它是通过引用隐式传递的?

为啥这个 Ruby 方法通过引用传递它的参数

为啥 PHP 在一种情况下允许将文字传递给按引用传递的参数,而在其他情况下不允许?

当我们不传递任何命令行参数时,为啥我们不会得到错误?