Stuck using dirent.h to find all files in subfolders C++ - c++

I'm currently stuck using dirent.h extension. The goal is to give the function a path directory to start from. That function would then look through all the subfolders from that directory and find files in it. It all works until there are two folders in the same directory. Then the program stubbornly chooses one folder and ignores the other.
Here goes my mess of a code.
(The issue is commented at the bottom of the code)
#include <iostream>
#include "dirent.h"
#include <windows.h>
DWORD getPathType(const char *path); //checks if path leads to folder or file
const char *dirlist[20]; //contains all directories (not files, only dir's)
int dirAm = 0; //specifies amount of directories
void loadTextures(const char *loaddir) {
dirlist[dirAm] = loaddir; //specifies starting directory
dirAm++;
for(int i = 0; i<20; i++) {
DIR *dir;
struct dirent *ent;
const char *currentdir = dirlist[i]; //stores current directory
dir = opendir(currentdir); //opens current directory
if (dir != NULL) {
std::cout << "[OPENING dir]\t" << currentdir << std::endl;
while ((ent = readdir(dir)) != NULL) {
const char *filename; //stores current file/folder name
char fullDirName[100]; //stores full path name (current dir+file name, for example /images/+image1.png)
DWORD filetype; //checking path type (file/folder)
filename = ent->d_name; //gets current file name
strcpy(fullDirName, currentdir); //concats current directory and file name to get full path, for example /images/image1.png
strcat(fullDirName, filename);
filetype = getPathType(fullDirName); //gets path type
if (filetype == FILE_ATTRIBUTE_DIRECTORY) {
//if its a directory add it to the list of directories, dirlist, the naming process is the same as above
const char *filenameIn;
char fullDirNameIn[100];
filenameIn = ent->d_name;
strcpy(fullDirNameIn, currentdir);
strcat(fullDirNameIn, filenameIn);
strcat(fullDirNameIn, "/");
std::cout << "[FOUND dir]\t" << fullDirNameIn<<std::endl;
dirlist[dirAm] = fullDirNameIn;
dirAm++;
/* Here is the problem! The cout line above finds all
folders in a directory and saves them in the array, but as soon as the new for
loop iteration starts, the values in the dirlist array... change? And I have no
idea what is going on */
} else {
std::cout << "[FOUND file]\t" << fullDirName << std::endl;
}
}
}
}
And here is the getPathType() function. Pretty straight forward, I guess.
DWORD getPathType(const char *path) {
DWORD fileat;
fileat = GetFileAttributesA(path);
return fileat;
}
Finally, here is the console output:
[OPENING dir] img/ <- opens starting dir
[FOUND dir] img/lvl0/ <- finds lvl0, should store it in dirlist
[FOUND dir] img/lvl1/ <- finds lvl1
[OPENING dir] img/lvl1/
[FOUND file] img/lvl1/player2.png
[OPENING dir] img/lvl1/ <- only opens lvl1
[FOUND file] img/lvl1/player2.png
I know this is a very big question, but I would be quite thankful if someone could share ideas on this.

You are ignoring the scope of your char arrays. Effectively you are doing this
const char *dirlist[20];
while (...)
{
char fullDirNameIn[100];
....
dirlist[dirAm] = fullDirNameIn;
}
The problem is that your array is scoped to the body of while loop but you are storing a pointer to that array outside the while loop. After you exit the body of the loop (i.e. when you iterate) then contents of your array become undefined, but you still have a pointer to it.
The solution is easy and this should be a lesson well learned. Don't use pointers, do what experienced programmers do and use std::string instead.
std::string dirlist[20];
while (...)
{
std::string fullDirNameIn;
....
dirlist[dirAm] = fullDirNameIn;
}

Related

How to give folder of images as input to Magick++ api?

I am need of passing a folder of images as input to Magick++ api. It can be done using mogrify in commandline as shown in post "ImageMagick script to resize folder of images". Reading a single image could be done through api call as
Image image(inputimage)
But how could we do the same for a folder of images? Can anyone help me with the respective api call?
That feature is not included in the Magick++ API. You will need to iterate the directory yourself and then use the Magick++ API to read and write the image. You can find an example on how to iterate through a folder in C/C++ in the following Stack Overflow post: How can I get the list of files in a directory using C or C++?.
I believe you would be responsible of reading the directory. The C library dirent.h is the first thing I think of, but I'm sure there's better C++/system/framework techniques.
#include <iostream>
#include <vector>
#include <dirent.h>
#include <Magick++.h>
int main(int argc, const char * argv[]) {
std::vector<Magick::Image> stack; // Hold images found
DIR * dir_handler = opendir("/tmp/images"); // Open dir
struct dirent * dir_entry;
if (dir_handler != NULL)
{
// Iterate over entries in directory
while ( (dir_entry = readdir(dir_handler)) != NULL ) {
// Only act on regular files
if (dir_entry->d_type == DT_REG) {
// Concatenate path (could be better)
std::string filename("/tmp/images/");
filename += dir_entry->d_name;
// Read image at path
stack.push_back(Magick::Image(filename));
}
}
closedir(dir_handler); // House keeping
} else {
// Handle DIR error
}
// Append all images into single montage
Magick::Image output;
Magick::appendImages(&output, stack.begin(), stack.end());
output.write("/tmp/all.png");
return 0;
}
There's also ExpandFilenames(int *,char ***) in the MagickCore library.
// Patterns to scan
int pattern_count = 1;
// First pattern
char pattern[PATH_MAX] = "/tmp/images/*.png";
// Allocate memory for list of patterns
char ** dir_pattern = (char **)MagickCore::AcquireMagickMemory(sizeof(char *));
// Assign first pattern
dir_pattern[0] = pattern;
// Expand patterns
Magick::MagickBooleanType ok;
ok = MagickCore::ExpandFilenames(&pattern_count, &dir_pattern);
if (ok == Magick::MagickTrue) {
std::vector<Magick::Image> stack;
// `pattern_count' now holds results count
for ( int i = 0; i < pattern_count; ++i) {
// `dir_pattern' has been re-allocated with found results
std::string filename(dir_pattern[i]);
stack.push_back(Magick::Image(filename));
}
Magick::Image output;
Magick::appendImages(&output, stack.begin(), stack.end());
output.write("/tmp/all.png");
} else {
// Error handle
}

opening a file in random named folder using c/c++

I'm trying to code a program where it opens and reads a file automatically. But the problem is the file is stored in a folder which name is unknown. I only know where the folder is located and the file's name. How to get to that file's path in char* ?
Edit: example: d:\files\<random folder>\data.txt
I don't know the name of random folder but I know that it exists in d:\files
Since this is tagged windows, you might as well use the Windows API functions:
FindFirstFile()
FindNextFile()
to enumerate and loop through all the files in a given directory.
To check for a directory, look at dwFileAttributes contained in the WIN32_FIND_DATA structure (filled by the calls to Find...File()). But make sure to skip . and .. directories. If needed, this can be done recursively.
You can check the links for some examples, or see Listing the Files in a Directory.
In case you are using MFC, you can use CFileFind (which is a wrapper around the API functions):
CFileFind finder;
BOOL bWorking = finder.FindFile(_T("*.*"));
while (bWorking)
{
bWorking = finder.FindNextFile();
TRACE(_T("%s\n"), (LPCTSTR)finder.GetFileName());
}
Just for fun, I implemented this using the new, experimental <filesystem> FS Technical Specification supported by GCC 5.
#include <iostream>
#include <experimental/filesystem>
// for readability
namespace fs = std::experimental::filesystem;
int main(int, char* argv[])
{
if(!argv[1])
{
std::cerr << "require 2 parameters, search directory and filename\n";
return EXIT_FAILURE;
}
fs::path search_dir = argv[1];
if(!fs::is_directory(search_dir))
{
std::cerr << "First parameter must be a directory: " << search_dir << '\n';
return EXIT_FAILURE;
}
if(!argv[2])
{
std::cerr << "Expected filename to search for\n";
return EXIT_FAILURE;
}
// file to search for
fs::path file_name = argv[2];
const fs::directory_iterator dir_end; // directory end sentinel
// used to iterate through each subdirectory of search_dir
fs::directory_iterator dir_iter(search_dir);
for(; dir_iter != dir_end; ++dir_iter)
{
// skip non directories
if(!fs::is_directory(dir_iter->path()))
continue;
// check directory for file
// iterate through files in this subdirectory dir_iter->path()
auto file_iter = fs::directory_iterator(dir_iter->path());
for(; file_iter != dir_end; ++file_iter)
{
// ignore directories and wrong filenames
if(fs::is_directory(file_iter->path())
|| file_iter->path().filename() != file_name)
continue;
// Ok we found it (the first one)
std::cout << "path: " << file_iter->path().string() << '\n';
return EXIT_SUCCESS;
}
}
// Not found
std::cout << file_name << " was not found in " << search_dir.string() << '\n';
return EXIT_FAILURE;
}
The idea is: list the directories under d:\files and try to open
the file in each directory.
There isn't (yet) a standard C++ way of getting all the existing files/directories. A crude but easy way of doing this would be
system("dir d:\\files /b /ad > tmpfile");
This lists all directories (/ad), redirected to a temporary file. Then open the file:
std::ifstream list("tmpfile");
And read it:
std::string dirname;
std::string filename;
while (std::getline(list, dirname))
{
filename = "d:\\files\\" + dirname + "\\data.txt";
if ( ... file exists ... )
break;
}
I call this method crude because it has problems that are hard/impossible to fix:
It overwrites a potentially useful file
It doesn't work if current directory is read-only
It will only work in Windows
It might be possible to use _popen and fgets instead of redirecting to file.

Weird characters in a std::vector<TCHAR*>

Trying to get all sub directories, and eventually all files in sub directories, and I'm passing a std::vector as a reference to a function that actually gets all of the directories. I can cout the cFileName inside the function but once it returns the main the vector has weird characters.
Currently have my Character Set to use Unicode. When I was using multibyte it would actually print weird characters, now it prints nothing. The vector does have something in it (directories.size() returns a value > 0)
Can't really think of anything else. Hopefully this was a good question lol. Thanks
#include <iostream>
#include <vector>
#include <Windows.h>
#include <tchar.h>
#include <stdio.h>
// Declare function prototypes
DWORD listDirectories(std::vector<TCHAR*>&);
// Global variable that holds the current working path of the program
TCHAR buffer[MAX_PATH];
void main()
{
// Declare variables, dwCount for return value from listDirectories, directories stores all sub directories, cDirectory stores current directory
DWORD dwCount;
//std::vector<TCHAR*> directories;
std::vector<TCHAR*> directories;
TCHAR cDirectory[MAX_PATH];
// Get current directory
GetCurrentDirectory(MAX_PATH, buffer);
// Set cDirectory (current directory) to buffer (ATM is current directory) + add \\ to the end and make it the working directory
_tcscpy_s(cDirectory, buffer);
_tcscat_s(cDirectory, L"\\*");
// dwCount is count of how many directories found, to be used later
dwCount = listDirectories(directories);
// Range for loop to print each value in the std::vector<TCHAR*> directories
for (auto tStr : directories) {
// Doing wcout here prints weird characters.
std::wcout << tStr << std::endl;
}
std::cin.get();
} // end void main()
DWORD listDirectories(std::vector<TCHAR*> &directories)
{
// Declare variables, count used for number of directories, hFind for FindFirstFile, data used to store file data
DWORD count = 0;
HANDLE hFind = INVALID_HANDLE_VALUE;
WIN32_FIND_DATA data;
TCHAR currentDir[MAX_PATH];
// Copy the current working directory into currentDir, will be used for subDir
_tcscpy_s(currentDir, buffer);
// Append "\\*" to buffer to make it a working directory
_tcscat_s(buffer, L"\\*");
// Find first file in the current working directory, storying data in data as a reference
hFind = FindFirstFile(buffer, &data);
// If hFind is not an invalid handle
if (hFind != INVALID_HANDLE_VALUE) {
// Go through each file in the directory
do
{
// If the file attributes is a directory
if (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
{
// Create a sub directory
TCHAR subDir[MAX_PATH];
// Fill subDir with current directory + "\\" + dir name
_tcscpy_s(subDir, currentDir);
_tcscat_s(subDir, L"\\");
_tcscat_s(subDir, data.cFileName);
// Fill subDir with current directory + "\\" + dir name
//sprintf_s(subDir, "%s%s%s", currentDir, "\\", data.cFileName);
// Add directory to my directories (std::vector<TCHAR*>) if count > 1, because I don't want the "." and ".." directories
if (count > 1){
directories.push_back(subDir);
// Doing wcout here prints the subDir just fine and works properly
std::wcout << subDir << std::endl;
}
// Add 1 to count, used as the return for how many directories found in the current directory
count++;
}
} while (FindNextFile(hFind, &data) != 0);
}
// Return count of directories found. -2 to get rid of the "." and ".." directories
return (count - 2);
} // end DWORD listDirectories(std::vector<TCHAR*>&)
In your listDirectories() function
TCHAR subDir[MAX_PATH];
...
directories.push_back(subDir);
subDir is an array local to the do ... while loop within the function and its lifetime ends with every iteration of the loop.
Change std::vector<TCHAR*> directories; to std::vector<std::basic_string<TCHAR>> directories; and your code should work correctly. Now you'll make a copy of the directory name and add that to the vector.
Another alternative is to forget about all the TCHAR stuff and just use the wide character versions of everything when dealing with the Windows API.
std::vector<std::wstring> directories;
wchar_t cDirectory[MAX_PATH];
...
hFind = FindFirstFileW(buffer, &data);
and so forth

How to delete all files in a folder, but not delete the folder using NIX standard libraries?

I am trying to create a program that deletes the contents of the /tmp folder, I am using C/C++ on linux.
system("exec rm -r /tmp")
deletes everything in the folder but it deletes the folder too which I dont want.
Is there any way to do this by some sort of bash script, called via system(); or is there a direct way i can do this in C/C++?
My question is similar to this one, but im not on OS X... how to delete all files in a folder, but not the folder itself?
#include <stdio.h>
#include <dirent.h>
int main()
{
// These are data types defined in the "dirent" header
DIR *theFolder = opendir("path/of/folder");
struct dirent *next_file;
char filepath[256];
while ( (next_file = readdir(theFolder)) != NULL )
{
// build the path for each file in the folder
sprintf(filepath, "%s/%s", "path/of/folder", next_file->d_name);
remove(filepath);
}
closedir(theFolder);
return 0;
}
You don't want to spawn a new shell via system() or something like that - that's a lot of overhead to do something very simple and it makes unnecessary assumptions (and dependencies) about what's available on the system.
In C/C++, you could do:
system("exec rm -r /tmp/*")
In Bash, you could do:
rm -r /tmp/*
This will delete everything inside /tmp, but not /tmp itself.
you can do
system("exec find /tmp -mindepth 1 -exec rm {} ';'");
by using use the wildcard * character you can delete all the files with any type of extension.
system("exec rm -r /tmp/*")
In C/C++ you can use (including hidden directories):
system("rm -r /tmp/* /tmp/.*");
system("find /tmp -mindepth 1 -delete");
But what if 'rm' or 'find' utilities are not availabe to sh?, better go 'ftw' and 'remove':
#define _XOPEN_SOURCE 500
#include <ftw.h>
static int remove_cb(const char *fpath, const struct stat *sb, int typeFlag, struct FTW *ftwbuf)
{
if (ftwbuf->level)
remove(fpath);
return 0;
}
int main(void)
{
nftw("./dir", remove_cb, 10, FTW_DEPTH);
return 0;
}
I realize this is very old question, but building on Demitri's great answer I created a function that will recursively delete files in subfolders if desired
It also does some error handling, in that it passes back errno. The function header is written for parsing by doxygen. This function works in the simple example cases I used, and deletes hidden folders and hidden files.
I hope this helps someone else in the future
#include <stdio.h>
#include <dirent.h>
#include <sys/stat.h>
#define SUCCESS_STAT 0
/**
* checks if a specific directory exists
* #param dir_path the path to check
* #return if the path exists
*/
bool dirExists(std::string dir_path)
{
struct stat sb;
if (stat(dir_path.c_str(), &sb) == 0 && S_ISDIR(sb.st_mode))
return true;
else
return false;
}
/**
* deletes all the files in a folder (but not the folder itself). optionally
* this can traverse subfolders and delete all contents when recursive is true
* #param dirpath the directory to delete the contents of (can be full or
* relative path)
* #param recursive true = delete all files/folders in all subfolders
* false = delete only files in toplevel dir
* #return SUCCESS_STAT on success
* errno on failure, values can be from unlink or rmdir
* #note this does NOT delete the named directory, only its contents
*/
int DeleteFilesInDirectory(std::string dirpath, bool recursive)
{
if (dirpath.empty())
return SUCCESS_STAT;
DIR *theFolder = opendir(dirpath.c_str());
struct dirent *next_file;
char filepath[1024];
int ret_val;
if (theFolder == NULL)
return errno;
while ( (next_file = readdir(theFolder)) != NULL )
{
// build the path for each file in the folder
sprintf(filepath, "%s/%s", dirpath.c_str(), next_file->d_name);
//we don't want to process the pointer to "this" or "parent" directory
if ((strcmp(next_file->d_name,"..") == 0) ||
(strcmp(next_file->d_name,"." ) == 0) )
{
continue;
}
//dirExists will check if the "filepath" is a directory
if (dirExists(filepath))
{
if (!recursive)
//if we aren't recursively deleting in subfolders, skip this dir
continue;
ret_val = DeleteFilesInDirectory(filepath, recursive);
if (ret_val != SUCCESS_STAT)
{
closedir(theFolder);
return ret_val;
}
}
ret_val = remove(filepath);
//ENOENT occurs when i folder is empty, or is a dangling link, in
//which case we will say it was a success because the file is gone
if (ret_val != SUCCESS_STAT && ret_val != ENOENT)
{
closedir(theFolder);
return ret_val;
}
}
closedir(theFolder);
return SUCCESS_STAT;
}
You could use nftw(3). First, make a pass to collect the set of file paths to remove. Then use unlink (for non-directories) and rmdir(2) in a second pass
From C++17 onwards you can use std::filesystem. The code below will use directory_iterator to list all the files and subdirectories in a directory and call remove_all to delete them:
#include <filesystem>
namespace fs = std::filesystem;
void delete_dir_content(const fs::path& dir_path) {
for (auto& path: fs::directory_iterator(dir_path)) {
fs::remove_all(path);
}
}
Note that this will throw a filesystem_error exception on underlying OS API errors. You can avoid this with:
void delete_dir_content(const fs::path& dir_path) {
for (auto& path: fs::directory_iterator(dir_path)) {
std::error_code err;
std::uintmax_t n = fs::remove_all(path, err);
if (static_cast<std::uintmax_t>(-1) == n) {
std::cout << "Failed to remove_all(" << path << ") with error: " << err.message() << std::endl;
}
}
}

how to search the computer for files and folders

i need a way to search the computer for files like Windows Explorer. i want my program to search lets say hard drive c:. i need it to search C:\ for folders and files (just the ones you could see in c:\ then if the user clicks on a file on the list like the folder test (C:\test) it would search test and let the user see what files/folders are in it.
Since you mentioned windows, the most straight forward winapi way to do it is with FindFirstFile and FindNextFile functions.
edit: Here's an example that shows you how to enumerate all files/folders in a directory.
#include <Windows.h>
#include <iostream>
int main()
{
WIN32_FIND_DATA file;
HANDLE search_handle=FindFirstFile(L"C:\\*",&file);
if (search_handle)
{
do
{
std::wcout << file.cFileName << std::endl;
}while(FindNextFile(search_handle,&file));
FindClose(search_handle);
}
}
This will be OS dependent. The SO question
How can I get a list of files in a directory using C or C++?
handles this problem well. You can download DIRENT here.
Now that you have this, I'd recommend recursively searching for a file with a DFS/BFS algorithm. You can assume the whole directory structure is a tree where each file is a leaf node and each subdirectory is an internal node.
So all you have to do is,
Get the list of files/folders in a directory with a function such as:
void getFilesFolders(vector<string> & dir_list, const string & folder_name)
If it's a directory, go to 1 with the directory name
If it's a file, terminate if it's the file you're looking for, else move on to the next file.
boost::filesystem can be a cross-platform solution for that (check out for such functions in it).
You can use Directory class members to do this with C# or managed C++. See the following MSDN article:
http://support.microsoft.com/kb/307009
If you wish to use C++ with MFC you can use CFileFind
http://msdn.microsoft.com/en-us/library/f33e1618%28v=VS.80%29.aspx
You'll have to supply your own browse window to present the file system tree.
Or you can use one of the directory/file controls to do both for you.
#include <Windows.h>
#include <iostream>
int FindF(char* pDirectory)
{
char szFindPath[MAX_PATH] = {0};
strcpy(szFindPath, pDirectory);
strcat(szFindPath, "\\*");
WIN32_FIND_DATA file;
HANDLE search_handle=FindFirstFile(szFindPath,&file);
if (search_handle)
{
do
{
if(file.dwFileAttributes == FILE_ATTRIBUTE_DIRECTORY)
{
strcpy(szFindPath, pDirectory);
strcat(szFindPath, "\\");
strcat(szFindPath, file.cFileName);
FindF(szFindPath);
}
std::wcout << file.cFileName << std::endl;
}while(FindNextFile(search_handle,&file));
CloseHandle(search_handle);
}
}
There really is no need to use 3rd party library to accomplish this. This is a short, independent function which lists all files (with their paths) in a directory, including subdiretories' files. std::string folderName has to finish with \, and if you want to list all files on computer, just create a loop in calling function along with GetLogicalDriveStrings (It returns strings with \, so it couldn't be more convenient in this case).
void FindAllFiles(std::string folderName)
{
WIN32_FIND_DATA FileData;
std::string folderNameWithSt = folderName + "*";
HANDLE FirstFile = FindFirstFile(folderNameWithSt.c_str(), &FileData);
if (FirstFile != INVALID_HANDLE_VALUE) {
do {
if (strcmp(FileData.cFileName, ".") != 0 && strcmp(FileData.cFileName, "..") != 0)
{
if(FileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
{
std::string NewPath = folderName + FileData.cFileName;
NewPath = NewPath + "\\";
FindAllFiles(NewPath);
}
else
{
std::cout /*<< folderName*/ << FileData.cFileName << std::endl;
}
}
} while(FindNextFile(FirstFile, &FileData));
}
}
This is ASCII version, remember that files and folders can be named in Unicode