Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Node.js recursively list full path of files

Good night everyone. I'm having trouble with probably some simple recursive function. The problem is to recursively list all files in a given folder and its subfolders.

For the moment, I've managed to list files in a directory using a simple function:

fs.readdirSync(copyFrom).forEach((file) => {
  let fullPath = path.join(copyFrom, file);

  if (fs.lstatSync(fullPath).isDirectory()) {
    console.log(fullPath);
  } else {
    console.log(fullPath);
  }
});

I've tried various methods like do{} ... while() but I can't get it right. As I'm a beginner in javascript, I finally decided to ask for help from you guys.

like image 802
Puka Avatar asked May 01 '18 18:05

Puka


2 Answers

Just add a recursive call and you are done:

 function traverseDir(dir) {
   fs.readdirSync(dir).forEach(file => {
     let fullPath = path.join(dir, file);
     if (fs.lstatSync(fullPath).isDirectory()) {
        console.log(fullPath);
        traverseDir(fullPath);
      } else {
        console.log(fullPath);
      }  
   });
 }
like image 86
Jonas Wilms Avatar answered Oct 26 '22 19:10

Jonas Wilms


Using console.log in this way displays the path and that's great, but what if you want to do something more meaningful with the paths? For example, maybe you want to collect all of them in an array and pass them off for processing elsewhere...

This process of beginning with a seed state and expanding a sequence of values while the state changes is called an unfold.

const { join } =
  require ('path')

const { readdirSync, statSync } =
  require ('fs')

const unfold = (f, initState) =>
  f ( (value, nextState) => [ value, ...unfold (f, nextState) ]
    , () => []
    , initState
    )

const None =
  Symbol ()

const relativePaths = (path = '.') =>
  readdirSync (path) .map (p => join (path, p))

const traverseDir = (dir) =>
  unfold
    ( (next, done, [ path = None, ...rest ]) =>
        path === None
          ? done ()
          : next ( path
                 , statSync (path) .isDirectory ()
                     ? relativePaths (path) .concat (rest)
                     : rest
                 )
    , relativePaths (dir)
    )

console.log (traverseDir ('.'))
// [ a, a/1, a/1/1, a/2, a/2/1, a/2/2, b, b/1, ... ]

If this is your first time seeing a program like this, unfold will feel very overwhelming. Below, is a simplified example of unfold used to generate an array of the lowercase alphabet

const unfold = (f, init) =>
  f ( (x, next) => [ x, ...unfold (f, next) ]
    , () => []
    , init
    )

const nextLetter = c =>
  String.fromCharCode (c.charCodeAt (0) + 1)

const alphabet =
  unfold
    ( (next, done, c) =>
        c > 'z'
          ? done ()
          : next ( c              // value to add to output
                 , nextLetter (c) // next state
                 )
    , 'a' // initial state
    )

console.log (alphabet)
// [ a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z ]

If you're still stuck, the techniques I've demonstrated here are explained in greater detail in answers to similar questions

  • Functional way to create an array of numbers
  • Loop until... with Ramda

Generally, it's preferred to use the asynchronous functions in the fs module as this prevents the program from hanging in the event of long disk read times or network delay. Unfolding plays nicely with asynchrony as demonstrated in these other Q&A's

  • Recursive Promises Not Returning
  • Recursion call async func with promises gets Possible Unhandled Promise Rejection
  • Recursive JS function with setTimeout
  • Promise with recursion
like image 31
Mulan Avatar answered Oct 26 '22 19:10

Mulan