使用Node.js遍历目录

时间:2011-08-12 14:33:41

标签: javascript node.js filesystems

我在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);
      });
    });
  });
};

问题在于,每个循环的 通过变量path为每个文件调用stat。调用回调时,path已经有另一个值,因此它dive进入错误的目录或调用action来查找错误的文件。

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

8 个答案:

答案 0 :(得分:14)

var path = dir + "/" + file;

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

答案 1 :(得分:10)

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

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

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);
  })
});

答案 2 :(得分:7)

不确定我是否应该将此作为答案发布,但为方便您和其他用户,这里是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);
};

答案 3 :(得分:7)

另一个合适的库是 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 :(得分:2)

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

答案 5 :(得分:0)

为此有一个NPM模块:

npm dree

它使您可以将目录树表示为字符串或对象以及几个自定义选项。使用其文件回调将使您实现目标。

示例:

const dree = require('dree');
const options = {
    depth: 5,                        // If you want to stop after 5 directory levels
    exclude: /dir_to_exclude/,       // If you want to exclude some pahts with a regexp
    extensions: [ 'txt', 'jpg' ]     // If you want only some extensions
};

const fileCallback = function (file) {
    action(file.path);
};

let tree;

// Do it synchronously
tree = dree.scan('./dir', options, fileCallback);

// Do it asynchronously (returns promise)
tree = await dree.scanAsync('./dir', options, fileCallback);

// Here tree contains an object representing the whole directory tree (filtered with options)

答案 6 :(得分:0)

为此有一个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 :(得分:-2)

function loop( ) {
    var item = list.shift( );
    if ( item ) {
        // content of the loop
        functionWithCallback( loop );
    } else {
        // after the loop has ended
        whatever( );
    }
}