I need to get a list of folders in the directory, but only the folders. No files are needed. Only folders. I use filters to determine if this is a folder, but they do not work and all files and folders are output.
string root = "D:\\*";
cout << "Scan " << root << endl;
std::wstring widestr = std::wstring(root.begin(), root.end());
const wchar_t* widecstr = widestr.c_str();
WIN32_FIND_DATAW wfd;
HANDLE const hFind = FindFirstFileW(widecstr, &wfd);
In this way, I check that it is a folder.
if (INVALID_HANDLE_VALUE != hFind)
if (wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
if (!(wfd.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT))
How to solve a problem?
There are two ways to do this: The hard way, and the easy way.
The hard way is based on FindFirstFile and FindNextFile, filtering out directories as needed. You will find a bazillion samples that outline this approach, both on Stack Overflow as well as the rest of the internet.
The easy way: Use the standard directory_iterator class (or recursive_directory_iterator, if you need to recurse into subdirectories). The solution is as simple as1:
for ( const auto& entry : directory_iterator( path( L"abc" ) ) ) {
if ( is_directory( entry.path() ) ) {
// Do something with the entry
visit( entry.path() );
}
}
You will have to include the <filesystem> header file, introduced in C++17.
Note: Using the latest version of Visual Studio 2017 (15.3.5), this is not yet in namespace std. You will have to reference namespace std::experimental::filesystem instead.
1 Note in particular, that there is no need to filter out the . and .. pseudo-directories; those aren't returned by the directory iterators.
This function collects folders into given vector. If you set recursive to true, it will be scanning folders inside folders inside folders etc.
// TODO: proper error handling.
void GetFolders( std::vector<std::wstring>& result, const wchar_t* path, bool recursive )
{
HANDLE hFind;
WIN32_FIND_DATA data;
std::wstring folder( path );
folder += L"\\";
std::wstring mask( folder );
mask += L"*.*";
hFind=FindFirstFile(mask.c_str(),&data);
if(hFind!=INVALID_HANDLE_VALUE)
{
do
{
std::wstring name( folder );
name += data.cFileName;
if ( ( data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY )
// I see you don't want FILE_ATTRIBUTE_REPARSE_POINT
&& !( data.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT ) )
{
// Skip . and .. pseudo folders.
if ( wcscmp( data.cFileName, L"." ) != 0 && wcscmp( data.cFileName, L".." ) != 0 )
{
result.push_back( name );
if ( recursive )
// TODO: It would be wise to check for cycles!
GetFolders( result, name.c_str(), recursive );
}
}
} while(FindNextFile(hFind,&data));
}
FindClose(hFind);
}
Modified from https://stackoverflow.com/a/46511952/8666197
Related
I have written a Java program that at one point counts the number of folders in a directory. I would like to translate this program into C++ (I'm trying to learn it). I've been able to translate most of the program, but I haven't been able to find a way to count the subdirectories of a directory. How would I accomplish this?
Thanks in advance
Here is an implementation using the Win32 API.
SubdirCount takes a directory path string argument and it returns a count of its immediate child subdirectories (as an int). Hidden subdirectories are included, but any named "." or ".." are not counted.
FindFirstFile is a TCHAR-taking alias which ends up as either FindFirstFileA or FindFirstFileW. In order to keep strings TCHAR, without assuming availabilty of CString, the example here includes some awkward code just for appending "/*" to the function's argument.
#include <tchar.h>
#include <windows.h>
int SubdirCount(const TCHAR* parent_path) {
// The hideous string manipulation code below
// prepares a TCHAR wildcard string (sub_wild)
// matching any subdirectory immediately under
// parent_path by appending "\*"
size_t len = _tcslen(parent_path);
const size_t alloc_len = len + 3;
TCHAR* sub_wild = new TCHAR[alloc_len];
_tcscpy_s(sub_wild, alloc_len, parent_path);
if(len && sub_wild[len-1] != _T('\\')) { sub_wild[len++] = _T('\\'); }
sub_wild[len++] = _T('*');
sub_wild[len++] = _T('\0');
// File enumeration starts here
WIN32_FIND_DATA fd;
HANDLE hfind;
int count = 0;
if(INVALID_HANDLE_VALUE != (hfind = FindFirstFile(sub_wild, &fd))) {
do {
if(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
// is_alias_dir is true if directory name is "." or ".."
const bool is_alias_dir = fd.cFileName[0] == _T('.') &&
(!fd.cFileName[1] || (fd.cFileName[1] == _T('.') &&
!fd.cFileName[2]));
count += !is_alias_dir;
}
} while(FindNextFile(hfind, &fd));
FindClose(hfind);
}
delete [] sub_wild;
return count;
}
I just copied all my cds to my computer with a program called "Sound Juicer". It works fine, it creates for each artist a folder and it it for each album another folder. And of course in these folders the mp3 files.
The problem is I want the Tracknumber, the Artist and then the Tracktitle as name for my songs. What Sound Juicer does is adding d1t in front of the file which stands for "Disk 1 Title".
I'm a programmer so I used this problem to practice a little bit. This works :
void MainWindow::rename( const QString & text )
{
static int _files = 0;
QDir dir( text );
QFileInfoList a = dir.entryInfoList( QDir::Files | QDir::Dirs );
for( int i = 2; i < a.size(); i++ )
{
static QDir tmp;
if( a.at( i ).isDir() )
rename( a.at( i ).absoluteFilePath() );
if( a.at( i ).fileName().startsWith( "d1t" ) || a.at( i ).fileName().startsWith( "d2t" ) )
{
QString newFile = a.at( i ).fileName().remove(0,3);
tmp = a.at( i ).dir();
if( !tmp.rename( a.at( i ).fileName(), newFile ) )
qDebug() << "Failed";
_files++;
}
}
}
It checks a directory, selects the first file or directory and checks what it is. If it is a directory it calls itself (recursion) and starts again until he finds some files or no more directories exist. If a file is found, it renames it and adds 1 to the file counter.
However, it only renamed all files in the first 2 or 3 directories. After that it caused a SIGSEGV. Does anyone knows whats wrong?
Example of my directories :
1 Directory ("Sum 41") -> 1 Subdirectory ("All Killer No Filler") ->
Files "d1t01. Sum 41 - Introduction to Destruction.mp3" etc. ...
2 Subdirectory ("Blah Blah") -> Files ...
2 Directory ("Shinedown") -> 1 Subdirectory ("Sound of Madness") ->
Files d1t01. Shinedown - Devour.mp3 etc...
3 Directory ("Guns N’ Roses") -> Subdirectory ("Blah Blah") -> files ...
Subdirectory ("Blah ") -> files ...
With static you try to tell the compiler you want only one instance in the whole program, yet you put it in a for loop. That is not really clean. In this situation static is useless. Static can also cause problems due to the static initialization chaos problem. So if you dont really need static initialization, drop it and make it a local or a class variable.
Rewriting your method, to be able to read it better, i saw that you call the method recursively and that could cause an infinite loop.
Another problem is your files variable, what is the purpose of it?
void MainWindow::rename( const QString & text ) {
int files = 0;
QDir dir(text); // does QDir also accept wrong paths?
QFileInfoList list = dir.entryInfoList(QDir::Files|QDir::Dirs); // does it return a list in all cases?
foreach (QFileInfo entry, list) {
if (entry.isDir()) {
//is everything always ok when doing this?
// POTENTIAL infinite loop
rename(entry.absoluteFilePath());
}
else {
QString fileName = entry.fileName();
if(fileName.startsWith("d1t") || fileName.startsWith("d2t")) {
if (!entry.dir().rename(fileName, fileName.remove(0,3))) qDebug() << "Failed";
files++;
}
}
}
}
I have with the help of fonZ and Frank Osterfeld somehow fixed it. I finally created a GUI to select just a single path. However, I have done it. This piece of code looks up all directories without causing an infinite loop or an overflow (so far). The problem was something with QDir's and QFileInfo's functions to get the path. I played a while with it and this came out :
void MainWindow::rename( const QString& path )
{
//If invalid path return
if( path.isEmpty() || ( !QDir( path ).exists() ) )
return;
//entryInfoList( QDir::NoDotAndDotDot ) doesn't work.
QFileInfoList fileList = QDir( path ).entryInfoList();
foreach (QFileInfo entry, fileList ) {
//Eliminating wrong paths
if( entry.isDir() ){
if( entry.filePath().endsWith(".") )
continue;
//Start function again with new directory
rename( entry.filePath() );
}
else{
QString fileName = entry.fileName();
if( fileName.startsWith( "d1t" ) || fileName.startsWith( "d2t" ) ){
//Remove those characters
fileName.remove( 0, 3 );
//If renaming is successful, increment the successful files
//If not, increment the failed files and print an error
if( entry.dir().rename( entry.fileName(), fileName ) )
files++;
else{
addError( "Could not rename " + entry.fileName() + " to " + fileName );
filesFailed++;
}
}
}
}
}
I want to iterate over all files in a directory matching something "keyword.txt". I searched for some solutions in google and found this:
Can I use a mask to iterate files in a directory with Boost?
As i figured out later on, the "leaf()" function was replaced (source: http://www.boost.org/doc/libs/1_41_0/libs/filesystem/doc/index.htm -> goto section 'Deprecated names and features')
what i got so far is this, but it's not running. Sorry for this somehow stupid question, but im more or less a c++ beginner.
const std::string target_path( "F:\\data\\" );
const boost::regex my_filter( "keyword.txt" );
std::vector< std::string > all_matching_files;
boost::filesystem::directory_iterator end_itr; // Default ctor yields past-the-end
for( boost::filesystem::directory_iterator i( target_path ); i != end_itr; ++i )
{
// Skip if not a file
if( !boost::filesystem::is_regular_file( i->status() ) ) continue;
boost::smatch what;
// Skip if no match
if( !boost::regex_match( i->path().filename(), what, my_filter ) ) continue;
// File matches, store it
all_matching_files.push_back( i->path().filename() );
}
Try
i->path().filename().string()
this is the equivalent for i->leaf() in boost::filesystem 3.0
In your code:
// Skip if no match
if( !boost::regex_match( i->path().filename().string(), what, my_filter ) )
continue;
// File matches, store it
all_matching_files.push_back( i->path().filename().string() );
I need to save all ".xml" file names in a directory to a vector. To make a long story short, I cannot use the dirent API. It seems as if C++ does not have any concept of "directories".
Once I have the filenames in a vector, I can iterate through and "fopen" these files.
Is there an easy way to get these filenames at runtime?
Easy way is to use Boost.Filesystem library.
namespace fs = boost::filesystem;
// ...
std::string path_to_xml = CUSTOM_DIR_PATH;
std::vector<string> xml_files;
fs::directory_iterator dir_iter( static_cast<fs::path>(path_to_xml) ), dir_end;
for (; dir_iter != dir_end; ++dir_iter ) {
if ( boost::iends_with( boost::to_lower_copy( dir_iter->filename() ), ".xml" ) )
xml_files.push_back( dir_iter->filename() );
}
I suggest having a look at boost::filesystem if it should be portable and bringing boost in isn't too heavy.
If you don't like boost, try Poco. It has a DirectoryIterator. http://pocoproject.org/
Something like this (Note, Format is a sprintf:ish funciton you can replace)
bool MakeFileList(const wchar_t* pDirectory,vector<wstring> *pFileList)
{
wstring sTemp = Format(L"%s\\*.%s",pDirectory,L"xml");
_wfinddata_t first_file;
long hFile = _wfindfirst(sTemp.c_str(),&first_file);
if(hFile != -1)
{
wstring sFile = first_file.name;
wstring sPath = Format(L"%s%s",pDirectory,sFile.c_str());
pFileList->push_back(sPath);
while(_wfindnext(hFile,&first_file) != -1)
{
wstring sFile = first_file.name;
wstring sPath = Format(L"%s%s",pDirectory,sFile.c_str());
pFileList->push_back(sPath);
}
_findclose(hFile);
}else
return false;
return true;
}
What is the cleanest way to recursively search for files using C++ and MFC?
EDIT: Do any of these solutions offer the ability to use multiple filters through one pass? I guess with CFileFind I could filter on *.* and then write custom code to further filter into different file types. Does anything offer built-in multiple filters (ie. *.exe,*.dll)?
EDIT2: Just realized an obvious assumption that I was making that makes my previous EDIT invalid. If I am trying to do a recursive search with CFileFind, I have to use *.* as my wildcard because otherwise subdirectories won't be matched and no recursion will take place. So filtering on different file-extentions will have to be handled separately regardless.
Using CFileFind.
Take a look at this example from MSDN:
void Recurse(LPCTSTR pstr)
{
CFileFind finder;
// build a string with wildcards
CString strWildcard(pstr);
strWildcard += _T("\\*.*");
// start working for files
BOOL bWorking = finder.FindFile(strWildcard);
while (bWorking)
{
bWorking = finder.FindNextFile();
// skip . and .. files; otherwise, we'd
// recur infinitely!
if (finder.IsDots())
continue;
// if it's a directory, recursively search it
if (finder.IsDirectory())
{
CString str = finder.GetFilePath();
cout << (LPCTSTR) str << endl;
Recurse(str);
}
}
finder.Close();
}
Use Boost's Filesystem implementation!
The recursive example is even on the filesystem homepage:
bool find_file( const path & dir_path, // in this directory,
const std::string & file_name, // search for this name,
path & path_found ) // placing path here if found
{
if ( !exists( dir_path ) ) return false;
directory_iterator end_itr; // default construction yields past-the-end
for ( directory_iterator itr( dir_path );
itr != end_itr;
++itr )
{
if ( is_directory(itr->status()) )
{
if ( find_file( itr->path(), file_name, path_found ) ) return true;
}
else if ( itr->leaf() == file_name ) // see below
{
path_found = itr->path();
return true;
}
}
return false;
}
I know it is not your question, but it is also easy to to without recursion by using a CStringArray
void FindFiles(CString srcFolder)
{
CStringArray dirs;
dirs.Add(srcFolder + "\\*.*");
while(dirs.GetSize() > 0) {
CString dir = dirs.GetAt(0);
dirs.RemoveAt(0);
CFileFind ff;
BOOL good = ff.FindFile(dir);
while(good) {
good = ff.FindNextFile();
if(!ff.IsDots()) {
if(!ff.IsDirectory()) {
//process file
} else {
//new directory (and not . or ..)
dirs.InsertAt(0,nd + "\\*.*");
}
}
}
ff.Close();
}
}
Check out the recls library - stands for recursive ls - which is a recursive search library that works on UNIX and Windows. It's a C library with adaptations to different language, including C++. From memory, you can use it something like the following:
using recls::search_sequence;
CString dir = "C:\\mydir";
CString patterns = "*.doc;abc*.xls";
CStringArray paths;
search_sequence files(dir, patterns, recls::RECURSIVE);
for(search_sequence::const_iterator b = files.begin(); b != files.end(); b++) {
paths.Add((*b).c_str());
}
It'll find all .doc files, and all .xls files beginning with abc in C:\mydir or any of its subdirectories.
I haven't compiled this, but it should be pretty close to the mark.
CString strNextFileName , strSaveLog= "C:\\mydir";
Find.FindFile(strSaveLog);
BOOL l = Find.FindNextFile();
if(!l)
MessageBox("");
strNextFileName = Find.GetFileName();
Its not working. Find.FindNextFile() returning false even the files are present in the same directory``