I was writing some code yesterday that needed to enumerate all files in a directory. So I wrote what I've been writing since the beginning of time:
var files = Directory.GetFiles("*.*", new EnumerationOptions {
RecurseSubdirectories = true
});
At first, it seemed to be working just fine but then I noticed that hidden files were not showing up. And then I noticed that when I ran it on my Mac (essentially Linux) that files like ".gitignore" and directories like ".vscode", ".config", and ".git" weren't showing up.
After much searching, I was surprised to not find a simple answer. Pretty much everything I found was focused on Windows filesystem and assumed that the standard "*.*" wildcard pattern was going to work. Well, in the cross-platform world, that doesn't always work.
The Problem
The standard *.*
wildcard that we've all been using without thinking too hard about it actually means "match any file that contains a dot in the filename with characters before and after the dot. If the file you want is something like readme.txt
because there's a dot, there's text before the dot, and there's text after the dot so it works. But if your filename is .gitignore
or your directory name is .vscode
then *.*
doesn't match because there's a dot but there isn't any text before the dot.
EnumerationOptions has a property called AttributesToSkip and the default skips value for that property skips hidden files and system files. On linux and MacOS, any file that starts with a dot ('.') is automatically considered a hidden file or a hidden directory.
The Solution
There are two parts to the solution. The first part of the solution is to dump *.*
as the wildcard in favor of simply *
. This says bring back all files as long as there are any characters at all in the filename -- never mind any of those pesky dots.
The second part of the solution is to provide a new value for the AttributesToSkip property of EnumerationOptions.
var files = Directory.GetFiles(path, "*", new EnumerationOptions { RecurseSubdirectories = true, **AttributesToSkip = default** });
BTW, if you're using an older version of .NET Core then you might need to replace that
AttributesToSkip = default
with
AttributesToSkip = default(FileAttributes)
.
Summary
Well anyway...that whole thing was harder than I would have expected. Hopefully this blog post gets someone else to a solution in a lot less time than it took me.
-Ben