当传递带引号的参数时,为什么会出现“/bin/sh: Argument list too long”的错误?

31

在bash和bourne shell中,可以传递给sh -c''的命令行长度有多长?

这个限制比操作系统的限制要低得多(在现代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限制,而是一个系统限制,但是限制的是每个参数的长度,而不是整个参数列表的长度。

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

感谢CodeGnome对此进行了解释。


4个回答

30

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,但在一般情况下仍然有用。

做其他事情

原帖的问题实际上并不是一个真正的问题。该问题强加了一个任意的限制,而这个限制并没有解决现实世界中的问题。

您可以通过使用循环轻松解决此问题。例如,在Bash 4中:

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

这个方法可以正常工作。由于每次循环都会产生一个进程,因此速度肯定会慢一些,但它确实可以解决您遇到的命令行限制问题。

描述您真正的问题

如果循环不能解决您的问题,请更新问题以描述您实际尝试解决使用非常长的参数列表的问题。探索任意行长度限制是学术练习,不属于 Stack Overflow 的主题。


那不是系统限制!那是Shell的限制! - Igor Chubin
2
我在一个 2.6.31 系统上没有 MAX_ARG_STRLEN - Dennis Williamson
啊,它只是对getconf不可见,也不在limits.h中(但在man页面中有)。谢谢。 - Dennis Williamson
@DennisWilliamson 随时可以。仅供参考,我也使用了grep查找内核头文件,发现这个定义在/usr/src/linux-headers-3.0.0-14/include/linux/binfmts.h中,如果有人需要重新定义它。 - Todd A. Jacobs
如果你是正确的,那么这将是另一个愚蠢的 Linux 偏离 POSIX 标准。POSIX 只知道一个限制:进程的初始堆栈不能超过 ARG_MAX - schily
显示剩余4条评论

6

我不会收到那个错误信息。我的秘诀?使用单引号:

/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。

即使对于其他“主机”shell,这一点也是正确的:
$ dash
$ /bin/bash -c '/bin/true $(seq 1 100000)'
$ /bin/bash -c "/bin/true $(seq 1 100000)"
dash: /bin/bash: Argument list too long

病人:医生,我这样做会很疼。
医生:不要这样做。


1
Dennis,单/双引号不是关键点。我在这里使用$(seq 1 100000)仅用于演示目的;无论您如何生成此长字符串都没有关系;当然,我需要双引号,因为我需要非常长的字符串。主要问题是sh -c''的参数可以有多长。 - Igor Chubin
@IgorChubin:答案是,如果这很重要,那么你做错了。顺便说一下,在我的系统上,它是131071 - Dennis Williamson
@IgorChubin:请查看此处的“Do Something Else”(https://dev59.com/jmgu5IYBdhLWcg3wOUu-#11475732)。 - Dennis Williamson
好主意 :) 但无论如何,我想CodeGnome已经找到了正确的答案。真正的问题是参数的长度。 - Igor Chubin
仅为完整起见:/bin/bash -c '/bin/true "$(seq 1 100000)"'也会出现问题,因为shell如何对字符串进行标记化。虽然在解决X/Y问题中找到了Y的解决方案,但+1——我认为发布相关问题的解决方案很有用。 - Todd A. Jacobs

1

创建一个文件,第一行写上#!/bin/sh,然后在接下来的行中输入你的命令即可。 :)

更严肃地说,你也可以使用-s选项从标准输入读取命令,这样你就可以生成你的长命令行并将其导入到/bin/sh -s中。


即使没有 -s,你也可以这样做;是的,我知道;除了需要使用 shell stdin 进行其他操作的情况外,这可能是最好的解决方案。 - Igor Chubin
我认为CodeGnome已经找到了解决方案。请看他的回答。 - Igor Chubin
除了还有一个最大命令行长度的限制之外,这种结构“for $(生成大量参数的命令); do; done”是一条命令行,可能超过该长度。通过标准输入管道传递命令(无论是使用-s还是for arg in $(</dev/stdin))实际上是唯一的方式来实现真正无限的“参数”支持。 - dannysauer

0

在我的操作系统中,它是通过二分法获得的最大长度。

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

所以它会返回131071

但是没有必要让一行变得这么长;如果是由于大量参数导致的,可以使用"$@"代替;例如

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接