Windows上带通配符的目录中的文件

时间:2018-01-24 17:03:15

标签: c++ winapi

如何从包含通配符的路径轻松获取所有文件路径?例如: C:/ Data * Set / Files * / * .txt 我使用glob函数在Linux上编写了它,但我无法在Windows上执行此操作:/

遗憾的是,

FindFirstFile不支持目录名中的通配符。

我认为应该有Windows解决方案,但我找不到它。

2 个答案:

答案 0 :(得分:3)

因此,您应该取消使用特定于操作系统的文件访问,以支持独立于操作系统:Filesystem Library

假设您已获得filesystem::path input,其中包含带通配符的路径。要使用它来解决您的问题,您需要:

  1. 使用parent_pathinput拆分为目录
  2. 使用filename获取input文件名
  3. 获取directory_iteratorinput开始的相对或绝对路径
  4. 创建一个递归函数,它将beginend迭代器接收到获取的父路径,目录迭代器和文件名
  5. 任何时候目录或文件名使用'*'使用带有迭代器的regex来确定应该进入下一个目录的目录
  6. 返回匹配文件的路径或空path
  7. 由于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;
    }
    

    Live Example

答案 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);

}