问题
I have a directory structure like this:
/git
/content
/repo1
/repo2
/repo3
/modules
/repo4
/repo5
/tools
/project
/repo6
/repo7
/test
/repo8
/repo9
I'd like to be able to find the path to a particular repo just by passing the repo name:
searchDirForSubdir('/git', 'repo7'); // expected to return /git/tools/project/repo7
The function I have at the moment (below) returns undefined
, even though the console.log
call spits out the correct path. I know I'm messing up the recursion, but can't work out what I'm doing wrong.
function searchDirForSubdir (dirToSearch, needle, depth = 0) {
const DEPTH_LIMIT = 4;
const fs = require('fs');
for (let entry of fs.readdirSync(dirToSearch)) {
if (depth + 1 <= DEPTH_LIMIT) {
let fullPath = `${dirToSearch}/${entry}`;
if (!entry.startsWith('.')
&& fs.lstatSync(fullPath).isDirectory()
) {
if (entry == needle) {
console.log(fullPath);
return fullPath;
} else {
searchDirForSubdir (fullPath, needle, depth + 1);
}
}
}
}
}
回答1:
you are missing a return
clause before the line searchDirForSubdir (fullPath, needle, depth + 1);
, if it returned something.
Your code fixed:
function searchDirForSubdir(dirToSearch, needle, depth = 0) {
const DEPTH_LIMIT = 4;
const fs = require('fs');
for (let entry of fs.readdirSync(dirToSearch)) {
if (depth + 1 <= DEPTH_LIMIT) {
let fullPath = `${dirToSearch}/${entry}`;
if (!entry.startsWith('.')
&& fs.lstatSync(fullPath).isDirectory()) {
if (entry == needle) {
return fullPath;
} else {
const found = searchDirForSubdir(fullPath, needle, depth + 1);
if (found)
return found;
}
}
}
}
}
回答2:
fs/promises and fs.Dirent
Recursion is a functional heritage and so using it with functional style yields the best results. Here's an efficient dirs
program using Node's fast fs.Dirent objects and fs/promises
module. This approach allows you to skip wasteful fs.exist
or fs.stat
calls on every path -
import { readdir } from "fs/promises"
import { join } from "path"
async function* dirs (path = ".")
{ yield path
for (const dirent of await readdir(path, { withFileTypes: true }))
if (dirent.isDirectory())
yield* dirs(join(path, dirent.name))
}
async function* empty () {}
async function toArray (iter = empty())
{ let r = []
for await (const x of iter)
r.push(x)
return r
}
toArray(dirs(".")).then(console.log, console.error)
Let's get some files so we can see dirs
working -
$ yarn add immutable # (just some example package)
$ node main.js
[
'.',
'node_modules',
'node_modules/immutable',
'node_modules/immutable/contrib',
'node_modules/immutable/contrib/cursor',
'node_modules/immutable/contrib/cursor/__tests__',
'node_modules/immutable/dist'
]
async generators
And because we're using async generators, we can intuitively stop iteration as soon as a matching file is found -
import { readdir } from "fs/promises"
import { join, basename } from "path"
async function* dirs // ...
async function* empty // ...
async function toArray // ...
async function search (iter = empty(), test = _ => false)
{ for await (const p of iter)
if (test(p))
return p // <-- iteration stops here
}
search(dirs("."), path => basename(path) === "contrib") // <-- search for contrib
.then(console.log, console.error)
search(dirs("."), path => basename(path) === "foobar") // <-- search for foobar
.then(console.log, console.error)
$ node main.js
node_modules/immutable/contrib # ("contrib" found)
undefined # ("foobar" not found)
invent your own convenience
Above search
is a higher-order function like Array.prototype.find
. We could write searchByName
which probably more comfortable to use -
import // ...
async function* dirs // ...
async function* empty // ...
async function toArray // ...
async function search // ...
async function searchByName (iter = empty(), query = "")
{ return search(iter, p => basename(p) === query) }
searchByName(dirs("."), "contrib")
.then(console.log, console.error)
searchByName(dirs("."), "foobar")
.then(console.log, console.error)
Output is the same -
$ node main.js
node_modules/immutable/contrib # ("contrib" found)
undefined # ("foobar" not found)
make it a module
A practice that isn't emphasised enough. By making a module, we create a place to separate concerns and keep complexity from overwhelming the rest of our program -
// FsExtensions.js
import { readdir } from "fs/promises" // <-- import only what you need
import { join, basename } from "path"
async function* dirs // ...
async function* empty // ...
async function search // ...
async function searchByName // ...
async function toArray // ...
// ...
export { dirs, search, searchByName, toArray } // <-- you control what to export
// Main.js
import { dirs, searchByName } from './FsExtensions' // <-- import only what's used
searchByName(dirs("."), "contrib")
.then(console.log, console.error)
searchByName(dirs("."), "foobar")
.then(console.log, console.error)
limiting depth
dirs
is implemented using a recursive generator. We can easily restrict the depth of recursion by adding a parameter to our function. I'm using a default value of Infinity
but you can choose whatever you like -
async function* dirs (path = ".", depth = Infinity)
{ if (depth < 1) return // stop if depth limit is reached
yield path
for (const dirent of await readdir(path, { withFileTypes: true }))
if (dirent.isDirectory())
yield* dirs(join(path, dirent.name), depth - 1)
}
When dirs
is called with a second argument, the depth of recursion is limited -
toArray(dirs(".", 1)).then(console.log, console.error)
// [ '.' ]
toArray(dirs(".", 2)).then(console.log, console.error)
// [ '.', 'node_modules' ]
toArray(dirs(".", 3)).then(console.log, console.error)
// [ '.', 'node_modules', 'node_modules/immutable' ]
toArray(dirs(".", 4)).then(console.log, console.error)
// [ '.',
// 'node_modules',
// 'node_modules/immutable',
// 'node_modules/immutable/contrib',
// 'node_modules/immutable/dist'
// ]
searchByName(dirs(".", 1), "contrib").then(console.log, console.error)
// undefined
searchByName(dirs(".", 2), "contrib").then(console.log, console.error)
// undefined
searchByName(dirs(".", 3), "contrib").then(console.log, console.error)
// undefined
searchByName(dirs(".", 4), "contrib").then(console.log, console.error)
// node_modules/immutable/contrib
searchByName(dirs("."), "contrib").then(console.log, console.error)
// node_modules/immutable/contrib
searchByName(dirs("."), "foobar").then(console.log, console.error)
// undefined
来源:https://stackoverflow.com/questions/62027366/recursively-search-through-sub-directories-for-specified-directory-name-in-node