Eliminar el directorio que no está vacío

300

En mi aplicación Node, necesito eliminar un directorio que tiene algunos archivos, pero fs.rmdirsolo funciona en directorios vacíos. ¿Cómo puedo hacer esto?

sachin
fuente
1
En resumen: fs.readdir(dirPath)para una variedad de rutas en una carpeta, repita fs.unlink(filename)para eliminar cada archivo y, finalmente, fs.rmdir(dirPath)para eliminar la carpeta ahora vacía. Si necesita recurrir, verifique fs.lstat(filename).isDirectory().
iono

Respuestas:

319

Hay un módulo para esto llamado rimraf( https://npmjs.org/package/rimraf ). Proporciona la misma funcionalidad querm -Rf

Uso asíncrono :

var rimraf = require("rimraf");
rimraf("/some/directory", function () { console.log("done"); });

Uso de sincronización :

rimraf.sync("/some/directory");
Morgan ARR Allen
fuente
1
Extraño, nunca he visto un comportamiento así. Sugeriría buscar y / o presentar un error. github.com/isaacs/rimraf/issues
Morgan ARR Allen
35
Esto es algo que se puede hacer fácilmente con las bibliotecas de NodeJS Core, ¿por qué instalar un paquete de terceros no mantenido?
SudoKid
44
@EmettSpeer ¿Cuándo quieres decir con "hacerse fácilmente"? Auto escribir una función como deleteFolderRecursiveen la siguiente respuesta?
Freewind
23
"pero incluso con la siguiente función es mejor que agregar un paquete innecesario a su sistema". Estoy totalmente en desacuerdo. Está reinventando la rueda por enésima vez en millones sin absolutamente ninguna razón y arriesgándose a introducir errores o vulnerabilidades de seguridad en el proceso. Por lo menos es una pérdida de tiempo. Inb4 "¿y si sueltan el paquete?": En el caso extremadamente improbable de que el paquete se elimine del registro npm, siempre puede reemplazarlo por el suyo . No tiene sentido vendar tu cabeza antes de romperla.
Demonblack
3
ahora puede usar una recursiveopción: stackoverflow.com/a/57866165/6269864
245

Para eliminar la carpeta sincrónicamente

const fs = require('fs');
const Path = require('path');

const deleteFolderRecursive = function(path) {
  if (fs.existsSync(path)) {
    fs.readdirSync(path).forEach((file, index) => {
      const curPath = Path.join(path, file);
      if (fs.lstatSync(curPath).isDirectory()) { // recurse
        deleteFolderRecursive(curPath);
      } else { // delete file
        fs.unlinkSync(curPath);
      }
    });
    fs.rmdirSync(path);
  }
};
SharpCoder
fuente
33
Es posible que desee agregar algunas comprobaciones de que no va a ejecutar esto accidentalmente en '/'. Por ejemplo, pasar una ruta vacía y un error tipográfico en el archivo podría hacer que curPath sea el directorio raíz.
Jake_Howard
10
Implementación más robusta: reemplace var curPath = path + "/" + file;con var curPath = p.join(path, file);el módulo de ruta incluido:var p = require("path")
Andry
99
Windows tiene \ cortes, así que path.join(dirpath, file)debería ser mejor quepath + "/" + file
thybzi
55
Es posible que obtenga el "Tamaño de pila de llamadas máximo excedido" con este código debido a demasiadas operaciones en un tiempo de tic. @Walf si ejecuta la aplicación de consola, tiene 1 cliente, no más. Entonces, no es necesario usar asíncrono para la aplicación de consola en este caso
Leonid Dashko
44
Recibo 'Error: ENOTEMPTY: directorio no vacío'
Gaviota
168

La mayoría de las personas que usan fsNode.js desearían funciones cercanas a la "forma Unix" de tratar con archivos. Estoy usando fs-extra para traer todas las cosas geniales:

fs-extra contiene métodos que no están incluidos en el paquete vanilla Node.js fs. Tales como mkdir -p, cp -r y rm -rf.

Aún mejor, fs-extra es una caída en el reemplazo de fs nativos. Todos los métodos en fs no se modifican y se adjuntan a él. Significa que puede reemplazar fs por fs-extra :

// this can be replaced
const fs = require('fs')

// by this
const fs = require('fs-extra')

Y luego puede eliminar una carpeta de esta manera:

fs.removeSync('/tmp/myFolder'); 
//or
fs.remove('/tmp/myFolder', callback);
Pierre Maoui
fuente
para la versión de sincronización que necesitaremoveSync('/tmp/myFolder')
olidem
148

A partir de 2019 ...

A partir de Node.js 12.10.0 , fs.rmdirSyncadmite recursiveopciones, por lo que finalmente puede hacer:

fs.rmdirSync(dir, { recursive: true });

Donde la recursiveopción elimina todo el directorio de forma recursiva.

K - La toxicidad en SO está creciendo.
fuente
55
@anneb Eso sucede si está utilizando una versión anterior de Node.js (<12.10). La última versión reconoce la opción recursive: truey elimina las carpetas no vacías sin quejas.
GOTO 0
99
La eliminación recursiva sigue siendo experimental a partir del nodo v13.0.1
Tim
55
La firma de la función es en realidad fs.rmdir(path[, options], callback)ofs.rmdirSync(path[, options])
conceptdeluxe
@Tim, ¿qué quieres decir con experimental?
Emerica
2
@Emerica En los documentos oficiales de node.js hay una gran notificación naranja que dice que fs.rmdires experimental con estabilidad 1. "Estabilidad: 1 - Experimental. La función no está sujeta a las reglas de versiones semánticas. Pueden ocurrir cambios o eliminaciones compatibles con versiones anteriores. versión futura. El uso de la función no se recomienda en entornos de producción ".
Tim
24

Mi respuesta modificada de @oconnecp ( https://stackoverflow.com/a/25069828/3027390 )

Utiliza path.join para una mejor experiencia multiplataforma. Por lo tanto, no olvides exigirlo.

var path = require('path');

También cambió el nombre de la función a rimraf;)

/**
 * Remove directory recursively
 * @param {string} dir_path
 * @see https://stackoverflow.com/a/42505874/3027390
 */
function rimraf(dir_path) {
    if (fs.existsSync(dir_path)) {
        fs.readdirSync(dir_path).forEach(function(entry) {
            var entry_path = path.join(dir_path, entry);
            if (fs.lstatSync(entry_path).isDirectory()) {
                rimraf(entry_path);
            } else {
                fs.unlinkSync(entry_path);
            }
        });
        fs.rmdirSync(dir_path);
    }
}
thybzi
fuente
17

Por lo general, no resucito hilos viejos, pero hay mucho en abandono aquí y sin la respuesta de rimraf, todo esto me parece demasiado complicado.

Primero en el Nodo moderno (> = v8.0.0) puede simplificar el proceso utilizando solo módulos centrales de nodo, completamente asíncrono, y paralelizar la desvinculación de archivos simultáneamente en una función de cinco líneas y aún así mantener la legibilidad:

const fs = require('fs');
const path = require('path');
const { promisify } = require('util');
const readdir = promisify(fs.readdir);
const rmdir = promisify(fs.rmdir);
const unlink = promisify(fs.unlink);

exports.rmdirs = async function rmdirs(dir) {
  let entries = await readdir(dir, { withFileTypes: true });
  await Promise.all(entries.map(entry => {
    let fullPath = path.join(dir, entry.name);
    return entry.isDirectory() ? rmdirs(fullPath) : unlink(fullPath);
  }));
  await rmdir(dir);
};

En otra nota, una protección para ataques transversales de ruta es inapropiada para esta función porque

  1. Está fuera del alcance basado en el Principio de responsabilidad única .
  2. Debe ser manejado por la persona que llama, no por esta función. Esto es similar a la línea de comandos rm -rfen que toma un argumento y permitirá al usuario rm -rf /si se le solicita. Sería responsabilidad de un script proteger el rmprograma en sí mismo.
  3. Esta función no podría determinar dicho ataque ya que no tiene un marco de referencia. Nuevamente, esa es la responsabilidad de la persona que llama que tendría el contexto de intención que le proporcionaría una referencia para comparar el recorrido de la ruta.
  4. Sym-links no son una preocupación ya que .isDirectory()es falsepara SYM-enlaces y no estén disociados en recursed.

Por último, pero no menos importante, existe una rara condición de carrera en la que la recursión podría producir un error si una de las entradas se desvincula o se elimina fuera de este script en el momento justo mientras se ejecuta esta recursión. Dado que este escenario no es típico en la mayoría de los entornos, es probable que se lo pase por alto. Sin embargo, si es necesario (para algunos casos extremos), este problema se puede mitigar con este ejemplo un poco más complejo:

exports.rmdirs = async function rmdirs(dir) {
  let entries = await readdir(dir, { withFileTypes: true });
  let results = await Promise.all(entries.map(entry => {
    let fullPath = path.join(dir, entry.name);
    let task = entry.isDirectory() ? rmdirs(fullPath) : unlink(fullPath);
    return task.catch(error => ({ error }));
  }));
  results.forEach(result => {
    // Ignore missing files/directories; bail on other errors
    if (result && result.error.code !== 'ENOENT') throw result.error;
  });
  await rmdir(dir);
};

EDITAR: hacer isDirectory()una función. Elimine el directorio real al final. Arregla la recursión faltante.

Sukima
fuente
1
Esta es una solución realmente ordenada. Pregunta sobre el segundo ejemplo de código: no llama awaita su Promise.all(…); ¿Es esto intencional? Parece que en su estado actual results.forEachiteraría sobre las promesas, mientras que el código espera iterar sobre los resultados. ¿Me estoy perdiendo de algo?
Anton Strogonoff
@ Tony tienes razón, es un error tipográfico / error. ¡Buena atrapada!
Sukima
Tal vez una verificación primero para asegurarse de que el directorio existe? algo así comoif (!fs.existsSync(dir)) return
GTPV
@GTPV ¿Por qué? Esto aumenta la responsabilidad de esta función. readdirarrojará un error como debería. Si rmdir non-existing-direl código de salida es un error. Sería responsabilidad del consumidor intentar / atrapar. Este es el mismo método descrito en los documentos del nodo cuando se trata de usar funciones fs. Esperan que intente / atrape y mire los errores codepara determinar qué hacer. Un control adicional introduce una condición de carrera.
Sukima
Definitivamente veo tu punto. Sin embargo, esperaría intuitivamente que intentar eliminar una carpeta que no existe tendría éxito, ya que simplemente no haría nada. Sin condición de carrera si fs.existsse utiliza la versión síncrona de . PD: esta es una gran solución.
GTPV
12

Aquí hay una versión asíncrona de la respuesta de @ SharpCoder

const fs = require('fs');
const path = require('path');

function deleteFile(dir, file) {
    return new Promise(function (resolve, reject) {
        var filePath = path.join(dir, file);
        fs.lstat(filePath, function (err, stats) {
            if (err) {
                return reject(err);
            }
            if (stats.isDirectory()) {
                resolve(deleteDirectory(filePath));
            } else {
                fs.unlink(filePath, function (err) {
                    if (err) {
                        return reject(err);
                    }
                    resolve();
                });
            }
        });
    });
};

function deleteDirectory(dir) {
    return new Promise(function (resolve, reject) {
        fs.access(dir, function (err) {
            if (err) {
                return reject(err);
            }
            fs.readdir(dir, function (err, files) {
                if (err) {
                    return reject(err);
                }
                Promise.all(files.map(function (file) {
                    return deleteFile(dir, file);
                })).then(function () {
                    fs.rmdir(dir, function (err) {
                        if (err) {
                            return reject(err);
                        }
                        resolve();
                    });
                }).catch(reject);
            });
        });
    });
};
Tony Brix
fuente
10

Escribí esta función llamada eliminar carpeta. Eliminará recursivamente todos los archivos y carpetas en una ubicación. El único paquete que requiere es asíncrono.

var async = require('async');

function removeFolder(location, next) {
    fs.readdir(location, function (err, files) {
        async.each(files, function (file, cb) {
            file = location + '/' + file
            fs.stat(file, function (err, stat) {
                if (err) {
                    return cb(err);
                }
                if (stat.isDirectory()) {
                    removeFolder(file, cb);
                } else {
                    fs.unlink(file, function (err) {
                        if (err) {
                            return cb(err);
                        }
                        return cb();
                    })
                }
            })
        }, function (err) {
            if (err) return next(err)
            fs.rmdir(location, function (err) {
                return next(err)
            })
        })
    })
}
oconnecp
fuente
44
La idea es en realidad no escribir su propio código si ya ha sido escrito por otra persona. La mejor manera de hacerlo es usar rimraf o fs-extra o cualquier otro módulo de nodo, para hacer el trabajo por usted.
Victor Pudeyev
90
Sí, escribir su propio código es terrible, porque el uso de docenas de módulos de terceros para operaciones relativamente triviales nunca ha demostrado tener inconvenientes en aplicaciones a gran escala.
Eric
8

Si está utilizando el nodo 8+ quiere asincronicidad y no quiere dependencias externas, aquí está la versión asíncrona / espera:

const path = require('path');
const fs = require('fs');
const util = require('util');

const readdir = util.promisify(fs.readdir);
const lstat = util.promisify(fs.lstat);
const unlink = util.promisify(fs.unlink);
const rmdir = util.promisify(fs.rmdir);

const removeDir = async (dir) => {
    try {
        const files = await readdir(dir);
        await Promise.all(files.map(async (file) => {
            try {
                const p = path.join(dir, file);
                const stat = await lstat(p);
                if (stat.isDirectory()) {
                    await removeDir(p);
                } else {
                    await unlink(p);
                    console.log(`Removed file ${p}`);
                }
            } catch (err) {
                console.error(err);
            }
        }))
        await rmdir(dir);
        console.log(`Removed dir ${dir}`);
    } catch (err) {
      console.error(err);
    }
}
RonZ
fuente
4

Versión asíncrona de la respuesta de @ SharpCoder usando fs.promises:

const fs = require('fs');
const afs = fs.promises;

const deleteFolderRecursive = async path =>  {
    if (fs.existsSync(path)) {
        for (let entry of await afs.readdir(path)) {
            const curPath = path + "/" + entry;
            if ((await afs.lstat(curPath)).isDirectory())
                await deleteFolderRecursive(curPath);
            else await afs.unlink(curPath);
        }
        await afs.rmdir(path);
    }
};
Error 404
fuente
3

Llegué aquí mientras trataba de terminar con el gulpy estoy escribiendo para mayores alcances.

Cuando desee eliminar archivos y carpetas usando del, debe agregar /**una eliminación recursiva.

gulp.task('clean', function () {
    return del(['some/path/to/delete/**']);
});
Jin Kwon
fuente
2

El paquete de facto es rimraf, pero aquí está mi pequeña versión asíncrona:

const fs = require('fs')
const path = require('path')
const Q = require('q')

function rmdir (dir) {
  return Q.nfcall(fs.access, dir, fs.constants.W_OK)
    .then(() => {
      return Q.nfcall(fs.readdir, dir)
        .then(files => files.reduce((pre, f) => pre.then(() => {
          var sub = path.join(dir, f)
          return Q.nfcall(fs.lstat, sub).then(stat => {
            if (stat.isDirectory()) return rmdir(sub)
            return Q.nfcall(fs.unlink, sub)
          })
        }), Q()))
    })
    .then(() => Q.nfcall(fs.rmdir, dir))
}
clarkttfu
fuente
2

De acuerdo con la fsdocumentación , fsPromisesactualmente proporciona la recursiveopción de forma experimental, que, al menos en mi propio caso en Windows, elimina el directorio y los archivos que contiene.

fsPromises.rmdir(path, {
  recursive: true
})

¿ recursive: trueElimina los archivos en Linux y MacOS?

chico mayor
fuente
1

Ultra-velocidad y a prueba de fallas

Puede usar el lignatorpaquete ( https://www.npmjs.com/package/lignator ), es más rápido que cualquier código asíncrono (por ejemplo, rimraf) y más a prueba de fallas (especialmente en Windows, donde la eliminación de archivos no es instantánea y los archivos pueden estar bloqueado por otros procesos).

4,36 GB de datos, 28 042 archivos, 4 217 carpetas en Windows eliminadas en 15 segundos frente a los 60 segundos de rimraf en el disco duro antiguo.

const lignator = require('lignator');

lignator.remove('./build/');
HankMoody
fuente
1

La carpeta de sincronización se elimina con los archivos o solo con un archivo.

No soy muy generoso ni colaborador, pero no pude encontrar una buena solución a este problema y tuve que encontrar el camino ... así que espero que les guste :)

Funciona perfecto para mí con cualquier número de directorios anidados y subdirectorios. Tenga cuidado con el alcance de 'esto' cuando recurra la función, su implementación puede ser diferente. En mi caso, esta función permanece en el retorno de otra función, por eso la llamo con esto.

    const fs = require('fs');

    deleteFileOrDir(path, pathTemp = false){
            if (fs.existsSync(path)) {
                if (fs.lstatSync(path).isDirectory()) {
                    var files = fs.readdirSync(path);
                    if (!files.length) return fs.rmdirSync(path);
                    for (var file in files) {
                        var currentPath = path + "/" + files[file];
                        if (!fs.existsSync(currentPath)) continue;
                        if (fs.lstatSync(currentPath).isFile()) {
                            fs.unlinkSync(currentPath);
                            continue;
                        }
                        if (fs.lstatSync(currentPath).isDirectory() && !fs.readdirSync(currentPath).length) {
                            fs.rmdirSync(currentPath);
                        } else {
                            this.deleteFileOrDir(currentPath, path);
                        }
                    }
                    this.deleteFileOrDir(path);
                } else {
                    fs.unlinkSync(path);
                }
            }
            if (pathTemp) this.deleteFileOrDir(pathTemp);
        }
MetaTron
fuente
1

Mientras que recursivees una opción experimental defs.rmdir

function rm (path, cb) {
    fs.stat(path, function (err, stats) {
        if (err)
            return cb(err);

        if (stats.isFile())
            return fs.unlink(path, cb);

        fs.rmdir(path, function (err) {
            if (!err || err && err.code != 'ENOTEMPTY') 
                return cb(err);

            fs.readdir(path, function (err, files) {
                if (err)
                    return cb(err);

                let next = i => i == files.length ? 
                    rm(path, cb) : 
                    rm(path + '/' + files[i], err => err ? cb(err) : next(i + 1));

                next(0);
            });
        });
    });
}
Aikon Mogwai
fuente
1

Actualización 2020

Desde la versión 12.10.0 recursiveOption se ha agregado para las opciones.

Tenga en cuenta que la eliminación recursiva es experimental .

Entonces lo harías para la sincronización:

fs.rmdirSync(dir, {recursive: true});

o para asíncrono:

fs.rmdir(dir, {recursive: true});
Giovanni Patruno
fuente
0

¡Solo usa el módulo rmdir ! Es fácil y simple.

Aminovski
fuente
66
No siempre es una buena idea usar un módulo para cada pequeño fragmento de código. Si tiene que crear un acuerdo de licencia, por ejemplo, esto genera un verdadero dolor.
Mijago
44
necesita agregar un ejemplo de código para que su respuesta sea más interesante
Xeltor
0

Otra alternativa es usar el fs-promisemódulo que proporciona versiones prometidas de los fs-extramódulos.

entonces podrías escribir como este ejemplo:

const { remove, mkdirp, writeFile, readFile } = require('fs-promise')
const { join, dirname } = require('path')

async function createAndRemove() {
  const content = 'Hello World!'
  const root = join(__dirname, 'foo')
  const file = join(root, 'bar', 'baz', 'hello.txt')

  await mkdirp(dirname(file))
  await writeFile(file, content)
  console.log(await readFile(file, 'utf-8'))
  await remove(join(__dirname, 'foo'))
}

createAndRemove().catch(console.error)

nota: async / await requiere una versión reciente de nodejs (7.6+)

Max Fichtelmann
fuente
0

Una forma rápida y sucia (tal vez para probar) podría ser usar directamente el método execo spawnpara invocar la llamada del sistema operativo para eliminar el directorio. Lea más sobre NodeJs child_process .

let exec = require('child_process').exec
exec('rm -Rf /tmp/*.zip', callback)

Los inconvenientes son:

  1. Depende del sistema operativo subyacente, es decir, el mismo método se ejecutaría en Unix / Linux, pero probablemente no en Windows.
  2. No puede secuestrar el proceso en condiciones o errores. Simplemente asigna la tarea al sistema operativo subyacente y espera a que se devuelva el código de salida.

Beneficios:

  1. Estos procesos pueden ejecutarse de forma asincrónica.
  2. Puede escuchar la salida / error del comando, por lo tanto, la salida del comando no se pierde. Si no se completa la operación, puede verificar el código de error y volver a intentarlo.
Erupción
fuente
2
¡Perfecto para cuando escribes un script y no quieres instalar dependencias porque estás a punto de eliminar este script en 30 segundos después de eliminar todos tus archivos!
Mathias el
Siempre hay formas de estropear y eliminar el sistema de archivos raíz. En este caso, OP puede eliminar la -fbandera para estar seguro, o asegurarse mientras escribe que él / ella no va a eliminar todo. exec + rmes un comando válido y útil en el nodo que uso a menudo durante las pruebas.
Erupción
0

Desearía que hubiera una manera de hacer esto sin módulos adicionales para algo tan minúsculo y común, pero esto es lo mejor que se me ocurrió.

Actualización: ahora debería funcionar en Windows (probado Windows 10), y también debería funcionar en sistemas Linux / Unix / BSD / Mac.

const
    execSync = require("child_process").execSync,
    fs = require("fs"),
    os = require("os");

let removeDirCmd, theDir;

removeDirCmd = os.platform() === 'win32' ? "rmdir /s /q " : "rm -rf ";

theDir = __dirname + "/../web-ui/css/";

// WARNING: Do not specify a single file as the windows rmdir command will error.
if (fs.existsSync(theDir)) {
    console.log(' removing the ' + theDir + ' directory.');
    execSync(removeDirCmd + '"' + theDir + '"', function (err) {
        console.log(err);
    });
}
b01
fuente
Tal vez fs-extra es el camino a seguir si desea un solo módulo.
b01
3
Este método es francamente peligroso. La concatenación de cadenas de un comando de shell, especialmente sin escapar, invita a vulnerabilidades de ejecución de código y similares. Si va a usar rmdir, use el child_process.execFileque no invoque el shell y, en su lugar, pase argumentos explícitamente.
nevyn
@nevyn Lo intentaré y actualizaré mi respuesta si funciona.
b01
¡Siempre prefiera no usar terceros! ¡Gracias!
Anton Mitsev
Además de eso, este método es bastante lento. La API nativa de Nodejs es mucho más rápida.
mersey
0

Este es un enfoque que utiliza promisify y dos funciones de ayuda (to and toAll) para resolver la promesa.

Realiza todas las acciones de forma asíncrona.

const fs = require('fs');
const { promisify } = require('util');
const to = require('./to');
const toAll = require('./toAll');

const readDirAsync = promisify(fs.readdir);
const rmDirAsync = promisify(fs.rmdir);
const unlinkAsync = promisify(fs.unlink);

/**
    * @author Aécio Levy
    * @function removeDirWithFiles
    * @usage: remove dir with files
    * @param {String} path
    */
const removeDirWithFiles = async path => {
    try {
        const file = readDirAsync(path);
        const [error, files] = await to(file);
        if (error) {
            throw new Error(error)
        }
        const arrayUnlink = files.map((fileName) => {
            return unlinkAsync(`${path}/${fileName}`);
        });
        const [errorUnlink, filesUnlink] = await toAll(arrayUnlink);
        if (errorUnlink) {
            throw new Error(errorUnlink);
        }
        const deleteDir = rmDirAsync(path);
        const [errorDelete, result] = await to(deleteDir);
        if (errorDelete) {
            throw new Error(errorDelete);
        }
    } catch (err) {
        console.log(err)
    }
}; 
Aecio Levy
fuente
0

// sin el uso de ninguna biblioteca de terceros

const fs = require('fs');
var FOLDER_PATH = "./dirname";
var files = fs.readdirSync(FOLDER_PATH);
files.forEach(element => {
    fs.unlinkSync(FOLDER_PATH + "/" + element);
});
fs.rmdirSync(FOLDER_PATH);
Amy
fuente
1
Esto funcionará para lo que necesito, pero es posible que desee usar la ruta en lugar de concatenar la barra:fs.unllinkSync(path.join(FOLDER_PATH, element);
jackofallcode
-1
const fs = require("fs")
const path = require("path")

let _dirloc = '<path_do_the_directory>'

if (fs.existsSync(_dirloc)) {
  fs.readdir(path, (err, files) => {
    if (!err) {
      for (let file of files) {
        // Delete each file
        fs.unlinkSync(path.join(_dirloc, file))
      }
    }
  })
  // After the 'done' of each file delete,
  // Delete the directory itself.
  if (fs.unlinkSync(_dirloc)) {
    console.log('Directory has been deleted!')
  }
}
Erisan Olasheni
fuente
1
Creo que algo como esto debería funcionar para los directorios anidados.
fool4jesus
Sí, tanto para el directorio anidado como para el anidado
Erisan Olasheni, el