【问题标题】:Quotes in Node.js spawn argumentsNode.js 中的引号派生参数
【发布时间】:2018-06-09 10:38:09
【问题描述】:

我在 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 以检查它是否对双引号进行了一些奇怪的转义。

【问题讨论】:

  • 我看不懂 C 语言,但您可以提供 { shell: true } 作为选项让 shell 处理它。它还为引号提供规范化。

标签: javascript node.js tar spawn


【解决方案1】:

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

系统 shell 会去掉参数周围的引号,所以程序最后会得到未加引号的值。生成一个进程绕过了这一步,因为它绕过了 shell,因此程序将这些文字引号作为参数的一部分获取,并且不知道如何适当地处理它们。

据我所知,有两个真正的选择可以解决这个问题:

  1. 这是违反直觉的,但切换引用类型应该可以解决此问题。将上面的代码切换为:

    const tar = spawn("tar", [
      "--create", "--gzip",
      "--exclude='/foo/bar'", "--exclude='/foo/baz'", "/foo"
    ], { stdio: ["ignore", "pipe", "inherit"] });
    
  2. 或者,您可以使用{ shell: true } 并使用您当前的格式。这将通过 shell 传递生成请求,因此将发生当前正在跳过的解析步骤。查看更多有关此here 的信息。

    const tar = spawn('tar', [
      '--create', '--gzip',
      '--exclude="/foo/bar"', '--exclude="/foo/baz"', '/foo'
    ], { stdio: ['ignore', 'pipe', 'inherit'], shell: true });
    

【讨论】:

  • 谢谢。我已经尝试过单引号,它们给出了类似的结果。而 shell:true 选项确实对我有用。仍然不确定在没有它的情况下调用 tar 时 args 会发生什么。
  • 非常感谢,rsync --exclude '.git' [...] 遇到了同样的问题。 shell:true 是解决方案。
【解决方案2】:

您应该了解 shell 如何处理空格和引号。我说的是“外壳”——有不同的外壳,我不知道它们之间的区别,所以我要写的可能不适用于你。有人可以随意编辑它,让它更精确。

您可以在 shell 命令中包含各种复杂的语法:管道命令、输入和输出文件、内插变量、内插命令、环境变量,以及至少 4 种(是的,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

在每种情况下,命令都是运行带有参数列表--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 foomycommand "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

【讨论】:

  • 感谢您的详细解释。但是,目前尚不清楚您建议的解决方案是什么。我不能直接提供参数,因为它们可能包含空格。我更新了问题以表明他们确实可以。
  • 空格没有错。在 shell 命令中,如果您想要文字空格,则必须使用引号,因为没有引号的空格意味着它是参数的结尾。但是在spawn 中,参数已经被拆分了;无需说明哪些空格将被视为文字,因为它们都是。我已经更新了答案,在其中一个参数中包含了一个空格。
【解决方案3】:

如果我理解您的要求,您只想保留默认的 shell 行为,即去掉引号并将参数作为单个参数传递,即使它有空格。

在这种情况下,你可以这样做:

spawn(exe, args, { windowsVerbatimArguments: true });

docs:

windowsVerbatimArguments <boolean> 在 Windows 上不会引用或转义参数。在 Unix 上被忽略。当指定 shell 并且是 CMD 时,它会自动设置为 true默认: false.

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-06-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-07-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多