如何在Node.js中调试"Error: spawn ENOENT"?

531

当我收到以下错误时:

events.js:72
        throw er; // Unhandled 'error' event
              ^
Error: spawn ENOENT
    at errnoException (child_process.js:1000:11)
    at Process.ChildProcess._handle.onexit (child_process.js:791:34)

我该按照哪个流程来修复它?

作者注:这个错误的许多问题促使我发布这个问题以供将来参考。

相关问题:


16
在我的情况下,我把整个命令作为字符串传递,就像使用exec一样,而不是将命令作为第一个参数和选项作为第二个参数的数组进行传递。例如,我使用的是spawn("adb logcat -c")而不是spawn("adb", ["logcat", "-c"]) - Joshua Pinter
这对我有用:https://dev59.com/E7Lma4cB1Zd3GeqPZVdF#65008091 - Ank_247shbm
在 VS Code 中,卸载了以下的 Linter 扩展后,它就可以工作了:marketplace.visualstudio.com/items?itemName=fnando.linter。请参见 https://github.com/yarnpkg/yarn/issues/5275#issuecomment-1250079916。 - Manohar Reddy Poreddy
再次,环境中缺少依赖项导致问题。 - laconbass
32个回答

303

注意:这个错误几乎总是由于命令不存在、工作目录不存在或者Windows独有的bug引起。

我发现一种特别简单的方式来了解根本原因:

Error: spawn ENOENT
这个错误的问题在于,错误信息中几乎没有足够的信息告诉你调用点在哪里,也就是未找到哪个可执行文件或命令,尤其是当你有很多 spawn 调用的代码库时。另一方面,如果我们知道导致错误的确切命令,那么我们可以按照@laconbass 的答案来解决这个问题。
我发现了一个非常简单的方法来定位是哪个命令导致了问题,而不是像 @laconbass 的答案建议的那样在代码中添加事件监听器。关键思路是使用一个包装器(wrapper)来包裹原始的 spawn 调用,并打印发送到 spawn 调用的参数。
以下是包装器函数,在你的 index.js 或任何服务器启动脚本的顶部放置它即可。
(function() {
    var childProcess = require("child_process");
    var oldSpawn = childProcess.spawn;
    function mySpawn() {
        console.log('spawn called');
        console.log(arguments);
        var result = oldSpawn.apply(this, arguments);
        return result;
    }
    childProcess.spawn = mySpawn;
})();

那么下次运行您的应用程序时,在未捕获异常的消息之前,您将看到类似于以下内容:

接着是需要翻译的内容:

spawn called
{ '0': 'hg',
  '1': [],
  '2':
   { cwd: '/* omitted */',
     env: { IP: '0.0.0.0' },
     args: [] } }

通过这种方式,您可以轻松知道实际执行的命令,然后找出为什么nodejs找不到可执行文件以解决问题。


5
这里有另一个想法:只需将spawn()更改为exec(),然后再试一次。 exec()会告诉您它尝试运行的命令。 - Adam Monsen
1
重要提示:请确保将以下代码尽可能放在主JS文件的开头。如果您先加载其他模块,它们可能会藏起“spawn”函数,并且这里的覆盖将永远不会被调用。 - Dan Nissenbaum
4
我运行脚本没有成功,它根本不起作用。 - newguy
2
这对我来说完美地运作了。我只是把它放在我的gulpfile.js文件的顶部,然后就有了生成日志! - Yann Duran
嗯...对我没用。我把它放在了gulpfile.js的顶部。 - demisx
显示剩余5条评论

158

步骤1:确保正确调用了spawn

首先,请查阅child_process.spawn( command, args, options )的文档

使用给定的command启动一个新进程,并使用args作为命令行参数。如果省略args,则默认为空数组。

第三个参数用于指定其他选项,默认为:

{ cwd: undefined, env: process.env }

使用env来指定将对新进程可见的环境变量,其默认值为process.env

确保您没有将任何命令行参数放在command中,并且整个spawn调用是有效的。继续下一步。

步骤2:确定发出错误事件的事件发射器

在源代码中搜索每个spawnchild_process.spawn的调用,例如:

spawn('some-command', [ '--help' ]);

并附加一个事件监听器来捕捉 'error' 事件,这样你就能知道抛出它的确切事件发射器是什么,并将其标记为“未处理”。调试后,该处理程序可以被删除。

spawn('some-command', [ '--help' ])
  .on('error', function( err ){ throw err })
;

执行后,您应该能够获取注册了“error”监听器的文件路径和行号。类似于:

/file/that/registers/the/error/listener.js:29
      throw err;
            ^
Error: spawn ENOENT
    at errnoException (child_process.js:1000:11)
    at Process.ChildProcess._handle.onexit (child_process.js:791:34)

如果前两行仍然存在

events.js:72
        throw er; // Unhandled 'error' event

