如何从包含通配符的路径轻松获取所有文件路径?例如: C:/ Data * Set / Files * / * .txt 我使用glob
函数在Linux上编写了它,但我无法在Windows上执行此操作:/
FindFirstFile
不支持目录名中的通配符。
我认为应该有Windows解决方案,但我找不到它。
答案 0 :(得分:3)
因此,您应该取消使用特定于操作系统的文件访问,以支持独立于操作系统:Filesystem Library
假设您已获得filesystem::path input
,其中包含带通配符的路径。要使用它来解决您的问题,您需要:
parent_path
将input
拆分为目录filename
获取input
文件名directory_iterator
到input
开始的相对或绝对路径begin
和end
迭代器接收到获取的父路径,目录迭代器和文件名'*'
使用带有迭代器的regex
来确定应该进入下一个目录的目录path
由于the excellent Ben Voigt's comment我已更新算法以跨越非野外目录。
例如:
regex GenerateRegex(string& arg) {
for (auto i = arg.find('*'); i != string::npos; i = arg.find('*', i + 2)) {
arg.insert(i, 1, '.');
}
return regex(arg);
}
filesystem::path FindFirstFile(filesystem::path directory, filesystem::path::const_iterator& start, const filesystem::path::const_iterator& finish, string& filename) {
while (start != finish && start->string().find('*') == string::npos) {
directory /= *start++;
}
filesystem::directory_iterator it(directory);
filesystem::path result;
if (it != filesystem::directory_iterator()) {
if (start == finish) {
for (auto i = filename.find('.'); i != string::npos; i = filename.find('.', i + 2)) {
filename.insert(i, 1, '\\');
}
const auto re = GenerateRegex(filename);
do {
if (!filesystem::is_directory(it->status()) && regex_match(it->path().string(), re)) {
result = *it;
break;
}
} while (++it != filesystem::directory_iterator());
}
else {
const auto re = GenerateRegex(start->string());
do {
if (it->is_directory() && regex_match(prev(it->path().end())->string(), re)) {
result = FindFirstFile(it->path(), next(start), finish, filename);
if (!result.empty()) {
break;
}
}
} while (++it != filesystem::directory_iterator());
}
}
return result;
}
可以通过以下方式调用:
const filesystem::path input("C:/Test/Data*Set/Files*/*.txt");
if (input.is_absolute()) {
const auto relative_parent = input.parent_path().relative_path();
cout << FindFirstFile(input.root_path(), begin(relative_parent), end(relative_parent), input.filename().string()) << endl;
} else {
const auto parent = input.parent_path();
cout << FindFirstFile(filesystem::current_path(), begin(parent), end(parent), input.filename().string()) << endl;
}
答案 1 :(得分:3)
需要了解FindFirstFile[Ex]
的工作原理。这是NtQueryDirectoryFile
以上的shell。 FindFirstFile[Ex]
需要将输入名称划分为文件夹名称(将用作 FileHandle 打开)和用作 FileName 的搜索掩码。 mask只能在文件名中。文件夹必须具有不带通配符的确切名称才能首先打开。
结果FindFirstFile[Ex]
始终打开具体的单个文件夹并通过掩码在此文件夹中搜索。对于递归搜索文件 - 我们需要递归调用FindFirstFile[Ex]
。通常我们在所有级别使用相同的常量搜索掩码。例如,当我们想要从X:\SomeFolder
开始查找所有文件时,我们首先在级别0上使用FindFirstFile[Ex]
调用X:\SomeFolder\*
。如果我们找到SomeSubfolder
,我们会调用FindFirstFile[Ex]
1级X:\SomeFolder\SomeSubfolder\*
,依此类推。但我们可以在不同级别使用不同的搜索掩码。第0级Data*Set
,第1级Files*
,第2级*.txt
所以我们需要递归调用FindFirstFileEx
,并且在不同的递归级别上使用不同的掩码。例如,我们想找到c:\Program*\*\*.txt
。我们需要从c:\Program*
开始,然后为每个已创建的结果附加\*
掩码,然后在下一级添加\*.txt
。或者我们可以例如想要下一个 - 通过下一个掩码搜索文件 - c:\Program Files*\Internet Explorer\*
具有任何深层次。我们可以使用常量深度搜索文件夹掩码(可选),并在所有更深层次上使用最终掩码(也可选)。
所有这些都可能真的不那么难以实现:
struct ENUM_CONTEXT : WIN32_FIND_DATA
{
PCWSTR _szMask;
PCWSTR *_pszMask;
ULONG _MaskCount;
ULONG _MaxLevel;
ULONG _nFiles;
ULONG _nFolders;
WCHAR _FileName[MAXSHORT + 1];
void StartEnum(PCWSTR pcszRoot, PCWSTR pszMask[], ULONG MaskCount, PCWSTR szMask, ULONG MaxLevel, PSTR prefix)
{
SIZE_T len = wcslen(pcszRoot);
if (len < RTL_NUMBER_OF(_FileName))
{
memcpy(_FileName, pcszRoot, len * sizeof(WCHAR));
_szMask = szMask, _pszMask = pszMask, _MaskCount = MaskCount;
_MaxLevel = szMask ? MaxLevel : MaskCount;
_nFolders = 0, _nFolders = 0;
Enum(_FileName + len, 0, prefix);
}
}
void Enum(PWSTR pszEnd, ULONG nLevel, PSTR prefix);
};
void ENUM_CONTEXT::Enum(PWSTR pszEnd, ULONG nLevel, PSTR prefix)
{
if (nLevel > _MaxLevel)
{
return ;
}
PCWSTR lpFileName = _FileName;
SIZE_T cb = lpFileName + RTL_NUMBER_OF(_FileName) - pszEnd;
PCWSTR szMask = nLevel < _MaskCount ? _pszMask[nLevel] : _szMask;
SIZE_T cchMask = wcslen(szMask) + 1;
if (cb < cchMask + 1)
{
return ;
}
*pszEnd++ = L'\\', cb--;
DbgPrint("%s[<%.*S>]\n", prefix, pszEnd - lpFileName, lpFileName);
memcpy(pszEnd, szMask, cchMask * sizeof(WCHAR));
ULONG dwError;
HANDLE hFindFile = FindFirstFileEx(lpFileName, FindExInfoBasic, this, FindExSearchNameMatch, 0, FIND_FIRST_EX_LARGE_FETCH);
if (hFindFile != INVALID_HANDLE_VALUE)
{
PWSTR FileName = cFileName;
do
{
SIZE_T FileNameLength = wcslen(FileName);
switch (FileNameLength)
{
case 2:
if (FileName[1] != '.') break;
case 1:
if (FileName[0] == '.') continue;
}
if (dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
{
_nFolders++;
if (!(dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT))
{
if (cb < FileNameLength)
{
__debugbreak();
}
else
{
memcpy(pszEnd, FileName, FileNameLength * sizeof(WCHAR));
Enum(pszEnd + FileNameLength, nLevel + 1, prefix - 1);
}
}
}
else if (nLevel >= _MaskCount || (!_szMask && nLevel == _MaskCount - 1))
{
_nFiles++;
DbgPrint("%s%u%u <%.*S>\n", prefix, nFileSizeLow, nFileSizeHigh, FileNameLength, FileName);
}
} while (FindNextFile(hFindFile, this));
if ((dwError = GetLastError()) == ERROR_NO_MORE_FILES)
{
dwError = NOERROR;
}
FindClose(hFindFile);
}
else
{
dwError = GetLastError();
}
if (dwError && dwError != ERROR_FILE_NOT_FOUND)
{
DbgPrint("%s[<%.*S>] err = %u\n", prefix, pszEnd - lpFileName, lpFileName, dwError);
}
}
void Test(PCWSTR pcszRoot)
{
char prefix[MAXUCHAR + 1];
memset(prefix, '\t', RTL_NUMBER_OF(prefix) - 1);
prefix[RTL_NUMBER_OF(prefix) - 1] = 0;
ENUM_CONTEXT ectx;
static PCWSTR Masks[] = { L"Program*", L"*", L"*.txt" };
static PCWSTR Masks2[] = { L"Program*", L"*" };
static PCWSTR Masks3[] = { L"Program Files*", L"Internet Explorer" };
// search Program*\*\*.txt with fixed deep level
ectx.StartEnum(pcszRoot, Masks, RTL_NUMBER_OF(Masks), 0, RTL_NUMBER_OF(prefix) - 1, prefix + RTL_NUMBER_OF(prefix) - 1);
// search *.txt files from Program*\*\ - any deep level
ectx.StartEnum(pcszRoot, Masks2, RTL_NUMBER_OF(Masks2), L"*.txt", RTL_NUMBER_OF(prefix) - 1, prefix + RTL_NUMBER_OF(prefix) - 1);
// search all files (*) from Program Files*\Internet Explorer\
ectx.StartEnum(pcszRoot, Masks3, RTL_NUMBER_OF(Masks3), L"*", RTL_NUMBER_OF(prefix) - 1, prefix + RTL_NUMBER_OF(prefix) - 1);
}