Est*_*ask 5 javascript tar spawn node.js
我在Node.js spawn参数中使用双引号,因为它们可能包含空格:
const excludes = ['/foo/bar', '/foo/baz', '/foo/bar baz'];
const tar = spawn('tar', [
'--create', '--gzip',
// '--exclude="/foo/bar"', '--exclude="/foo/baz"', '--exclude="/foo/bar baz"'
...excludes.map(exclude => `--exclude="${exclude}"`),
'/foo'
], { stdio: ['ignore', 'pipe', 'inherit'] });
Run Code Online (Sandbox Code Playgroud)
出于某种原因,tar忽略--exclude以这种方式提供的参数.结果与spawn存在require('child_process').spawn和结果相同require('cross-spawn').
--exclude 当没有双引号用于不需要它们的路径时,可以正常工作.
即使使用双引号,同样的东西也可以像shell一样工作:
tar --create --gzip --exclude="/foo/bar" --exclude="/foo/baz" /foo > ./foo.tgz
Run Code Online (Sandbox Code Playgroud)
我不知道那里发生了什么,如何spawn调试以检查它是否为双引号做了一些奇怪的转义.
小智 7
这是引用类型优先级中的问题.双引号优先于单引号,因此产生的调用会中断.
系统shell将剥离参数周围的引号,因此程序在结尾处获得不带引号的值.产生一个进程绕过这个步骤,因为它绕过了shell,因此程序将这些文字引号作为参数的一部分,并且不知道如何正确处理它们.
我知道有两个解决这个问题的实际选择:
这是违反直觉的,但切换报价类型应解决此问题.将上面的代码切换为:
const tar = spawn("tar", [
"--create", "--gzip",
"--exclude='/foo/bar'", "--exclude='/foo/baz'", "/foo"
], { stdio: ["ignore", "pipe", "inherit"] });
Run Code Online (Sandbox Code Playgroud)或者,您可以使用{ shell: true }和使用当前格式.这将通过shell传递spawn请求,因此将发生当前正在跳过的解析步骤.在这里查看更多相关信息.
const tar = spawn('tar', [
'--create', '--gzip',
'--exclude="/foo/bar"', '--exclude="/foo/baz"', '/foo'
], { stdio: ['ignore', 'pipe', 'inherit'], shell: true });
Run Code Online (Sandbox Code Playgroud)如果我明白你在问什么,你只想保留默认的 shell 行为,即去掉引号并将参数作为单个参数传递,即使它有空格。
在这种情况下,您可以执行以下操作:
spawn(exe, args, { windowsVerbatimArguments: true });
Run Code Online (Sandbox Code Playgroud)
请参阅文档:
windowsVerbatimArguments<boolean>Windows 上不进行参数的引用或转义。在 Unix 上被忽略。当指定并且是 CMD时,这会true自动设置。默认:。shellfalse
您应该了解 shell 如何处理空格和引号。我说的是“shell”——有不同的 shell,我不知道它们之间的区别,所以我要写的内容可能不适用于您。有人可以随意编辑它,使其更加精确。
您可以在 shell 命令中包含各种复杂的语法:管道命令、输入和输出文件、插值变量、插值命令、环境变量以及至少 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
Run Code Online (Sandbox Code Playgroud)
在每种情况下,命令都是使用tar参数列表--create, --exclude=/foo/bar,运行可执行文件/foo。
请注意引号的行为,这与我所知道的所有其他语言不同。在大多数语言中,字符串文字完全用一对引号括起来 - 这就是编译器/解释器知道它们从哪里开始和结束的方式。但在 shell 命令中,空格告诉 shell 一个参数在哪里结束,下一个参数在哪里开始。(引用/转义的空格不算在内。)引号的唯一目的是改变某些字符的处理方式。Shell 命令对此非常灵活,因此以下命令也与上面的命令等效:
tar -"-"create --exc'lude=/fo'o/bar /foo
tar --cr'eate' --exclude"="/foo"/bar" /foo
Run Code Online (Sandbox Code Playgroud)
当我说这些命令是等效的时,我的意思是tar可执行文件无法知道调用了哪一个命令。也就是说,不可能编写一个可执行文件mycommand,使得命令mycommand foo将mycommand "foo"不同的输出写入 STDOUT 或 STDERR,或者返回不同的退出代码,或者以其他方式表现不同。
但是,当从 Nodejs 运行 shell 命令时,您不需要使用 shell 功能进行管道传输、流式传输到文件/从文件流式传输、插入变量等,因为如果您愿意,JavaScript 可以处理所有这些内容。因此,当您向 提供参数时spawn,它会绕过这些 shell 功能;它不会对 shell 特殊字符执行任何操作。您只需直接提供参数即可。因此,在下面的示例中,参数之一将为--exclude=/foo/bar baz,这将导致忽略目录中tar调用的文件/目录:bar baz/foo
const tar = spawn('tar', [
'--create', '--gzip',
'--exclude=/foo/bar', '--exclude=/foo/baz', '--exclude=/foo/bar baz',
'/foo'
], { stdio: ['ignore', 'pipe', 'inherit'] });
Run Code Online (Sandbox Code Playgroud)
(尽管很明显,如果您使用 javascript 字符串文字,则可能需要在 javascript 级别转义某些字符。)
我不喜欢 joshuhn 的两个答案。(1) 甚至对我不起作用,我很惊讶它对他有用 - 如果它有效,那么我将其视为 Nodejs 中的错误(或者可能在tar)。(我在 Ubuntu 16.04.3 LTS 中运行 nodejs v6.9.5,使用 GNU tar v1.28。)至于(2),这意味着不必要地将 shell 字符串处理的所有复杂性引入到 JavaScript 代码中。正如文档所说:
注意:如果
shell启用该选项,请勿将未经处理的用户输入传递给此函数。任何包含 shell 元字符的输入都可用于触发任意命令执行。
我不知道 shell 转义的所有复杂性,所以我不会冒险spawn使用shell不受信任的输入选项运行。