重复执行此步骤,直到它们不再出现。 在进行下一步之前,您必须确定发出错误的监听器。

步骤3:确保环境变量$PATH已设置

有两种可能的情况:

  1. 您依赖于默认的spawn行为,因此子进程环境将与process.env相同。
  2. 您正在显式地向options参数上的spawn传递一个env对象。

在这两种情况下,您必须检查生成的子进程将使用的环境对象上的PATH键。

情况1的示例

// inspect the PATH key on process.env
console.log( process.env.PATH );
spawn('some-command', ['--help']);

场景2的示例

var env = getEnvKeyValuePairsSomeHow();
// inspect the PATH key on the env object
console.log( env.PATH );
spawn('some-command', ['--help'], { env: env });

如果缺少PATH(即未定义),将导致spawn发出ENOENT错误,因为除非命令是可执行文件的绝对路径,否则无法定位任何command

当正确设置PATH后,请继续下一步。它应该是一个目录或目录列表。最后一种情况是常见的。

步骤4:确保command存在于PATH中定义的其中一个目录中

如果文件名command(即'some-command')在PATH定义的至少一个目录中不存在,则spawn可能会发出ENOENT错误。

确定command的确切位置。在大多数Linux发行版中,可以使用which命令在终端上完成。它会告诉你可执行文件的绝对路径(如上所示),或者告诉你找不到它。

在找到命令时,which的示例用法及其输出

> which some-command
some-command is /usr/bin/some-command

当使用 which 命令时,若未找到该命令,则会输出以下内容:

> which some-command
bash: type: some-command: not found

未正确安装的程序是“未找到”命令最常见的原因。如有需要,请参考每个命令的文档并将其安装。

当命令是一个简单的脚本文件时,请确保它可以从路径(PATH)中的目录访问。如果不能,要么将它移动到一个目录下,要么创建一个链接。

一旦确定PATH已正确设置,并且可以从PATH中访问command,则应该能够生成子进程而不会抛出spawn ENOENT异常。


1
这对我调试Spawn ENOENT非常有帮助。我已经多次参考过它。谢谢! - CodeManiak
48
如果在选项中指定了“cwd”(当前工作目录),但所给定的目录不存在,也会抛出ENOENT错误。 - Daniel Imfeld
4
@DanielImfeld 全能救世主。请撰写这样的答案。 - GreenAsJade
4
当你按照这个答案中的第三步示例使用 spawn('some-command', ['--help'], { env: env }); 并传递自定义环境时,请确保指定 PATH,例如:{ env: { PATH: process.env.PATH } }。默认情况下,env选项不会从当前环境继承变量。 - anty
14
我通过将 shell: true 传递给生成选项来解决了我的问题。 - Nickofthyme
显示剩余17条评论

98
只需添加shell: true选项即可解决我的问题:
以下代码在Windows上有错误:
const { spawn } = require('child_process');
const child = spawn('dir');

解决方法:
const { spawn } = require('child_process');
const child = spawn('dir', [], {shell: true});

基于Node.js文档(感谢@Boris的评论):
请注意:
如果启用了shell选项,请勿将未经过滤的用户输入传递给此函数。任何包含shell元字符的输入都可能被用于触发任意命令执行。

6
谢谢!这解决了我的问题,不需要定义cmd或path。 - bFunc
在 MacOS 上添加 { shell: true } 也可以起作用。 - Topher Fangio
终于在一天之后解决了我的问题!我一直在使用Windows操作系统,这个方法解决了我的问题,非常感谢。 - MegaBytten
3
请注意,如果启用了 shell 选项,请勿将未经过滤的用户输入传递给此函数。任何包含 shell 元字符的输入都可能被用于触发任意命令执行。 - user3064538
1
这就是解决我的问题的方法,在Windows上无法找到eslint...非常感谢:) - pesho hristov
1
请注意,这是仅限于Windows的特殊情况。在其他情况下可能会抛出ENOENT错误。 - laconbass

46

正如@DanielImfeld所指出的,如果在选项中指定了"cwd",但给定的目录不存在,则会抛出ENOENT错误。


1
有没有一种方法可以在特定目录中执行命令? - Mitro
在Windows(7)中,似乎您还需要在cwd路径中包括驱动器号:'c:/...'而不仅仅是'/...'。 - Museful

33

Windows 解决方案:用 node-cross-spawn 替换 spawn。例如,在你的 app.js 开头添加以下代码:

(function() {
    var childProcess = require("child_process");
    childProcess.spawn = require('cross-spawn');
})(); 

3
除了是一个可以直接使用的替代品,不需要使用 child_process,其余都是按照 node 的 spawn 或 spawnSync 完全相同的方式工作。 var spawn = require('cross-spawn');// 异步地生成 NPM 进程 var child = spawn('npm', ['list', '-g', '-depth', '0'], { stdio: 'inherit' }); - Bogdan Trusca

