在节点中不区分大小写的文件系统中获取实际文件名的有效方法

gma*_*man 5 javascript filesystems filenames node.js electron

不是这个问题的欺骗。这个问题是不是有关Windows。这是跨操作系统的普遍问题。

除了获取目录并找到匹配的名称之外,是否有一种有效的方法可以在 node.js 中获取文件名的正确大小写?

示例:假设我有一个包含 3 个文件的文件夹

+-someFolder
  +-fooBar.txt
  +-Moo.txt
  +-ReadMe.txt
Run Code Online (Sandbox Code Playgroud)

我想要一个通过somefolder/readme.txtreturn的函数someFolder/ReadMe.txt

AFAICT 唯一的方法是调用fs.readDirfs.readDirSync查看是否有匹配的文件,例如

const fs = require('fs');
const path = require('path');

function getActualFilename(filename) {
  if(!fs.existsSync(filename)) {
    throw new Error(`${filename} does not exist`);
  }
  return getActualFilenameImpl(filename);
}

function getActualFilenameImpl(filename) {
  const lcFilename = path.basename(filename).toLowerCase();
  // handles passing in `c:\\`
  if (!lcFilename) {
    return filename.toUpperCase();
  }

  const dirname = path.dirname(filename);
  let filenames;
  try {
    filenames = fs.readdirSync(dirname);
  } catch (e) {
    // we already verified the path exists above so if this
    // happens it means the OS won't let use get a listing (UNC root on windows)
    // so it's the best we can do
    return filename;
  }
  const matches = filenames.filter(name => lcFilename === name.toLowerCase());
  if (!matches.length) {
    throw new Error(`${filename} does not exist`);
  }

  const realname = matches[0];
  if (dirname !== '.') {
    if (dirname.endsWith('/') || dirname.endsWith('\\')) {
      return path.join(dirname, realname);
    } else {
      return path.join(getActualFilenameImpl(dirname), realname);
    }
  } else {
    return realname;
  }
}
Run Code Online (Sandbox Code Playgroud)

上面的代码非常hacky。尝试不同的事情已经清楚地表明存在很多边缘情况。在 Windows 上,特别是 UNC 路径会失败,因为fs.readdirSync一旦到达网络路径根就无法调用。我不知道要调用哪些函数来确定该路径的分隔位置,然后如何获得正确的 case 路径,这可能是一组完全独立的 Windows API 调用(例如调用net use用于显示共享的任何函数)等。 .

我确实注意到path.dirname当它到达 UNC 路径时停止删除尾部斜杠,因此使用它来尝试确定何时停止尝试。

笔记:

  • 例如,我知道在 Linux(也可以在 Mac 上)文件系统可能区分大小写,我必须检查这一点,但我最关心的是 Windows 和标准 macOS,稍后将处理区分大小写的问题。

  • 我还了解到 JavaScript 的 toLowerCase 可能与操作系统不区分大小写的概念不匹配,因此如果有一个解决方案考虑到这一点,那也很棒!

  • 我知道我可以缓存结果或目录列表以加快速度,但想知道是否有其他一些不读取整个目录列表的函数可以使用。

我实际上正在尝试解决几个问题,并且愿意接受其他建议

问题 1:要在特定于应用程序的数据库中存储什么文件名。似乎最好存储实际的文件名。见#3

问题 2:确定 2 个文件名是否引用同一个文件/文件夹。因此,如果用户指定SomeFolder/foobar.txt并且somefolder/FOOBAR.txt我不希望它们显示为 2 个单独的文件(如果它们实际上是同一个文件)。我需要我的应用程序知道它们引用了同一个文件。我想我可以调用fs.stat这个并检查该ino字段是否匹配?

问题 3:与问题 1 相关,重新加载与文件相关的元数据。如果用户SomeFolder/foobar.txt在某个时候指定并且我的应用程序生成与文件相关的元数据,那么在其他某个时间点他们指定somefolder/FOOBAR.txt我需要找到匹配的元数据。我目前的想法是通过查找实际文件名并使用它来解决这个问题。虽然我想如果他们将文件重命名为FooBar.txtfoobar.txt它会丢失元数据。不确定我是否关心这种情况,因为如果它们重命名为FooBar.txtSomethingElse.txt我绝对不在乎我是否会丢失元数据。

也就是说,也许我应该将 存储ino为我的数据库中的密钥?不确定我对这个想法是否满意,但这是一种可能性,并且很想知道其他人是否这样做。一些检查表明,至少在 macOS 上,ino 在同一驱动器上的移动和重命名之间保持不变,这对我的用例来说是一件好事。另一方面,我认为 ino 仅对每个文件系统有效,因此如果我安装了 2 个不同的驱动器,我可能会遇到 inos 冲突。我可以使用devino作为关键

const stat = fs.statSync(filename);
const key = `${stat.dev}:${stat.ino}`;
Run Code Online (Sandbox Code Playgroud)

虽然我不知道stat.dev可移动存储是否总是相同的。我认为不是。所以看起来文件名作为键可能更好?

kay*_*ahr 0

只要文件系统不保持不同情况下同名文件之间的连接(我不知道任何这样的文件系统),除了扫描目录之外就没有其他解决方案,因为根本没有提供 API为此,在任何层面上都可以做到这一点。

因此,您必须按照您已经建议的那样手动扫描,或者使用glob等库来查找文件,同时忽略大小写。

但你说你在数据库中也有文件名。因此,如果您可以确保数据库中的文件名与文件系统中的文件名完全匹配,那么您应该能够通过执行不区分大小写的数据库查询来找到不同情况下的文件。如果它是 SQL 数据库那么它应该已经提供此功能。如果它是更原始的数据存储,您可以添加另一个始终为小写的文件名属性,以便您可以与之匹配以找到真实的文件。