Recursively search through sub directories for specified directory name in node

亡梦爱人 提交于 2020-06-17 09:19:26

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!