Node.js中的引号产生参数

问题描述 投票:3回答:2

我在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'] });

出于某种原因,tar忽略了以这种方式提供的--exclude参数。结果与spawnrequire('child_process').spawnrequire('cross-spawn')相同。

当不需要它们的路径没有双引号时,--exclude按预期工作。

即使使用双引号,同样的东西也可以像shell一样工作:

tar --create --gzip --exclude="/foo/bar" --exclude="/foo/baz" /foo > ./foo.tgz

我不知道那里发生了什么,以及如何调试spawn来检查它是否为双引号做了一些奇怪的转义。

javascript node.js tar spawn
2个回答
5
投票

这是引用类型优先级中的问题。双引号优先于单引号,因此产生的调用会中断。

系统shell将剥离参数周围的引号,因此程序在结尾处获得不带引号的值。在绕过shell时,产生一个进程会绕过这一步,因此程序会将这些文字引号作为参数的一部分,并且不知道如何正确处理它们。

我知道有两个解决这个问题的实际选择:

  1. 这是违反直觉的,但切换报价类型应解决此问题。将上面的代码切换为: const tar = spawn("tar", [ "--create", "--gzip", "--exclude='/foo/bar'", "--exclude='/foo/baz'", "/foo" ], { stdio: ["ignore", "pipe", "inherit"] });
  2. 或者,您可以使用{ shell: true }并使用当前格式。这将通过shell传递spawn请求,因此将发生当前正在跳过的解析步骤。查看有关此here的更多信息。 const tar = spawn('tar', [ '--create', '--gzip', '--exclude="/foo/bar"', '--exclude="/foo/baz"', '/foo' ], { stdio: ['ignore', 'pipe', 'inherit'], shell: true });

3
投票

您应该了解shell如何处理空格和引号。我说“贝壳” - 有不同的贝壳,我不知道它们之间的区别,所以我写的东西可能不适用于你。有人可以自由编辑,以便更精确。

您可以在shell命令中包含各种语法复杂性:管道命令,输入和输出文件,插值变量,插值命令,环境变量以及至少4种(是,四种)不同的引用字符串的方式。但是出于这个问题的目的,我们只是说shell命令是一个命令名,后跟一个(可能是空的)字符串参数列表。命令名称可以是内置命令(cdlssudo等),也可以是可执行文件。或者,换句话说,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

在每种情况下,命令是使用参数列表tar--create--exclude=/foo/bar运行可执行文件/foo

请注意引号的行为,它与我所知道的所有其他语言不同。在大多数语言中,字符串文字完全被一对引号括起来 - 这就是编译器/解释器知道它们开始和结束的位置。但是在shell命令中,空格是告诉shell一个参数结束而下一个参数开始的地方。 (引用/转义的空格不计算。)引号的唯一目的是更改某些字符的处理方式。 Shell命令对此非常灵活,因此以下命令也与上述命令相同:

tar -"-"create --exc'lude=/fo'o/bar /foo
tar --cr'eate' --exclude"="/foo"/bar" /foo

当我说这些命令是等价的时,我的意思是tar可执行文件无法知道哪一个被调用。也就是说,不可能编写可执行文件mycommand,使命令mycommand foomycommand "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'] });

(虽然很明显,如果你使用javascript字符串文字,你可能需要在javascript级别转义一些字符。)

我不喜欢joshuhn的答案。 (1)甚至没有为我工作,我很惊讶它为他工作 - 如果它确实那么我将其视为nodejs中的错误(或可能在tar中)。 (我在Ubuntu 16.04.3 LTS中运行nodejs v6.9.5,使用GNU tar v1.28。)对于(2),它意味着不必要地将shell字符串处理的所有复杂性引入到您的javascript代码中。正如the documentation所说:

注意:如果启用了shell选项,请不要将未经过授权的用户输入传递给此函数。包含shell元字符的任何输入都可用于触发任意命令执行。

我一个人不知道shell逃逸的所有错综复杂,所以我不会冒险使用spawn选项和不受信任的输入运行shell

© www.soinside.com 2019 - 2024. All rights reserved.