How to create full path with node's fs.mkdirSync?
Solution 1
One option is to use shelljs module
npm install shelljs
var shell = require('shelljs');
shell.mkdir('-p', fullPath);
From that page:
Available options:
p: full path (will create intermediate dirs if necessary)
As others have noted, there's other more focused modules. But, outside of mkdirp, it has tons of other useful shell operations (like which, grep etc...) and it works on windows and *nix
Edit: comments suggest this doesn't work on systems that don't have mkdir cli instances. That is not the case. That's the point shelljs - create a portable cross platform set of shell like functions. It works on even windows.
Solution 2
Update
NodeJS version 10.12.0
has added a native support for both mkdir
and mkdirSync
to create a directory recursively with recursive: true
option as the following:
fs.mkdirSync(targetDir, { recursive: true });
And if you prefer fs Promises API
, you can write
fs.promises.mkdir(targetDir, { recursive: true });
Original Answer
Create directories recursively if they do not exist! (Zero dependencies)
const fs = require('fs');
const path = require('path');
function mkDirByPathSync(targetDir, { isRelativeToScript = false } = {}) {
const sep = path.sep;
const initDir = path.isAbsolute(targetDir) ? sep : '';
const baseDir = isRelativeToScript ? __dirname : '.';
return targetDir.split(sep).reduce((parentDir, childDir) => {
const curDir = path.resolve(baseDir, parentDir, childDir);
try {
fs.mkdirSync(curDir);
} catch (err) {
if (err.code === 'EEXIST') { // curDir already exists!
return curDir;
}
// To avoid `EISDIR` error on Mac and `EACCES`-->`ENOENT` and `EPERM` on Windows.
if (err.code === 'ENOENT') { // Throw the original parentDir error on curDir `ENOENT` failure.
throw new Error(`EACCES: permission denied, mkdir '${parentDir}'`);
}
const caughtErr = ['EACCES', 'EPERM', 'EISDIR'].indexOf(err.code) > -1;
if (!caughtErr || caughtErr && curDir === path.resolve(targetDir)) {
throw err; // Throw if it's just the last created dir.
}
}
return curDir;
}, initDir);
}
Usage
// Default, make directories relative to current working directory.
mkDirByPathSync('path/to/dir');
// Make directories relative to the current script.
mkDirByPathSync('path/to/dir', {isRelativeToScript: true});
// Make directories with an absolute path.
mkDirByPathSync('/path/to/dir');
Demo
Explanations
-
[UPDATE] This solution handles platform-specific errors like
EISDIR
for Mac andEPERM
andEACCES
for Windows. Thanks to all the reporting comments by @PediT., @JohnQ, @deed02392, @robyoder and @Almenon. - This solution handles both relative and absolute paths. Thanks to @john comment.
- In the case of relative paths, target directories will be created (resolved) in the current working directory. To Resolve them relative to the current script dir, pass
{isRelativeToScript: true}
. - Using
path.sep
andpath.resolve()
, not just/
concatenation, to avoid cross-platform issues. - Using
fs.mkdirSync
and handling the error withtry/catch
if thrown to handle race conditions: another process may add the file between the calls tofs.existsSync()
andfs.mkdirSync()
and causes an exception.- The other way to achieve that could be checking if a file exists then creating it, I.e,
if (!fs.existsSync(curDir) fs.mkdirSync(curDir);
. But this is an anti-pattern that leaves the code vulnerable to race conditions. Thanks to @GershomMaes comment about the directory existence check.
- The other way to achieve that could be checking if a file exists then creating it, I.e,
- Requires Node v6 and newer to support destructuring. (If you have problems implementing this solution with old Node versions, just leave me a comment)
Solution 3
A more robust answer is to use use mkdirp.
var mkdirp = require('mkdirp');
mkdirp('/path/to/dir', function (err) {
if (err) console.error(err)
else console.log('dir created')
});
Then proceed to write the file into the full path with:
fs.writeFile ('/path/to/dir/file.dat'....
Solution 4
fs-extra adds file system methods that aren't included in the native fs module. It is a drop in replacement for fs.
Install fs-extra
$ npm install --save fs-extra
const fs = require("fs-extra");
// Make sure the output directory is there.
fs.ensureDirSync(newDest);
There are sync and async options.
https://github.com/jprichardson/node-fs-extra/blob/master/docs/ensureDir.md
Solution 5
Using reduce we can verify if each path exists and create it if necessary, also this way I think it is easier to follow. Edited, thanks @Arvin, we should use path.sep to get the proper platform-specific path segment separator.
const path = require('path');
// Path separators could change depending on the platform
const pathToCreate = 'path/to/dir';
pathToCreate
.split(path.sep)
.reduce((prevPath, folder) => {
const currentPath = path.join(prevPath, folder, path.sep);
if (!fs.existsSync(currentPath)){
fs.mkdirSync(currentPath);
}
return currentPath;
}, '');
Related videos on Youtube
Comments
-
David Silva Smith almost 2 years
I'm trying to create a full path if it doesn't exist.
The code looks like this:
var fs = require('fs'); if (!fs.existsSync(newDest)) fs.mkdirSync(newDest);
This code works great as long as there is only one subdirectory (a newDest like 'dir1') however when there is a directory path like ('dir1/dir2') it fails with Error: ENOENT, no such file or directory
I'd like to be able to create the full path with as few lines of code as necessary.
I read there is a recursive option on fs and tried it like this
var fs = require('fs'); if (!fs.existsSync(newDest)) fs.mkdirSync(newDest,'0777', true);
I feel like it should be that simple to recursively create a directory that doesn't exist. Am I missing something or do I need to parse the path and check each directory and create it if it doesn't already exist?
I'm pretty new to Node. Maybe I'm using an old version of FS?
-
jfriend00 almost 9 yearsgithub.com/substack/node-mkdirp and all sorts of other solutions on this Google search.
-
Matt Parkins over 5 years@AndyRay This StackOverflow question is now the top result in google for this question, which is funny because that means it's recursi....
-
MrJomp about 5 yearsThat was a problem on older versions of Node, updating to Node 12+ solves the problem
-
-
David Silva Smith almost 9 yearsThanks! I ended up using exec (I was already using this) and it worked like a charm. var exec = require('child_process').exec; var command = "mkdir -p '" + newDest + "'"; var options = {}; var after = function(error, stdout, stderr) { console.log('error', error); console.log('stdout', stdout); console.log('stderr', stderr); } exec(command, options, after);
-
cshotton over 8 yearsThis option may break on node.js platforms that don't have a command line mkdir instance (i.e., non-Linux-y hosts) so it isn't portable, if that matters.
-
Ruan Mendes almost 8 yearsPrefer this answer since you are importing just what you need, not an entire library
-
Stephen Rauch over 7 yearsWhen giving an answer it is preferable to give some explanation as to WHY your answer is the one.
-
josebui over 7 yearsSorry, you are right, I think this way it is cleaner and easier to follow
-
pagep over 7 yearsThis is the best answer! Most of us already have fs-extra in the app anyway.
-
MikingTheViking about 7 yearsUpvote for the easy, recursive response not requiring an additional library or approach!
-
bryanmac about 7 years@cshotton - are you referring to the comment or the answer? shelljs works even on windows. exec mkdir -p (the comment) of course doesn't.
-
John about 7 yearsThis specifically works with relative paths. If targDir were specified as an absolute path, the split/reduce sequence would loose the context:
/home/name
would end up being evaluated relative the the current working directory as if it werehome/name
. -
Arvin about 7 years@josebui I think it is better using "path.sep" instead of forward slash (/) to avoid environment specefic issues.
-
janos about 7 yearsCongrats on the Populist badge ;-)
-
Christopher Bull almost 7 yearsMissing require statements: const fs = require('fs'); const path = require('path');
-
Mouneer almost 7 years@ChristopherBull, intentionally not added just to focus on the logic, but anyways, I added them. Thanks ;)
-
moodboom almost 7 years12 lines of solid code, zero dependencies, I'll take it every time.
-
Gershom Maes almost 7 yearsIs it necessary to check if the dir exists? Can't you just call
mkdir
either way, and handle the error if necessary? -
John Q over 6 years@Mouneer on Mac OS X 10.12.6, the error thrown when trying to create "/" after passing in an absolute path is "EISDIR" (Error: EISDIR: illegal operation on a directory, mkdir '/'). I think probably checking for dir existence is still the best cross-platform way to go (acknowledging it will be slower).
-
deed02392 over 6 yearsI suspect similar to the OS X error, it fails on Windows because there are no permissions to do a MKDIR on `C:\`. This could be handled by checking if the dir exists already.
-
Mouneer over 6 years@JohnQ, the way I handle the existing directory is the best practice to keep ourselves safe from race conditions. Regarding permissions, I follow Terminal conversions. If no sufficient permissions then fail explicitly. I only handle the already-exists error because it affects the business; If parent directory exists, don't stop and go create children. So, If your use case involves lake of permissions, then you need to catch this exception yourself and certainly re-throw other unrelated exceptions just like what I do.
-
Mouneer over 6 years@deed02392, check my previous comment.
-
deed02392 over 6 years@Mouneer I think it would be more useful to point out you can get around the permissions issue for all non-root non-administrator accounts if you only pass a relative path to build from. Otherwise I'm not sure how you could tell if the perm issue will matter unless you just guess the dir exists and try to create the child.
-
Mouneer over 6 years@deed02392 forgive me, I'm not sure if I understand your point well. But let's say, If I need to handle the perm error I can
try{mkDirByPathSync('/path/to/dir');} catch(e){if (PERMISSION_ERROR) {// Do something}}
. And using a relative or absolute path isn't the actual reason for the perm issues; You may use a relative path but your permissions are not sufficient to create its directories. Also, you can use an absolute path to your home directory for example and create its directories normally as you have the needed permissions. So, could you please guide me to the arguing point? -
deed02392 over 6 years@Mouneer Eg: I am a normal user and I want to create a new dir at:
C:\Users\Me\Desktop
. With your code, there will be a perm error trying tomkdir('C:\')
. And another one trying tomkdir('C:\Users')
. But these paths already exist anyway. If you passed it relative to 'Me' your would be doing.\Desktop\newdir
. You will bypass the EPERM error because you own'Desktop'
(so you'll get EEXIST instead). You are saying you can't check if they exist safely before. I am suggesting that//Do something
cannot be anything useful. I don't think you can provide a full example of handling EPERM. -
Mouneer over 6 years@deed02392 I got your point. Thanks for the example and you're right. I will make a little edit to the snippet to support this use case and let you know. Thanks.
-
deed02392 over 6 yearsOK, good luck, I am hoping it's possible. My feeling is it won't be very clean.
-
Mouneer over 6 years@deed02392 what about catching the perm exception and throw it only if last nested dir failed ^_^
-
deed02392 over 6 yearsDo you mean throw it only if you got an EPERM when trying to create the deepest directory? That will improve it for sure.
-
Mouneer over 6 years@deed02392, I tested the snippet with paths like
/home/publicDir/test
which works as expected even when I have no permissions for directories:/
,/home
,/home/publicDir
. This is because the exception thrown for this directories isEEXIST
and notEACCES
norEPERM
. Try it yourself rextester.com/edit/FJCAC1817. -
deed02392 over 6 years@Mouneer All my examples have been for my platform (Windows). There, the behaviour is EPERM gets raised as I explained :-)
-
Mouneer over 6 years@deed02392, Is this your case rextester.com/XZI35837 ? And does this snippet helps you with your use case?
-
deed02392 over 6 years@Mouneer It works fine on rextester, but I haven't tested it on Windows. Can you do that or do you need me to?
-
Mouneer over 6 years@deed02392 No, never mind thanks for your comments ^_^
-
McAden over 6 yearsWorks fine in windows for me. However, I had to change
const sep = path.sep
because on windows that will give a` but in my gulp scripts all my paths are forward
/`. -
Mouneer over 6 yearsAs per Docs: "
path.sep
provides the platform-specific path segment separator:\
on Windows or/
on POSIX". So, no need to change it. @McAden -
McAden over 6 years@Mouneer Right, that was the problem I'm on Windows, but I'm using
/
-
robyoder about 6 years@Mouneer not sure what happened here, but creating absolute paths still doesn't work for me. I'm on macOS, and I get
Error: EISDIR: illegal operation on a directory, mkdir '/'
. -
Stepan Rafael about 6 yearsThanks. It is the far best method.
-
John Vandivier about 6 yearsnifty trick:
return targetDir.split(sep)...
so you can have the path for your file operation after ensuring the folder -
Playdome.io about 6 yearsfiles on windows are handled with backslash not forward slash. Your code simply won't work there. C:\data\test ...
-
Hamiora about 6 yearsEdited but suggest you validate your comment. On node try the following and see what happens var fs = require('fs') fs.mkdirSync('test') fs.mkdirSync('test\\test1') fs.mkdirSync('test/test2')
-
Playdome.io about 6 yearsWhatever you're saying.., my down vote still stays until you learn to write better code.
-
Hamiora about 6 yearsHaha. Ok, I'll work really hard on learning how to write better code. BTW most answers above, including the OP, use forward slashes. Suggest you stop trolling.
-
Playdome.io about 6 yearsSimply put
path.delimiter
instead of forward slash instead of arguing? Or handle both in the code so then you may get upvoted. -
Pete Alvin about 6 yearsI wish creating all necessary intermediate directories was built-in.
-
Peter T. about 6 yearsI had the problem that this function failed on Windows as it tried to mkdir "C:\" which failed with "EPERM" (not "EEXIST"), so I modified the function as
if (err.code !== 'EEXIST' && (path.sep === "\\" && ! curDir.match(/^[a-z]:\\$/i))) {
to handle this special case in windows. Maybe you want to include this in your code above. -
Qwertie almost 6 yearsPeople often use
/
as a path separator on Windows so that whatever they have written will work on Unix/Linux. So refusing to accept/
on Windows isn't good. Most Windows commands and programs accept both. -
Almenon almost 6 years@Mouneer that fixes the windows error, thanks :) I would also suggest putting in the following line to fix the error on mac: if(curDir == '/') return '/' see github.com/Almenon/AREPL-vscode/blob/…
-
Mouneer almost 6 years@JohnQ Check the updated answer. It's supporting platform-specific errors like EISDIR for Mac. Thanks for reporting. :)
-
Mouneer almost 6 years@deed02392 Check the updated answer. It's supporting platform-specific errors like EPERM and EACCES for Windows. Thanks for reporting :)
-
Mouneer almost 6 years@robyoder Check the updated answer. It's supporting platform-specific errors like EISDIR for Mac Thanks for reporting :)
-
Mouneer almost 6 years@Almenon Check the updated answer. It's supporting platform-specific errors like EISDIR for Mac and EPERM and EACCES for Windows. Thanks for reporting :)
-
Avius almost 6 yearsGood solution. I wrote a unit test for this which helped me spot a tiny mistake in this line:
if (!caughtErr || caughtErr && targetDir === curDir) {
. I am on Windows and if I try to construct an absolute path like somkDirByPathSync('/path/to/dir');
, and get an error so thatcaughtErr
istrue
, thentargetDir
will be\path\to\dir
whilecurDir
wil beC:\path\to\dir
and the condition check will fail (if I understand the intended behaviour correctly). Fix would perhaps be usingpath.resolve(targetDir)
. -
rdm almost 6 yearsWhen I try this, path.delimiter is undefined. (meanwhile, '/' works on windows)
-
Avius over 5 yearsBoth
path.resolve(targetDir)
andpath.resolve(baseDir, targetDir)
seem to work equally well for me and my tests : ] -
Mouneer over 5 yearsSince the path is absolute, both
path.resolve(targetDir)
andpath.resolve(baseDir, targetDir)
will work similarly. @Avius Generally I updated the answer to cover your use case. Thanks for reporting :) -
nurettin over 5 yearsI found the related pull request github.com/nodejs/node/pull/23313
-
Choco Li over 5 yearsIt will throw error when directory exist and stop. Use a try catch block can make it keep creating other non-exist folder.
-
Qwertie over 5 yearsWas the downvote for supporting Windows correctly? Did I mention it works on other OSs too?
-
ChiralMichael over 5 yearsI am pretty late to the party, but I just have a note that your comment about the race condition is superfluous. You still have a race condition (another process may remove the directory between calls to mkdir). I'm always a fan of having exceptions be exceptional. It clarifies intent and also limits the performance impact of throwing an exception. Do the natural thing and bail if the directory gets created underneath you. That's the expected behavior anyways.
-
Mouneer over 5 years@ChiralMichael, thanks for expressing you point of view. Regarding
You still have a race condition
, the scenario you're talking about is what already handled by code now. RegardingDo the natural thing
, the natural thing here is to create the directory. This is the main and the only usage of the function. So, when creating a directory, I expect some failures with the core logic that I should handle. Those failures become handled now and not unhandled. Any other failures that I expect and I cannot handle - like wrong permissions - are left to be bailed normally. -
Илья Зеленько over 5 yearsYou can use this cool function with Promise or callback of your choice.
-
ChiralMichael over 5 years@Mouneer Allow me to restate. There is a race in the iterations of reduce(): an EEXIST error on one iteration, then the directory deleted from under you in another process, then mkdir on the next iteration. In accounting for the race condition of two processes creating the same directory, you've simply pushed the problem around. My second comment: "do the natural thing" is to mean that I would expect any create directory method to fail if the directory was created underneath it. Finally, you structured you algorithm so that an exception (EEXIST) is an expected workflow. This is to be avoided.
-
schnatterer over 5 yearsThis would be great if it offered a possibility to use
memfs
for unit testing. It doesn't :-( github.com/jprichardson/node-fs-extra/issues/274 -
Josh Slate over 5 years
path.sep
is coming through as either / or \\ for me.path.delimiter
is : or ;. -
Rich Apodaca over 5 yearsThis should be the accepted answer. It does not throw if the directory already exists, and can be used with async/await via fs.promises.mkdir.
-
ZzZombo over 5 years@Hamiora: did you post code without testing? There is a lot of blatant errors that make it simply not to work at all... All those shadowed variables... The horror!
-
Mouneer over 5 years@ChiralMichael,
then mkdir on the next iteration
what would happen?I would expect any create directory method to fail if the directory was created underneath it.
I prefer not to fail especially when the directory is a parent directory/parent1/parent2/child
I would - in contrary - thank the second process instead :). It's a business decision anyway. -
Hamiora over 5 yearsHey @zzzombo. My original answer was edited by other contributors, so I don't really consider this my answer anymore. But I wrote and lifted it from my own working code when I posted it, and plus or minus the one shadowed variable, the logic is still sound. Suggest to copy the logic, not the code, but thanks for comment.
-
drorw about 5 yearsHere are some good examples for using mkdirp from open source projects
-
Nika Kasradze about 5 yearsthis is not a solution, this is an alternative to the solution. context: pics.onsizzle.com/…
-
Karim about 4 yearsgood solution because doesn't require node >=10 like the other answers
-
bkwdesign over 3 yearsif i pass in a path
/does/not/exist
it only creates the first leveldoes
folder ;-( -
cyborg over 3 yearsOk, I don't know why but I would have thought that would be extremely easy to see why in your case in debugging.
-
bryanmac almost 3 years@NikaKasradze this is a possible solution and works. All solutions are alternatives.
-
bryanmac almost 3 years@cshotton, this works on platforms where mkdir is not present (windows). That is the point of shelljs - to creata a portable shell experience. Did you try it?