为什么在Node REPL中__dirname未定义?

278

从Node的手册中我看到可以使用__dirname获取文件的目录,但在REPL中似乎未定义。这是我的误解还是出现了错误?

$ node
> console.log(__dirname)
ReferenceError: __dirname is not defined
    at repl:1:14
    at REPLServer.eval (repl.js:80:21)
    at Interface.<anonymous> (repl.js:182:12)
    at Interface.emit (events.js:67:17)
    at Interface._onLine (readline.js:162:10)
    at Interface._line (readline.js:426:8)
    at Interface._ttyWrite (readline.js:603:14)
    at ReadStream.<anonymous> (readline.js:82:12)
    at ReadStream.emit (events.js:88:20)
    at ReadStream._emitKey (tty.js:320:10)

32
在使用--experimental-modules标志调用node时,__dirname__filename也不可用。 - Dan Dascalescu
3
我在使用eslint时遇到了问题,我错误地在我的.eslintrc.json文件中将"browser": true设置为"node": true"。 - Toivo Säwén
如果什么都不管用,这是一个hack。https://dev59.com/GGoy5IYBdhLWcg3wCpoz#49879107 - Pawel
12个回答

304

如果您正在使用Node.js模块,则不存在__dirname__filename

根据Node.js文档

没有require、exports、module.exports、__filename、__dirname

这些CommonJS变量在ES模块中不可用。

可以通过module.createRequire()require导入到ES模块中。

可以通过import.meta.url在每个文件内创建__filename__dirname的等效项:

import { fileURLToPath } from 'url';
import { dirname } from 'path';

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

https://nodejs.org/docs/latest-v15.x/api/esm.html#esm_no_filename_or_dirname


39
从技术角度来看,这是唯一正确的答案。所有其他解决方案都使用当前工作目录,而不是__dirname特性,它实际上是保存当前运行脚本的文件夹。 - Ravi Luthra
5
这是针对此问题的完美答案。谢谢,它对我有很大帮助! @RaviLuthra 是100%正确的。 - Karlsson
3
Concurr这是唯一正确的答案,如果问题涉及ES6模块类型的架构。导入语句需要引用路径,最终传递到__dirname,这实际上是保存当前运行脚本的文件夹的功能。 - Bruno Miyamotto Luque
1
当使用cat script.js | node --input-type module命令时,显然import.meta.url是未定义的。 - Saber Hayati
1
REPL上下文不是ES模块。这个答案完全没有回答原问题的重点。正如@SaberHayati所提到的,import.meta在REPL上下文中是无法访问的,而且这个答案根本不起作用。我不知道为什么它会得到这么多赞。 - martian17

233

__dirname 在脚本中定义,REPL 中不可用。

尝试创建一个名为 a.js 的脚本。

console.log(__dirname);

并运行它:

node a.js

你会看到打印出__dirname

添加背景说明:__dirname表示“此脚本的目录”。在REPL中,您没有脚本。因此,__dirname将没有任何实际意义。


10
在RequireJS模块中,您不能使用某些全局变量。如果您在服务器端使用RequireJS,请参考 https://dev59.com/9V_Va4cB1Zd3GeqPWb0l。将该链接复制到浏览器中以获取更多信息。 - esengineer
2
是的,这确实应该添加到答案中,因为那就是让我困惑的地方。 - Tomáš Zato
4
不在 REPL 的加载脚本中添加这个是很恶心的。我想不出任何理由它不会在那里... - jcollum
1
我在REPL中使用.load script.js加载了一个脚本文件。遗憾的是,在script.js中仍然无法使用__dirname。 - Kevin Wheeler
点赞!这让我省了15分钟,因为我一直在想到底发生了什么。 - PirateApp
这只是重申问题而没有回答它。 - Post Self

170

如果您使用 nesh,您可以将其定义为加载脚本的一部分;这很方便。 - jcollum
2
__dirname = process.cwd() 或者 __dirname=fs.realpathSync('.') 或者 __dirname = process.env.PWD - Jthorpe
1
“path.dirname” 在最新的主要版本“6.0.0”中似乎不再接受非字符串值,因此此答案中的第一个建议将无法工作。 - trysis

69

在 ES6 中使用:

import path from 'path';
const __dirname = path.resolve();

当使用--experimental-modules参数调用node时也可用。


