[node.js] node.js fs.readdir 재귀 디렉토리 검색
fs.readdir을 사용하여 비동기 디렉토리 검색에 대한 아이디어가 있습니까? 재귀를 도입하고 읽을 다음 디렉토리로 디렉토리 읽기 기능을 호출 할 수 있음을 알고 있지만 비동기 적이 지 않을까 걱정됩니다.
어떤 아이디어? 나는 node-walk 를 살펴 보았지만 readdir처럼 배열의 파일 만 제공하지는 않습니다. 이기는 하지만
다음과 같은 출력을 찾고 있습니다 …
['file1.txt', 'file2.txt', 'dir/file3.txt']
답변
이것을 달성하는 기본적으로 두 가지 방법이 있습니다. 비동기 환경에서는 직렬 및 병렬의 두 가지 루프가 있음을 알 수 있습니다. 직렬 루프는 다음 반복으로 넘어 가기 전에 하나의 반복이 완료 될 때까지 기다립니다. 이렇게하면 루프의 모든 반복이 순서대로 완료됩니다. 병렬 루프에서는 모든 반복이 동시에 시작되고 하나가 다른 것보다 먼저 완료 될 수 있지만 직렬 루프보다 훨씬 빠릅니다. 따라서이 경우 결과를 완료하고 반환하는 한 (순서를 원하지 않는 한) 워크가 완료되는 순서가 중요하지 않기 때문에 병렬 루프를 사용하는 것이 좋습니다.
병렬 루프는 다음과 같습니다.
var fs = require('fs');
var path = require('path');
var walk = function(dir, done) {
var results = [];
fs.readdir(dir, function(err, list) {
if (err) return done(err);
var 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, function(err, res) {
results = results.concat(res);
if (!--pending) done(null, results);
});
} else {
results.push(file);
if (!--pending) done(null, results);
}
});
});
});
};
직렬 루프는 다음과 같습니다.
var fs = require('fs');
var path = require('path');
var walk = function(dir, done) {
var results = [];
fs.readdir(dir, function(err, list) {
if (err) return done(err);
var i = 0;
(function next() {
var file = list[i++];
if (!file) return done(null, results);
file = path.resolve(dir, file);
fs.stat(file, function(err, stat) {
if (stat && stat.isDirectory()) {
walk(file, function(err, res) {
results = results.concat(res);
next();
});
} else {
results.push(file);
next();
}
});
})();
});
};
그리고 홈 디렉토리에서 테스트하려면 (경고 : 홈 디렉토리에 많은 것이 있으면 결과 목록이 커집니다) :
walk(process.env.HOME, function(err, results) {
if (err) throw err;
console.log(results);
});
편집 : 개선 된 예.
답변
이것은 노드 8에서 제공되는 약속, 활용 / 약점, 구조 조정, 비동기 대기, 맵 + 축소 등을 포함하여 최대의 새로운 유행어 기능을 사용하여 동료가 무엇을 알아 내려고 할 때 머리를 긁습니다. 진행되고있다.
노드 8 이상
외부 의존성이 없습니다.
const { promisify } = require('util');
const { resolve } = require('path');
const fs = require('fs');
const readdir = promisify(fs.readdir);
const stat = promisify(fs.stat);
async function getFiles(dir) {
const subdirs = await readdir(dir);
const files = await Promise.all(subdirs.map(async (subdir) => {
const res = resolve(dir, subdir);
return (await stat(res)).isDirectory() ? getFiles(res) : res;
}));
return files.reduce((a, f) => a.concat(f), []);
}
용법
getFiles(__dirname)
.then(files => console.log(files))
.catch(e => console.error(e));
노드 10.10+
더 많은 whizbang과 함께 노드 10 이상에 대해 업데이트되었습니다.
const { resolve } = require('path');
const { readdir } = require('fs').promises;
async function getFiles(dir) {
const dirents = await readdir(dir, { withFileTypes: true });
const files = await Promise.all(dirents.map((dirent) => {
const res = resolve(dir, dirent.name);
return dirent.isDirectory() ? getFiles(res) : res;
}));
return Array.prototype.concat(...files);
}
노드 11.15.0부터는 파일 배열을 병합하는 files.flat()
대신 사용할 수 있습니다 Array.prototype.concat(...files)
.
노드 11 이상
모든 사람의 머리를 완전히 날려 버리려면 async iterators 를 사용하여 다음 버전을 사용할 수 있습니다 . 소비자는 정말 시원 할뿐만 아니라 한 번에 하나씩 결과를 가져 와서 실제로 큰 디렉토리에 더 적합하게 만들 수 있습니다.
const { resolve } = require('path');
const { readdir } = require('fs').promises;
async function* getFiles(dir) {
const dirents = await readdir(dir, { withFileTypes: true });
for (const dirent of dirents) {
const res = resolve(dir, dirent.name);
if (dirent.isDirectory()) {
yield* getFiles(res);
} else {
yield res;
}
}
}
리턴 유형이 이제 약속이 아닌 비동기 반복기이므로 사용법이 변경되었습니다.
(async () => {
for await (const f of getFiles('.')) {
console.log(f);
}
})()
누군가 관심이 있다면 https://qwtel.com/posts/software/async-generators-in-the-wild/에서 비동기 반복기에 대해 더 많이 썼습니다.
답변
누군가가 유용하다고 생각하는 경우 동기 버전을 작성했습니다.
var walk = function(dir) {
var results = [];
var list = fs.readdirSync(dir);
list.forEach(function(file) {
file = dir + '/' + file;
var stat = fs.statSync(file);
if (stat && stat.isDirectory()) {
/* Recurse into a subdirectory */
results = results.concat(walk(file));
} else {
/* Is a file */
results.push(file);
}
});
return results;
}
팁 : 필터링시 더 적은 리소스를 사용합니다. 이 함수 자체 내에서 필터링하십시오. 예를 들어 results.push(file);
아래 코드로 교체하십시오 . 필요에 따라 조정하십시오.
file_type = file.split(".").pop();
file_name = file.split(/(\\|\/)/g).pop();
if (file_type == "json") results.push(file);
답변
A. 파일 모듈을 살펴보십시오 . walk라는 기능이 있습니다.
file.walk (시작, 콜백)
(null, dirPath, dirs, files)를 전달하여 각 디렉토리에 대한 콜백을 호출하여 파일 트리를 탐색합니다.
이것은 당신을위한 것일 수 있습니다! 그리고 네, 그것은 비동기입니다. 그러나 필요한 경우 전체 경로를 직접 집계해야한다고 생각합니다.
B. 대안, 심지어 내가 좋아하는 것 중 하나 : 유닉스 find
를 사용하십시오 . 이미 프로그래밍 된 무언가가 왜 다시 프로그래밍됩니까? 아마도 정확히 필요한 것은 아니지만 여전히 체크 아웃 할 가치가 있습니다.
var execFile = require('child_process').execFile;
execFile('find', [ 'somepath/' ], function(err, stdout, stderr) {
var file_list = stdout.split('\n');
/* now you've got a list with full path file names */
});
Find에는 폴더가 거의 변경되지 않는 한 후속 검색을 매우 빠르게 수행하는 멋진 내장 캐싱 메커니즘이 있습니다.
답변
또 다른 멋진 npm 패키지는 glob 입니다.
npm install glob
매우 강력하며 모든 되풀이 요구를 충족해야합니다.
편집하다:
나는 실제로 glob에 완전히 만족하지 않았으므로 readdirp을 만들었습니다 .
API가 파일과 디렉토리를 재귀 적으로 찾고 특정 필터를 쉽게 적용 할 수 있다고 확신합니다.
설명서 를 읽고 내용을 보다 잘 이해하고 다음을 통해 설치하십시오.
npm install readdirp
답변
해당 작업을 수행하기 위해 node-glob 을 사용하는 것이 좋습니다 .
var glob = require( 'glob' );
glob( 'dirname/**/*.js', function( err, files ) {
console.log( files );
});
답변
npm 패키지를 사용하려면 렌치 가 좋습니다.
var wrench = require("wrench");
var files = wrench.readdirSyncRecursive("directory");
wrench.readdirRecursive("directory", function (error, files) {
// live your dreams
});
편집 (2018) :
최근에 읽은 사람 : 2015 년에이 패키지가 더 이상 사용되지 않습니다.
wrench.js는 더 이상 사용되지 않으며 꽤 오랫동안 업데이트되지 않았습니다. 추가 파일 시스템 작업을 수행하려면 fs-extra 를 사용하는 것이 좋습니다 .