Deleting a collection of files recursively with rimraf

Some times you might want to delete a whole bunch of files that exist in a file system structure. If the project you are making is aways going to be running in a POSIX environment, you could use the rm command with a child process, but say you want to make the app more portable. This is where something like rimraf may come in handy.

My test_rimraf project

When testing out any kind of node.js project I often make a test_[name-of-project] folder, and test it out in there, often writing some demo scripts that make use of that project.

If you want to check out my test_rimraf on this post you can find it here.

Making some files

To test out rimraf I first need a way to make some files in a path, so I made a mkfiles.js script that provides a method that helps me make a bunch of files at a given path. I then made two scripts make-basic.js, and make-junk.js that can be used to simulate the creation of a basic file structurer that contains files that I might want to keep, and files that I want gone.

In a real life situation you might have some kind of file system structure composed of an array of different file types, or maybe they follow a certain pattern in the filename. For example you might want to delete all of the *.md files in your node_modules folder, but preserve everything else that may be of use. If this is the case you can skip over this making some files section, and get on with it.

Here is the source of mkfiles if interested.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
var fs = require('fs'),
path = require('path'),
mkdirp = require('mkdirp');
// make a path of do nothing
var mkPath = function (p) {
return new Promise(function (resolve, reject) {
mkdirp(p, function (e) {
if (e) {
reject(e);
}
resolve();
});
});
},
// make a file
mkFile = function (p, end, prefix, data) {
p = p || '.source';
end = end || '.txt';
prefix = prefix || 'test_';
data = data || 'test_data';
return new Promise(function (resolve, reject) {
mkPath(p).catch (function (e) {
reject(e);
}).then(function () {
fs.writeFile(path.join(p, prefix + end), data, 'utf-8', function (e) {
if (e) {
reject(e)
}
resolve();
});
});
});
};
// make files
exports.mkFiles = function (options) {
options = options || {};
var p = options.p || './source',
type = options.type || '.txt',
count = options.count || 10,
prefix = options.prefix || 'test_';
console.log('making files.');
var i = 0;
var make = function () {
var fix = prefix + i;
mkFile(p, type, fix).then(function () {
console.log(p + '/' + fix + type);
i += 1;
if (i < count) {
make();
}
}).catch (function (e) {
console.log(e)
});
};
make();
};

I Then used this in my make-basic script:

1
2
3
var mkFiles = require('./mkfiles.js');
mkFiles.mkFiles();

and also a make-junk script, that builds a more complex example structure.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
var mkFiles = require('./mkfiles.js');
// source/
mkFiles.mkFiles({
p: './source',
type: '.txt'
});
// source/html
mkFiles.mkFiles({
p: './source/html',
type: '.txt'
});
mkFiles.mkFiles({
p: './source/html',
type: '.html'
});
// source/html/css
mkFiles.mkFiles({
p: './source/html/css',
type: '.txt'
});
mkFiles.mkFiles({
p: './source/html/css',
type: '.css'
});

So when calling my make-junk script I end up with a bunch of *.html, *.css, and *.txt files. This represents a situation in which I want to delete the *.txt files while not touching everything else. Get it? good moving on.

Deleting all files of a certain type recursively

So this is pretty much the whole point of using rimraf, to go over the whole of a file structure and delete all files that fix a certain glob pattern.

1
2
3
4
5
6
7
8
var rimraf = require('rimraf');
rimraf('./source/**/*.txt', function (e) {
console.log(e);
console.log('okay');
});

Notice the ** glob, this will cause rimraf to search the whole structure in the source folder for text files and delete them.

Plain JS alternative

You may be of a mindset where you always think to yourself “do I really need this dependency”. You may often think about how hard it might be to put together something on your own, just working with what there is to play with when it comes to the node.js core modules.

As such yes it is not to hard to get something together to do this. I was able to quickly throw together something using fs.readdir, fs.lstatSync, and fs.unlink.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
var fs = require('fs'),
path = require('path'),
matchPat = /\.txt$/,
readDir = function (dir, forItem) {
forItem = forItem || function (itemLoc) {
console.log(itemLoc)
};
// read the given dir
fs.readdir(dir, function (err, content) {
// for all contents in the path
content.forEach(function (item) {
itemLoc = path.join(dir, '/' + item);
// if a dir, continue recursively
if (fs.lstatSync(itemLoc).isDirectory()) {
readDir(itemLoc, forItem);
}
// log the current item
forItem(itemLoc);
});
});
};
// call readDir with the given forItem method.
readDir('./source', function (itemLoc) {
// if there is a match
if (itemLoc.match(matchPat)) {
console.log(itemLoc.match(matchPat));
// unlink (delete)
fs.unlink(itemLoc, function (e) {
if (e) {
console.log(e);
}
});
}
});

Keep in mind this is something I put together in about fifteen minutes maybe. I could invest some more time, and make it a bit more robust. It could make use of promises, work well as a CLI tool, so forth, and so on. It may be a bit time consuming to get this solution to work well with respect to a wide range of use case scenarios.

Conclusion

rimraf works pretty well for this task. It might be nice to have something that does this that also has RegEx support, but most of the time globs get the job done just fine.

Be sure to check out my other posts on node.js, and npm packages.