在 REPL 中,您不需要导入核心模块;它会为您动态加载它们。 - c24w
这会给出当前工作目录,而不是当前.js文件的目录。 - Dirbaio
@Dirbaio,在 REPL 中,当你在哪个当前的 .js 文件? - c24w
@Dirbaio 我想那就是我的观点。当您进入REPL时,没有当前的JS文件。如果不是CWD,您希望__dirname是什么? - c24w
28
这是错误的。__dirname 应该是当前模块的目录,但您的解决方案使其成为当前工作目录。这个荒谬的错误如何获得那么多赞我无法理解。 - Tomáš Zato
显示剩余2条评论

21

-> 在ES6版本中使用:

import path from "path"
const __dirname = path.resolve();

-> 例如,可以这样使用:

res.sendFile(path.join(__dirname ,'views','shop.html'))

11
这不等同于 __dirname,而是等同于 process.cwd()。例如,当从不同的目录运行脚本时,将会得到不同的结果。在使用ESM时,更好的方法是使用import.meta.url。请参见 https://nodejs.org/docs/latest-v14.x/api/esm.html#esm_import_meta 和 https://dev59.com/DlYN5IYBdhLWcg3w4br1。 - jacobq
这在我的 Docker 里运行成功。谢谢。 - Hayyaun
它能在浏览器上使用吗? - undefined

7

正如@qiao所说,您不能在Node repl中使用__dirname。然而,如果您需要在控制台中使用此值,可以使用path.resolve()path.dirname()。尽管path.dirname()只会给你一个".",所以可能没有那么有帮助。确保require('path')


7

我之前尝试使用 path.join(__dirname, 'access.log') 加入路径,但是出现了相同的错误。

以下是我的解决方法:

我首先导入了path包,并声明了一个名为__dirname的变量,然后调用resolve path方法。

在CommonJS中

var path = require("path");

var __dirname = path.resolve();

在ES6+中

import path  from 'path';

const __dirname = path.resolve();

愉快的编码......


1
我正在使用 Node v15.3.0,不需要使用 path.resolve()__dirname 可以直接使用。你的回答是否仍适用于 ES6+? - Zac
@Zac __dirname 只在使用 CommonJS 的 Node 14 或更高版本中可用。但是 ECMA 正在推动 ES 模块成为标准,而不是 CommonJS。 - Juha Untinen

4
如果你在使用node --experimental-modules时遇到了node __dirname not defined的问题,你可以这样解决:
const __dirname = path.dirname(import.meta.url)
                      .replace(/^file:\/\/\//, '') // can be usefull

例如,该示例仅适用于当前/ pwd 目录,而不适用于其他目录。


3

我曾以SYSTEM用户身份从批处理文件运行脚本,所有变量,如process.cwd()path.resolve()和所有其他方法都会给我C:\Windows\System32文件夹的路径,而不是实际路径。在实验过程中,我注意到当抛出错误时,堆栈包含了node文件的真实路径。

以下是一种非常“hacky”的方法,通过触发错误并从e.stack中提取路径来获取真实路径。请勿使用。

// this should be the name of currently executed file
const currentFilename = 'index.js';

function veryHackyGetFolder() {
  try {
    throw new Error();
  } catch(e) {
    const fullMsg = e.stack.toString();
    const beginning = fullMsg.indexOf('file:///') + 8;
    const end = fullMsg.indexOf('\/' + currentFilename);
    const dir = fullMsg.substr(beginning, end - beginning).replace(/\//g, '\\');
    return dir;
  }
}

用法

const dir = veryHackyGetFolder();

如果您不是通过 REPL 运行此代码,您可以使用 __dirname__filename - c24w
1
这是唯一一个至少试图提供正确解决方案的答案。 - Tomáš Zato
@c24w 我已经尝试了__dirname、__filename和其他一切方法,但在我的情况下,没有标准解决方案有效。如果你感到无聊,这是我的项目,它可以从任务计划程序中作为系统运行进程:github.com/DVLP/Unscheduler。任何其他可行的标准解决方案都比我上面的hack更好 :) - Pawel

3

看起来你也可以这样做:

__dirname=fs.realpathSync('.');

当然,不要忘记 fs=require('fs')
(在Node脚本中,它并不是全局的,只是在模块级别上被定义。)

1
在 REPL 中,您不需要要求核心模块;它会为您动态加载它们。 - c24w

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