32

针对 Windows 上的 ENOENT,请参考https://github.com/nodejs/node-v0.x-archive/issues/2318#issuecomment-249355505 进行修复。

例如,将 spawn('npm', ['-v'], {stdio: 'inherit'}) 替换为:

  • 对于所有 node.js 版本:

    spawn(/^win/.test(process.platform) ? 'npm.cmd' : 'npm', ['-v'], {stdio: 'inherit'})
    
  • 适用于 node.js 5.x 及以上版本:

  • spawn('npm', ['-v'], {stdio: 'inherit', shell: true})
    

2
这些修改应该在哪里进行? - Deilan
13
重点是添加 shell: true - Ted Nyberg

31

@laconbass的答案对我有所帮助,可能是最正确的。

我来这里是因为我错误地使用了spawn。以下是一个简单的例子:

这是错误的:

const s = cp.spawn('npm install -D suman', [], {
    cwd: root
});

这是不正确的:

const s = cp.spawn('npm', ['install -D suman'], {
    cwd: root
});

这是正确的:

const s = cp.spawn('npm', ['install','-D','suman'], {
    cwd: root
});

不过,我建议这样做:

const s = cp.spawn('bash');
s.stdin.end(`cd "${root}" && npm install -D suman`);
s.once('exit', code => {
   // exit
});

这是因为在安装了bash的情况下,cp.on('exit', fn)事件将会一直触发,否则如果我们使用第一种方法并直接启动“npm”,cp.on('error', fn)事件可能会先被触发。


1
考虑重构我的答案,提供一个“通用指南”,并将细节留给问题的每个原因(缺少依赖项、不正确的调用、错误的环境等)。 - laconbass
2
所有喜欢这个答案的人,可能也对这个本地替代方案感兴趣:https://gist.github.com/ORESoftware/7bf225f0045b4649de6848f1ea5def4c - Alexander Mills
3
因为您想要的是一个 shell,所以被 downvote 了。您应该使用 child_process.exec 或将 shell: true 传递给 spawn - givanse
1
@givanse 不一定是真的 - 你可能想要运行 zsh 或 bash 或 fsh,这取决于你想使用哪个 shell,并且它们的行为也不同。 - Alexander Mills

24

如何查找引发错误的spawn调用

已知常见原因

  1. 环境问题

    • 系统中不存在命令可执行文件(未安装依赖)。参见prominc的回答
    • 命令可执行文件不存在于PATH环境变量指定的目录之一。
    • 可执行二进制文件与不兼容的库编译。 参见danilo-ramirez的回答
  2. 仅限Windows的错误/怪异行为

  • 错误的spawn('command', ['--argument', 'list'], { cwd, env, ...opts })用法

    • 指定的工作目录(opts.cwd)不存在 · 参见leeroy-brun's answer
    • 命令字符串中的参数列表应当为Array类型,而非String类型 spawn('command --wrong --argument list')
    • 命令字符串中的环境变量应当设置为env参数对象中的键值对,而非直接在命令字符串中指定 spawn('ENV_VAR=WRONG command')
    • String类型指定的参数列表应当改为Array类型 spawn('cmd', '--argument list')
    • 未设置PATH环境变量 spawn('cmd', [], { env: { variable } } => spawn('cmd', [], { env: { ...process.env, variable } }

  • ENOENT错误的两种可能来源:

    1. 你正在编写的代码
    2. 你依赖的代码

    如果错误来源于你依赖的代码,通常是环境问题(或者Windows上的怪异问题)



    1
    我正在使用"ab"命令执行execa,但容器中没有安装"Apache Bench"...因此,这是第一个"环境问题"案例... - Marcello DeSales

    22

    对于可能会遇到此问题的任何人,如果所有其他答案都没有帮助并且您正在使用Windows,请知道当前存在一个与Windows上的spawn相关的大问题PATHEXT环境变量,这可能会导致某些调用spawn无法正常工作,具体取决于目标命令的安装方式。


    2
    那么解决方案是什么? - Nilzor
    6
    对我来说使用node-cross-spawn很有效。请参见以下答案:https://dev59.com/SV4c5IYBdhLWcg3wqLte#35561971 - Nilzor
    2
    我花了很长时间来找问题所在,结果发现这就是问题所在。我放弃了 spawn,改用 exec - reduckted

    11
    在我的情况下,由于所需的依赖系统资源未安装,导致出现此错误。
    更具体地说,我有一个使用ImageMagick的NodeJS应用程序。尽管已经安装了npm包,但核心Linux ImageMagick并未安装。我运行了apt-get来安装ImageMagick,之后一切正常工作!

    Windows需要安装ImageMagick吗?我在Windows上进行测试时出现错误。 - Somename
    运行 yarn(进行安装)解决了这个问题。 - Gal Bracha
    只需运行 brew install imagemagick - Paul

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