芽萌丸プログラミング部 @programming
@programming
2020/6/23 15:15 更新✏

Node.js: 指定フォルダ配下のファイルを再帰的に取得

今回は3つの関数を用意しました。

パラメータ

サンプルコード

同期版

/**
 * Usage:
 * $ node walkSync.js ~/path/to/find/ .js,.json
 */
const dir = process.argv[2]; // 検索フォルダ
const ext = process.argv[3]; // 拡張子(カンマ区切りOK)
const fs = require('fs');
const path = require('path');

/**
 * 指定したフォルダ配下のファイルのパスを取得 (同期版)
 * 
 * @param  {String} dir - このパスの配下を検索
 * @param  {String|Array<String>} suffix - (option) ファイル名のサフィックス(拡張子とかを想定)
 * @return {Array<String>} ヒットしたファイルのフルパスの一覧
 */
function walkSync(dir, suffix) {
  let results = [];
  let list = fs.readdirSync(dir);
  list.forEach(function(file) {
    file = path.resolve(dir, file);
    let stat = fs.statSync(file);
    if (stat && stat.isDirectory()) {
      /* Recurse into a subdirectory */
      results = results.concat(walkSync(file, suffix));
    } else {
      // NOTE: append files with specified suffix:
      if (!suffix || suffix.length <= 0 || _hasSuffix(file, suffix)) {
        results.push(file);
      }
    }
  });
  return results;

  function _hasSuffix(filename, list) {
    if (typeof list === "string") {
      return filename.endsWith(list);
    } else if (Array.isArray(list)) {
      for (let len = list.length, i = 0; i < len; i++) {
        const suffix = list[i];
        if (filename.endsWith(suffix)) {
          return true;
        }
      }
    }
    return false;
  }
}

console.log("done:", walkSync(dir, ext.split(",")));

/*
$ node walkSync.js /home/hoge/test/ .js
done: [
  '/home/hoge/test/app.js',
  '/home/hoge/test/walk.js',
  '/home/hoge/test/walks.js',
  '/home/hoge/test/walkSync.js'
]
 */

非同期版 (並行処理)

/**
 * Usage:
 * $ node walk.js ~/path/to/find/ .js,.json
 */
const dir = process.argv[2]; // 検索フォルダ
const ext = process.argv[3]; // 拡張子(カンマ区切りOK)
const fs = require('fs');
const path = require('path');

/**
 * 指定したフォルダ配下のファイルのパスを取得 (非同期版)
 * 
 * ※並行(パラレル)に処理します。
 * 
 * @param  {String} dir - このパスの配下を検索
 * @param  {String|Array<String>} suffix - (option) ファイル名のサフィックス(拡張子とかを想定)
 * @return {Array<String>} ヒットしたファイルのフルパスの一覧
 */

function walk(dir, suffix, done) {
  let results = [];
  fs.readdir(dir, function(err, list) {
    if (err) return done(err);
    let pending = list.length;
    if (!pending) return done(null, results);
    list.forEach(function(file) {
      file = path.resolve(dir, file);
      fs.stat(file, function(err, stat) {
        if (stat && stat.isDirectory()) {
          walk(file, suffix, function(err, res) {
            results = results.concat(res);
            if (!--pending) done(null, results);
          });
        } else {
          // NOTE: append files with specified suffix:
          if (!suffix || suffix.length <= 0 || _hasSuffix(file, suffix)) {
            results.push(file);
          }
          if (!--pending) done(null, results);
        }
      });
    });
  });

  function _hasSuffix(filename, list) {
    if (typeof list === "string") {
      return filename.endsWith(list);
    } else if (Array.isArray(list)) {
      for (let len = list.length, i = 0; i < len; i++) {
        const suffix = list[i];
        if (filename.endsWith(suffix)) {
          return true;
        }
      }
    }
    return false;
  }
}

walk(dir, ext.split(","), (err, res) => {
  console.log("done:", res);
});

/*
$ node walk.js /home/hoge/test/ .js
done: [
  '/home/hoge/test/app.js',
  '/home/hoge/test/walk.js',
  '/home/hoge/test/walks.js',
  '/home/hoge/test/walkSync.js'
]
 */

非同期版 (直列処理)

/**
 * Usage:
 * $ node walks.js ~/path/to/find/ .js,.json
 */
const dir = process.argv[2]; // 検索フォルダ
const ext = process.argv[3]; // 拡張子(カンマ区切りOK)
const fs = require('fs');
const path = require('path');

/**
 * 指定したフォルダ配下のファイルのパスを取得 (非同期版)
 *
 * ※直列(シリアル)に処理します。
 * 
 * @param  {String} dir - このパスの配下を検索
 * @param  {String|Array<String>} suffix - (option) ファイル名のサフィックス(拡張子とかを想定)
 * @return {Array<String>} ヒットしたファイルのフルパスの一覧
 */
function walks(dir, suffix, done) {
  let results = [];
  fs.readdir(dir, function(err, list) {
    if (err) return done(err);
    let i = 0;
    (function next() {
      let file = list[i++];
      if (!file) return done(null, results);
      file = path.resolve(dir, file);
      fs.stat(file, function(err, stat) {
        if (stat && stat.isDirectory()) {
          walks(file, suffix, function(err, res) {
            results = results.concat(res);
            next();
          });
        } else {
          // NOTE: append files with specified suffix:
          if (!suffix || suffix.length <= 0 || _hasSuffix(file, suffix)) {
            results.push(file);
          }
          next();
        }
      });
    })();
  });

  function _hasSuffix(filename, list) {
    if (typeof list === "string") {
      return filename.endsWith(list);
    } else if (Array.isArray(list)) {
      for (let len = list.length, i = 0; i < len; i++) {
        const suffix = list[i];
        if (filename.endsWith(suffix)) {
          return true;
        }
      }
    }
    return false;
  }
}

walks(dir, ext.split(","), (err, res) => {
  console.log("done:", res);
});

/*
$ node walks.js /home/hoge/test/ .js
done: [
  '/home/hoge/test/app.js',
  '/home/hoge/test/walk.js',
  '/home/hoge/test/walks.js',
  '/home/hoge/test/walkSync.js'
]
 */

以上、割とよく使うテクニックのメモでした。

Node.js

芽萌丸プログラミング部 @programming
芽萌丸プログラミング部@programming
プログラミング関連アカウント。Web標準技術を中心に書いていきます。フロントエンドからサーバサイドまで JavaScript だけで済ませたい人たちの集いです。記事は主に @TanakaSoftwareLab が担当。
オススメ:Zattoyomiで時事ネタチェックの時間節約!