您应该了解 shell 如何处理空格和引号。我说的是“外壳”——有不同的外壳,我不知道它们之间的区别,所以我要写的可能不适用于你。有人可以随意编辑它,让它更精确。
您可以在 shell 命令中包含各种复杂的语法:管道命令、输入和输出文件、内插变量、内插命令、环境变量,以及至少 4 种(是的,4 种)不同的引用字符串的方式。但是为了这个问题的目的,我们只说 shell 命令是一个命令名,后跟一个(可能为空的)字符串参数列表。命令名称可以是内置命令(cd、ls、sudo 等),也可以是可执行文件。或者,换句话说,一个shell命令是一个或多个字符串的列表(包括第一个字符串,它告诉shell它是什么类型的命令)。
由于上面提到的复杂性,有几个字符是特殊字符。这意味着您可能需要使用引号对它们进行转义。但是,引号会在语言中引入大量冗余。例如,以下命令是等效的:
tar --create --exclude=/foo/bar /foo
tar --create --exclude='/foo/bar' /foo
tar --create --exclude="/foo/bar" /foo
tar --create '--exclude=/foo/bar' /foo
tar --create "--exclude=/foo/bar" /foo
在每种情况下,命令都是运行带有参数列表--create、--exclude=/foo/bar、/foo 的可执行文件tar。
注意引号的行为,这与我所知道的所有其他语言都不同。在大多数语言中,字符串文字完全由一对引号括起来 - 这就是编译器/解释器知道它们从哪里开始和结束的方式。但是在 shell 命令中,空格是告诉 shell 一个参数结束和下一个参数开始的地方。 (带引号/转义的空格不算在内。)引号的唯一目的是改变某些字符的处理方式。 Shell 命令对此非常灵活,因此以下命令也与上述命令等价:
tar -"-"create --exc'lude=/fo'o/bar /foo
tar --cr'eate' --exclude"="/foo"/bar" /foo
当我说这些命令是等效的时,我的意思是tar 可执行文件无法知道哪个命令被调用了。也就是说,不可能编写一个可执行文件mycommand 使得命令mycommand foo 和mycommand "foo" 向STDOUT 或STDERR 写入不同的输出,或者返回不同的退出代码,或者以其他方式表现不同。
但是,当从 nodejs 运行 shell 命令时,您不需要使用 shell 功能进行管道传输、文件流式传输、插入变量等,因为如果您愿意,javascript 可以处理所有这些内容。因此,当您向spawn 提供参数时,它会绕过这些shell 功能;它对 shell 特殊字符没有任何作用。您只需直接提供参数。所以在下面的例子中,参数之一将是--exclude=/foo/bar baz,这将导致tar 忽略/foo 目录中名为bar baz 的文件/目录:
const tar = spawn('tar', [
'--create', '--gzip',
'--exclude=/foo/bar', '--exclude=/foo/baz', '--exclude=/foo/bar baz',
'/foo'
], { stdio: ['ignore', 'pipe', 'inherit'] });
(虽然很明显,如果您使用的是 javascript 字符串文字,您可能需要在 javascript 级别转义一些字符。)
我不喜欢 joshuhn 的任何一个回答。 (1) 甚至对我不起作用,我很惊讶它对他起作用——如果它起作用了,那么我认为它是 nodejs 中的一个错误(或者可能在 tar 中)。 (我在 Ubuntu 16.04.3 LTS 中使用 GNU tar v1.28 运行 nodejs v6.9.5。)至于(2),这意味着不必要地将 shell 字符串处理的所有复杂性引入到您的 javascript 代码中。正如the documentation 所说:
注意:如果启用了shell 选项,请不要将未经处理的用户输入传递给此函数。任何包含 shell 元字符的输入都可用于触发任意命令执行。
我不知道 shell 转义的所有复杂性,所以我不会冒险使用带有不受信任输入的 shell 选项运行 spawn。