使用nodejs的spawn会导致“unknown option --”和“[Error: spawn ENOENT]”错误。

34

我正在尝试让spawn执行rm -rf node_modules命令,然后执行npm install命令(在Windows 7上;nx命令是通过透明安装的CygWin获得的。所有nx命令在命令行中都能正常解析)。

一开始我使用exec来实现这个功能,但想捕捉stdout/stderr信息,所以决定使用spawn并重写代码。然而,这破坏了一切。

rm命令被重写成这样:

var spawn = require("child_process").spawn,
    child = spawn("rm", ["-rf", "node_modules"]);
child.stdout.on('data', function (data) { console.log(data.toString()); });
child.stderr.on('data', function (data) { console.log(data.toString()); });
child.on('error', function() { console.log(arguments); });

然而,运行此代码会生成以下错误:

rm: unknown option -- ,

Try `rm --help' for more information.

npm命令被重写后,变成了这样:

var spawn = require("child_process").spawn,
    child = spawn("npm", ["install"]);
child.stdout.on('data', function (data) { console.log(data.toString()); });
child.stderr.on('data', function (data) { console.log(data.toString()); });
child.on('error', function() { console.log(arguments); });

然而,运行此代码会生成以下错误:

{
  '0': {
    [Error: spawn ENOENT]
    code: 'ENOENT',
    errno: 'ENOENT',
    syscall: 'spawn'
  }
}

如何使 spawn 运行与使用 exec 相同的命令,而不会出现错误?为什么这样做不起作用?阅读API http://nodejs.org/api/child_process.html#child_process_child_process_spawn_command_args_options,似乎表明这正是应该使用 spawn 的方式...

3个回答

72

经过尝试各种不同的方法后,我终于查看了Windows上“npm”实际是什么,结果发现它是一个称为npm的bash脚本,以及一个称为npm.cmd的Windows本地批处理脚本(不知道为什么是“.cmd”,应该是“.bat”,但就是这样)。Windows的命令解析器会看到npm,注意到它不是可执行文件,然后看到npm.cmd,然后注意到它是可执行文件,然后就会使用它。这在终端中很有用,但是spawn()不会进行任何这样的解析:将npm传递给它会导致失败,因为它不是可执行文件。然而,将npm.cmd作为命令传递给它却可以正常工作。

(另外,之前不确定为什么rm失败了,因为它实际上没有任何改变就可以正确工作。可能是误读为问题的一部分,而实际上并不是。)

所以:如果你在Windows中遇到spawn显示ENOENT时,当你尝试触发命令时,在纯命令提示符中工作,请查找你调用的命令是否是真正的可执行文件,或者是否有一个.bat/.cmd文件, 命令提示符会“乐意地”为您运行它。如果是这样,请使用.bat/.cmd文件作为参数传递给spawn

编辑

由于这篇文章仍在得到赞,一种确保命令始终有效的好方法是基于process.platform进行引导,对于Windows来说,它将是win32

var npm = (process.platform === "win32" ? "npm.cmd" : "npm"),
    child = spawn(npm, ["install", ...]);
...

针对触发此错误的特定使用情况进行编辑

自发布这个问题(以及它的答案)以来,已经发布了几个包,允许在不依赖exec或spawn的情况下运行npm任务,你应该使用它们。

可能最受欢迎的是npm-run-all,它不仅可以让你从其他npm脚本以及Node中运行任何npm任务,还可以添加命令以按顺序或并行运行多个npm脚本,带或不带通配符。

在原始问题的上下文中,错误被引发是因为我试图将npm作为exec/spawn来清理并重新安装,现代解决方案是在package.json中有一个专门的清理任务:

{
  ...
  "scripts": {
    "clean": "rimraf ./node_modules",
    ...
  },
  ...
}

接下来在命令行中调用clean任务,然后再执行安装命令。

> npm run clean && npm install

或者,在某些Node脚本中,可以使用以下方式:

const runAll = require("npm-run-all");
...
runAll(["clean", "install"])
    .then(() => {
        console.log("done!");
    })
    .catch(err => {
        console.log("failed!");
    });
< p >(或者当然,作为一个包含在package.js中的复合脚本,例如"redo": "run-s clean install",然后使用runAll(["redo"])


2
一个 .cmd 文件几乎是相同的东西,但并不完全相同。 - tadman
1
你本来可以使用 exec("npm install ..."),它在 Windows 上是可行的,而 spawn 则会失败。尽管 exec 不会提供实时的 stdout/stderr 数据(据我所知)。 - huysentruitw
2
无法实现;正如您所指出的,exec没有stdout和stderr处理功能,而没有日志的npm安装几乎是无用的。 - Mike 'Pomax' Kamermans
4
在Windows上尝试执行child = spawn('cmd', ['/c', 'rm -rf node_modules'], {env:process.env}); - Nick Sotiros
1
@Mike'Pomax'Kamermans,这非常有用,让我困惑了几个小时。太糟糕了,npm的人们决定故意不支持API... - Lucas
显示剩余7条评论

-1

我认为这可能是某种Cygwin陷阱。我正在运行Ubuntu 12.04,尝试复制您的问题,但对我来说一切都很正常。简而言之,我没有看到您做错了什么。

如果它抱怨选项,也许可以将其拆分为多个选项,如下所示:

child = spawn("rm", ["-r", "-f", "node_modules"]);

这有点像是一种绝望的尝试,但在我的Ubuntu 12.04上也可以工作。您可以尝试仅删除一个文件并查看是否会出现相同的情况。

child = spawn("rm", ["/home/username/Desktop/TestFile"]);

如果仍然失败,那么你知道你正在与一些疯狂的东西打交道。
您甚至可以尝试仅执行没有参数的命令,如下所示:
child = spawn("ls");

如果仍然失败,我猜你可能根本无法让spawn工作,并且感激至少exec正在工作。

对于你来说,答案不多,但是像我说的那样,我看不出你做错了什么。

此外,我不知道你的npm命令如何工作,因为你没有指定要安装什么,但是话虽如此,如果我使用相同的命令,它会以不同的方式失败。. . 我看到很多stderr输出,而不是总体错误。

顺便说一句,我正在运行node v0.8.21。您可以通过node -v查询。如果您运行另一个版本,请尝试使用0.8.21。


结果证明,这比那更有趣。添加了一个答案来解释实际发生的事情。 - Mike 'Pomax' Kamermans
我不运行Cygwin,所以不确定它是什么可执行文件,但exec或spawn需要先运行Cygwin,然后Cygwin才运行'rm'。 - Nick Sotiros

-2

使用完整路径来运行进程,例如:

var cmd = require('child_process').spawn("C:\\windows\\system32\\cmd.exe");

2
只有在您已经知道可执行文件的扩展名和位置时,才能使用该方法。硬编码这些信息会立即使您的项目无法在其他人的计算机上运行,更不用说跨平台了。 - Mike 'Pomax' Kamermans

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