【问题标题】:Walking a directory with Node.js [duplicate]使用 Node.js 遍历目录 [重复]
【发布时间】:2011-10-25 21:06:02
【问题描述】:

我在 node.js 中的这段代码有问题。我想递归遍历目录树并将回调action 应用于树中的每个文件。这是我目前的代码:

var fs = require("fs");

// General function
var dive = function (dir, action) {
  // Assert that it's a function
  if (typeof action !== "function")
    action = function (error, file) { };

  // Read the directory
  fs.readdir(dir, function (err, list) {
    // Return the error if something went wrong
    if (err)
      return action(err);

    // For every file in the list
    list.forEach(function (file) {
      // Full path of that file
      path = dir + "/" + file;
      // Get the file's stats
      fs.stat(path, function (err, stat) {
        console.log(stat);
        // If the file is a directory
        if (stat && stat.isDirectory())
          // Dive into the directory
          dive(path, action);
        else
          // Call the action
          action(null, path);
      });
    });
  });
};

问题在于,在 for each 循环 中,每个文件都通过变量 path 调用统计信息。当回调被调用时,path 已经有另一个值,因此它 dives 进入错误的目录或调用 action 以获得错误的文件。

使用fs.statSync 可能很容易解决这个问题,但这不是我想要的解决方案,因为它会阻塞进程。

【问题讨论】:

    标签: javascript node.js filesystems


    【解决方案1】:

    var path = dir + "/" + file;

    您忘记将path 设为局部变量。现在它不会在你的背后改变。

    【讨论】:

    • 这可以通过将"use strict"; 放在文件顶部来捕获。
    • 为了跨平台兼容性,使用节点的path.join函数而不是附加一个原始的"/"
    【解决方案2】:

    为此使用node-dir。因为您需要对目录和文件进行单独操作,所以我将为您提供 2 个使用 node-dir 的简单迭代器。

    异步迭代目录及其子目录的文件并将文件路径数组传递给回调。

    var dir = require('node-dir');
    
    dir.files(__dirname, function(err, files) {
      if (err) throw err;
      console.log(files);
      //we have an array of files now, so now we'll iterate that array
      files.forEach(function(filepath) {
        actionOnFile(null, filepath);
      })
    });
    

    异步迭代目录及其子目录的子目录,并将目录路径数组传递给回调。

    var dir = require('node-dir');
    
    dir.subdirs(__dirname, function(err, subdirs) {
      if (err) throw err;
      console.log(subdirs);
      //we have an array of subdirs now, so now we'll iterate that array
      subdirs.forEach(function(filepath) {
        actionOnDir(null, filepath);
      })
    });
    

    【讨论】:

      【解决方案3】:

      另一个合适的库是 filehound。它支持文件过滤(如果需要)、回调和承诺。

      例如:

      const Filehound = require('filehound');
      
      function action(file) {
        console.log(`process ${file}`)
      }
      
      Filehound.create()
      .find((err, files) => {
          if (err) {
              return console.error(`error: ${err}`);
          }
      
          files.forEach(action);
      });
      

      该库文档齐全,并提供了大量常见用例示例。 https://github.com/nspragg/filehound

      免责声明:我是作者。

      【讨论】:

      • 它还能找到文件夹吗?
      【解决方案4】:

      不确定我是否真的应该将其发布为答案,但为了您和其他用户的方便,这里是 OP 的重写版本,它可能会被证明是有用的。它提供:

      • 更好的错误管理支持
      • 探索完成时调用的全局完成回调

      代码:

      /**
       * dir: path to the directory to explore
       * action(file, stat): called on each file or until an error occurs. file: path to the file. stat: stat of the file (retrived by fs.stat)
       * done(err): called one time when the process is complete. err is undifined is everything was ok. the error that stopped the process otherwise
       */
      var walk = function(dir, action, done) {
      
          // this flag will indicate if an error occured (in this case we don't want to go on walking the tree)
          var dead = false;
      
          // this flag will store the number of pending async operations
          var pending = 0;
      
          var fail = function(err) {
              if(!dead) {
                  dead = true;
                  done(err);
              }
          };
      
          var checkSuccess = function() {
              if(!dead && pending == 0) {
                  done();
              }
          };
      
          var performAction = function(file, stat) {
              if(!dead) {
                  try {
                      action(file, stat);
                  }
                  catch(error) {
                      fail(error);
                  }
              }
          };
      
          // this function will recursively explore one directory in the context defined by the variables above
          var dive = function(dir) {
              pending++; // async operation starting after this line
              fs.readdir(dir, function(err, list) {
                  if(!dead) { // if we are already dead, we don't do anything
                      if (err) {
                          fail(err); // if an error occured, let's fail
                      }
                      else { // iterate over the files
                          list.forEach(function(file) {
                              if(!dead) { // if we are already dead, we don't do anything
                                  var path = dir + "/" + file;
                                  pending++; // async operation starting after this line
                                  fs.stat(path, function(err, stat) {
                                      if(!dead) { // if we are already dead, we don't do anything
                                          if (err) {
                                              fail(err); // if an error occured, let's fail
                                          }
                                          else {
                                              if (stat && stat.isDirectory()) {
                                                  dive(path); // it's a directory, let's explore recursively
                                              }
                                              else {
                                                  performAction(path, stat); // it's not a directory, just perform the action
                                              }
                                              pending--; checkSuccess(); // async operation complete
                                          }
                                      }
                                  });
                              }
                          });
                          pending--; checkSuccess(); // async operation complete
                      }
                  }
              });
          };
      
          // start exploration
          dive(dir);
      };
      

      【讨论】:

      • 这太棒了!一个小的优化:在第一次挂起之后你不需要 checkSuccess--。除非你完成了你当前所在的目录,否则你永远不会处于pending==0。
      【解决方案5】:

      不要重新发明轮子 - 改为使用并为开源做出贡献。尝试以下方法之一:

      【讨论】:

      • 我不喜欢那个模块。 This one(这是这个问题的结果)有一个更简单的 api。
      【解决方案6】:

      为此有一个 NPM 模块:

      npm dree

      例子:

      const dree = require('dree');
      const options = {
          depth: 5,                        // To stop after 5 directory levels
          exclude: /dir_to_exclude/,       // To exclude some pahts with a regexp
          extensions: [ 'txt', 'jpg' ]     // To include only some extensions
      };
      
      const fileCallback = function (file) {
          action(file.path);
      };
      
      let tree;
      
      // Doing it synchronously
      tree = dree.scan('./dir', options, fileCallback);
      
      // Doing it asynchronously (returns promise)
      tree = await dree.scanAsync('./dir', options, fileCallback);
      
      // Here tree contains an object representing the whole directory tree (filtered with options)
      

      【讨论】:

        【解决方案7】:
        function loop( ) {
            var item = list.shift( );
            if ( item ) {
                // content of the loop
                functionWithCallback( loop );
            } else {
                // after the loop has ended
                whatever( );
            }
        }
        

        【讨论】:

        • 不清楚你想让我在functionWithCallback(loop);做什么。您能否将您的解决方案应用到我的示例中?
        • 对于列表中的每个元素,您想要执行某项操作,此函数执行此操作,然后调用回调。我将在明天进行编辑以使其更接近您的代码。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-11-04
        • 2021-07-29
        • 1970-01-01
        • 1970-01-01
        • 2019-01-10
        相关资源
        最近更新 更多