Cross platform file list using wildcard - c++

I'm looking for a cross platform function that supports wildcard listing of a directory contents similar to what FindFirstFile on windows.
Is the wildcard pattern accepted in windows very specific to windows? I want something that supports FindFirstFile wildcard pattern but he working in Linux as well.

If C++17 and above:
You can "walk" a directory using a directory iterator, and match walked file names with a regex, like this:
static std::optional<std::string> find_file(const std::string& search_path, const std::regex& regex) {
const std::filesystem::directory_iterator end;
try {
for (std::filesystem::directory_iterator iter{search_path}; iter != end; iter++) {
const std::string file_ext = iter->path().extension().string();
if (std::filesystem::is_regular_file(*iter)) {
if (std::regex_match(file_ext, regex)) {
return (iter->path().string());
}
}
}
}
catch (std::exception&) {}
return std::nullopt;
}
Usage would be for example, for finding the first file, that ends in .txt:
auto first_file = find_file("DocumentsDirectory", std::regex("\\.(?:txt)"));
Similarly, if you are interested in more than matching by extension, the function line
const std::string file_ext = iter->path().extension().string();
should be modified to something that captures the part of the filename you are interested in (or the whole path to the file)
This could then be used in a function, which performs the wildcard listing by directory.

Here is a recursive variant.
It calls a functional f for each file in the list and returns the number of files found.
It is also recursive: it descends sub directories to the max depth specified.
Note that the search filter does a filename is matched.
The try-catch block in removed so that the caller can catch and process any problems.
#include <string>
#include <regex>
#include <filesystem>
// recursively call a functional *f* for each file that matches the expression
inline int foreach_file(const std::string& search_path, const std::regex& regex, int depth, std::function<void(std::string)> f) {
int n = 0;
const std::filesystem::directory_iterator end;
for (std::filesystem::directory_iterator iter{ search_path }; iter != end; iter++) {
const std::string filename = iter->path().filename().string();
if (std::filesystem::is_regular_file(*iter)) {
if (std::regex_match(filename, regex)) {
n++;
f(iter->path().string());
}
}
else if (std::filesystem::is_directory(*iter) && depth>0) {
n += foreach_file(iter->path().string(), regex, depth - 1, f);
}
}
return n;
}
Example:
void do_something(string filename) {
...
}
void do_all_json_that_start_with_z() {
// regex matches the whole filename
regex r("z.*.json", regex::ECMAScript | regex::icase); // ignoring case
foreach_file(R"(C:\MyFiles\)", r, 99, do_something); // max depth 99
}
// can use lambdas
void do_all_json_that_start_with_z() {
int n=0;
foreach_file(
R"(C:\MyFiles\)", // using raw string - for windows
regex("z.*.json"),
0, // do not descend to sub-directories
[&n](string s) { printf("%d) %s\n", ++n, s.c_str()); });
}

Related

The best way to ignore files with other extensions when using the C++ experimental <filesystem>?

With the future C++, is there a better way to ignore files with other than wanted extensions than the one shown in the code snippet below?
I am learning the C++ experimental <filesystem> (http://en.cppreference.com/w/cpp/experimental/fs) while writing a simple program that transforms text files from one directory to text file in another directory. The program takes input and output directories via command-line arguments. Only the files with certain extensions (like .csv, .txt, ...) should be processed. The output files should have the .xxx extension.
#include <filesystem>
namespace fs = std::tr2::sys; // the implementation from Visual Studio 2015
...
fs::path srcpath{ argv[1] };
fs::path destpath{ argv[2] };
...
for (auto name : fs::directory_iterator(srcpath))
{
if (!fs::is_regular_file(name))
continue; // ignore the non-files
fs::path fnameIn{ name }; // input file name
// Ignore unwanted extensions (here lowered because of Windows).
string ext{ lower(fnameIn.extension().string()) };
if (ext != ".txt" && ext != ".csv")
continue;
// Build the output filename path.
fs::path fnameOut{ destpath / fnameIn.filename().replace_extension(".xxx") };
... processing ...
}
Basically, your question boils down to, "given a string, how do I determine if it matches one of a number of possibilities?" That's pretty trivial: put the possibilities in a std::set:
//Before loop
std::set<std::string> wanted_exts = {".txt", ".csv"};
//In loop
string ext{ lower(fnameIn.extension().string()) };
if (wanted_exts.find(ext) == wanted_exts.end())
continue;
You can of course keep wanted_exts around for as long as you like, since it probably won't change. Also, if you have Boost.Containers, I would suggest making wanted_exts a flat_set. That will help minimize allocations.
std::tr2::sys was the namespace MSVC used in VS2013 to ship the filesystem TS, but that is actually supposed to be in the std::experimental::v1 namespace; the old namespace has been retained for backwards compatibility. v1 is an inline namespace, so you can drop that from the name and say
namespace fs = std::experimental::filesystem;
Assuming using boost is an option, you can perform filtering of the directory entries using Boost.Range adaptors. And testing for any one of several extensions can be done using boost::algorithm::any_of_equal.
#include <boost/algorithm/cxx11/any_of.hpp>
#include <boost/range/adaptors.hpp>
for(auto const& p :
boost::make_iterator_range(fs::directory_iterator(srcpath), {})
| boost::adaptors::transformed([](auto const& d) {
return fs::path(d); })
| boost::adaptors::filtered([](auto const& p) {
return fs::is_regular_file(p); })
| boost::adaptors::filtered([](auto const& p) {
auto const& exts = { ".txt", ".csv" };
return boost::algorithm::any_of_equal(exts, p.extension().string()); })
) {
// all filenames here will have one of the extensions you tested for
}
The solution of the loop that I have finally chosen...
#include <filesystem>
namespace fs = std::experimental::filesystem;
...
set<string> extensions{ ".txt", ".csv" };
for (auto const& name : fs::directory_iterator(srcpath))
{
if (!fs::is_regular_file(name))
continue;
fs::path fnameIn{ name };
string ext{ lower(fnameIn.extension().string()) };
if (extensions.find(ext) != extensions.end())
{
fs::path fnameOut{ destpath / fnameIn.filename().replace_extension(".xxx") };
processing(fnameIn, fnameOut);
}
}

File search APIs on Linux

In my project, I need to show all files on user's drive filtered by the filename with a text line. Are there any APIs to do such thing?
On Windows, I know, there're FindFirstFile and FindNextFile functions in WinAPI.
I use C++/Qt.
There's ftw() and linux has fts()
Besides those, you can iterate directories, using e.g. opendir8/readdir()
Qt provides the QDirIterator class:
QDirIterator iter("/", QDirIterator::Subdirectories);
while (iter.hasNext()) {
QString current = iter.next();
// Do something with 'current'...
}
If you are looking for a Unix command, you could do this :
find source_dir -name 'regex'
If you want to do it C++ style, I'd suggest to use boost::filesystem. It's a very powerfull cross platform library.
Of course, you will have to add an additional library.
Here is an example :
std::vector<std::string> list_files(const std::string& root, const bool& recursive, const std::string& filter, const bool& regularFilesOnly)
{
namespace fs = boost::filesystem;
fs::path rootPath(root);
// Throw exception if path doesn't exist or isn't a directory.
if (!fs::exists(rootPath)) {
throw std::exception("rootPath does not exist");
}
if (!fs::is_directory(rootPath)) {
throw std::exception("rootPath is not a directory.");
}
// List all the files in the directory
const std::regex regexFilter(filter);
auto fileList = std::vector<std::string>();
fs::directory_iterator end_itr;
for( fs::directory_iterator it(rootPath); it != end_itr; ++it) {
std::string filepath(it->path().string());
// For a directory
if (fs::is_directory(it->status())) {
if (recursive && it->path().string() != "..") {
// List the files in the directory
auto currentDirFiles = list_files(filepath, recursive, filter, regularFilesOnly);
// Add to the end of the current vector
fileList.insert(fileList.end(), currentDirFiles.begin(), currentDirFiles.end());
}
} else if (fs::is_regular_file(it->status())) { // For a regular file
if (filter != "" && !regex_match(filepath, regexFilter)) {
continue;
}
} else {
// something else
}
if (regularFilesOnly && !fs::is_regular_file(it->status())) {
continue;
}
// Add the file or directory to the list
fileList.push_back(filepath);
}
return fileList;
}
you can also use glob
http://man7.org/linux/man-pages/man3/glob.3.html
has the advantage of existing on a lot of Unices (Solaris for sure) as it is part of POSIX.
Ok, it's not C++ but pure C.
Look man find. find supports filtering by a mask ( -name option for examole)

Get relative path from two absolute paths

I have two absolute filesystem paths (A and B), and I want to generate a third filesystem path that represents "A relative from B".
Use case:
Media player managing a playlist.
User adds file to playlist.
New file path added to playlist relative to playlist path.
In the future, entire music directory (including playlist) moved elsewhere.
All paths still valid because they are relative to the playlist.
boost::filesystem appears to have complete to resolve relative ~ relative => absolute, but nothing to do this in reverse (absolute ~ absolute => relative).
I want to do it with Boost paths.
With C++17 and its std::filesystem::relative, which evolved from boost, this is a no-brainer:
#include <filesystem>
#include <iostream>
namespace fs = std::filesystem;
int main()
{
const fs::path base("/is/the/speed/of/light/absolute");
const fs::path p("/is/the/speed/of/light/absolute/or/is/it/relative/to/the/observer");
const fs::path p2("/little/light/races/in/orbit/of/a/rogue/planet");
std::cout << "Base is base: " << fs::relative(p, base).generic_string() << '\n'
<< "Base is deeper: " << fs::relative(base, p).generic_string() << '\n'
<< "Base is orthogonal: " << fs::relative(p2, base).generic_string();
// Omitting exception handling/error code usage for simplicity.
}
Output (second parameter is base)
Base is base: or/is/it/relative/to/the/observer
Base is deeper: ../../../../../../..
Base is orthogonal: ../../../../../../little/light/races/in/orbit/of/a/rogue/planet
It uses std::filesystem::path::lexically_relative for comparison.
The difference to the pure lexical function is, that std::filesystem::relative resolves symlinks and normalizes both paths using
std::filesystem::weakly_canonical (which was introduced for relative) before comparison.
As of version 1.60.0 boost.filesystem does support this. You're looking for the member function path lexically_relative(const path& p) const.
Original, pre-1.60.0 answer below.
Boost doesn't support this; it's an open issue — #1976 (Inverse function for complete) — that nevertheless doesn't seem to be getting much traction.
Here's a vaguely naive workaround that seems to do the trick (not sure whether it can be improved):
#include <boost/filesystem/path.hpp>
#include <boost/filesystem/operations.hpp>
#include <boost/filesystem/fstream.hpp>
#include <stdexcept>
/**
* https://svn.boost.org/trac/boost/ticket/1976#comment:2
*
* "The idea: uncomplete(/foo/new, /foo/bar) => ../new
* The use case for this is any time you get a full path (from an open dialog, perhaps)
* and want to store a relative path so that the group of files can be moved to a different
* directory without breaking the paths. An IDE would be a simple example, so that the
* project file could be safely checked out of subversion."
*
* ALGORITHM:
* iterate path and base
* compare all elements so far of path and base
* whilst they are the same, no write to output
* when they change, or one runs out:
* write to output, ../ times the number of remaining elements in base
* write to output, the remaining elements in path
*/
boost::filesystem::path
naive_uncomplete(boost::filesystem::path const p, boost::filesystem::path const base) {
using boost::filesystem::path;
using boost::filesystem::dot;
using boost::filesystem::slash;
if (p == base)
return "./";
/*!! this breaks stuff if path is a filename rather than a directory,
which it most likely is... but then base shouldn't be a filename so... */
boost::filesystem::path from_path, from_base, output;
boost::filesystem::path::iterator path_it = p.begin(), path_end = p.end();
boost::filesystem::path::iterator base_it = base.begin(), base_end = base.end();
// check for emptiness
if ((path_it == path_end) || (base_it == base_end))
throw std::runtime_error("path or base was empty; couldn't generate relative path");
#ifdef WIN32
// drive letters are different; don't generate a relative path
if (*path_it != *base_it)
return p;
// now advance past drive letters; relative paths should only go up
// to the root of the drive and not past it
++path_it, ++base_it;
#endif
// Cache system-dependent dot, double-dot and slash strings
const std::string _dot = std::string(1, dot<path>::value);
const std::string _dots = std::string(2, dot<path>::value);
const std::string _sep = std::string(1, slash<path>::value);
// iterate over path and base
while (true) {
// compare all elements so far of path and base to find greatest common root;
// when elements of path and base differ, or run out:
if ((path_it == path_end) || (base_it == base_end) || (*path_it != *base_it)) {
// write to output, ../ times the number of remaining elements in base;
// this is how far we've had to come down the tree from base to get to the common root
for (; base_it != base_end; ++base_it) {
if (*base_it == _dot)
continue;
else if (*base_it == _sep)
continue;
output /= "../";
}
// write to output, the remaining elements in path;
// this is the path relative from the common root
boost::filesystem::path::iterator path_it_start = path_it;
for (; path_it != path_end; ++path_it) {
if (path_it != path_it_start)
output /= "/";
if (*path_it == _dot)
continue;
if (*path_it == _sep)
continue;
output /= *path_it;
}
break;
}
// add directory level to both paths and continue iteration
from_path /= path(*path_it);
from_base /= path(*base_it);
++path_it, ++base_it;
}
return output;
}
I just wrote code that can translate an absolute path to a relative path. It works in all my use cases, but I can not guarantee it is flawless.
I have abreviated boost::filesystem to 'fs' for readability. In the function definition, you can use fs::path::current_path() as a default value for 'relative_to'.
fs::path relativePath( const fs::path &path, const fs::path &relative_to )
{
// create absolute paths
fs::path p = fs::absolute(path);
fs::path r = fs::absolute(relative_to);
// if root paths are different, return absolute path
if( p.root_path() != r.root_path() )
return p;
// initialize relative path
fs::path result;
// find out where the two paths diverge
fs::path::const_iterator itr_path = p.begin();
fs::path::const_iterator itr_relative_to = r.begin();
while( itr_path != p.end() && itr_relative_to != r.end() && *itr_path == *itr_relative_to ) {
++itr_path;
++itr_relative_to;
}
// add "../" for each remaining token in relative_to
if( itr_relative_to != r.end() ) {
++itr_relative_to;
while( itr_relative_to != r.end() ) {
result /= "..";
++itr_relative_to;
}
}
// add remaining path
while( itr_path != p.end() ) {
result /= *itr_path;
++itr_path;
}
return result;
}
I was just thinking about using boost::filesystem for the same task, but - since my application uses both Qt and Boost libraries, I decided to use Qt which does this task with one simple method QString QDir::relativeFilePath( const QString & fileName ):
QDir dir("/home/bob");
QString s;
s = dir.relativeFilePath("images/file.jpg"); // s is "images/file.jpg"
s = dir.relativeFilePath("/home/mary/file.txt"); // s is "../mary/file.txt"
It works like a charm and saved me a few hours of my life.
Here's how I do it in the library I build on top of boost filesystem:
Step 1: Determine "deepest common root". Basically, its like the greatest common denominator for 2 paths. For example, if you're 2 paths are "C:\a\b\c\d" and "C:\a\b\c\l.txt" then the common root they both share is "C:\a\b\c\".
To get this, convert both paths into absolute- NOT canonical- form (you'll want to be able to do this for speculative paths & symlinks).
Step 2: To go from A to B, you suffix A with enough copies of "../" to shift up the directory tree to the common root, then add the string for B to travel down the tree to it. On windows you can have 2 paths with no common root, so going from any A to any B is not always possible.
namespace fs = boost::filesystem;
bool GetCommonRoot(const fs::path& path1,
const fs::path& path2,
fs::path& routeFrom1To2,
std::vector<fs::path>& commonDirsInOrder)
{
fs::path pathA( fs::absolute( path1));
fs::path pathB( fs::absolute( path2));
// Parse both paths into vectors of tokens. I call them "dir" because they'll
// be the common directories unless both paths are the exact same file.
// I also Remove the "." and ".." paths as part of the loops
fs::path::iterator iter;
std::vector<fs::path> dirsA;
std::vector<fs::path> dirsB;
for(iter = pathA.begin(); iter != pathA.end(); ++iter) {
std::string token = (*iter).string();
if(token.compare("..") == 0) { // Go up 1 level => Pop vector
dirsA.pop_back();
}
else if(token.compare(".") != 0) { // "." means "this dir" => ignore it
dirsA.push_back( *iter);
}
}
for(iter = pathB.begin(); iter != pathB.end(); ++iter) {
std::string token = (*iter).string();
if(token.compare("..") == 0) { // Go up 1 level => Pop vector
dirsB.pop_back();
}
else if(token.compare(".") != 0) { // "." means "this dir" => ignore it
dirsB.push_back( *iter);
}
}
// Determine how far to check in each directory set
size_t commonDepth = std::min<int>( dirsA.size(), dirsB.size());
if(!commonDepth) {
// They don't even share a common root- no way from A to B
return false;
}
// Match entries in the 2 vectors until we see a divergence
commonDirsInOrder.clear();
for(size_t i=0; i<commonDepth; ++i) {
if(dirsA[i].string().compare( dirsB[i].string()) != 0) { // Diverged
break;
}
commonDirsInOrder.push_back( dirsA[i]); // I could use dirsB too.
}
// Now determine route: start with A
routeFrom1To2.clear();
for(size_t i=0; i<commonDepth; ++i) {
routeFrom1To2 /= dirsA[i];
}
size_t backupSteps = dirsA.size() - commonDepth; // # of "up dir" moves we need
for(size_t i=0; i<backupSteps; ++i) {
routeFrom1To2 /= "../";
}
// Append B's path to go down to it from the common root
for(size_t i=commonDepth; i<dirsB.size(); ++i) {
routeFrom1To2 /= dirsB[i]; // ensures absolutely correct subdirs
}
return true;
}
This will do what you want- you go up from A until you hit the common folder it and B are both descendants of, then go down to B. You probably don't need the "commonDirsInOrder" return that I have, but the "routeFrom1To2" return IS the one you're asking for.
If you plan to actually change the working directory to "B" you can use "routeFrom1To2" directly. Be aware that this function will produce an absolute path despite all the ".." parts, but that shouldn't be a problem.
I have write down one simple solution for this trick.
There's no usage on boost libraries, only STL's std::string, std::vector.
The Win32 platform has been tested.
Just calling:
strAlgExeFile = helper.GetRelativePath(PathA, PathB);
And, it would return relative path from PathA to PathB.
Example:
strAlgExeFile = helper.GetRelativePath((helper.GetCurrentDir()).c_str(), strAlgExeFile.c_str());
#ifdef _WIN32
#define STR_TOKEN "\\"
#define LAST_FOLDER "..\\"
#define FOLDER_SEP "\\"
#define LINE_BREAK "\r\n"
#else
#define STR_TOKEN "/"
#define LAST_FOLDER "../"
#define FOLDER_SEP "/"
#define LINE_BREAK "\n"
#endif // _WIN32
void CHelper::SplitStr2Vec(const char* pszPath, vector<string>& vecString)
{
char * pch;
pch = strtok (const_cast < char*> (pszPath), STR_TOKEN );
while (pch != NULL)
{
vecString.push_back( pch );
pch = strtok (NULL, STR_TOKEN );
}
}
string& CHelper::GetRelativePath(const char* pszPath1,const char* pszPath2)
{
vector<string> vecPath1, vecPath2;
vecPath1.clear();
vecPath2.clear();
SplitStr2Vec(pszPath1, vecPath1);
SplitStr2Vec(pszPath2, vecPath2);
size_t iSize = ( vecPath1.size() < vecPath2.size() )? vecPath1.size(): vecPath2.size();
unsigned int iSameSize(0);
for (unsigned int i=0; i<iSize; ++i)
{
if ( vecPath1[i] != vecPath2[i])
{
iSameSize = i;
break;
}
}
m_strRelativePath = "";
for (unsigned int i=0 ; i< (vecPath1.size()-iSameSize) ; ++i)
m_strRelativePath += const_cast<char *> (LAST_FOLDER);
for (unsigned int i=iSameSize ; i<vecPath2.size() ; ++i)
{
m_strRelativePath += vecPath2[i];
if( i < (vecPath2.size()-1) )
m_strRelativePath += const_cast<char *> (FOLDER_SEP);
}
return m_strRelativePath;
}
I needed to do this without Boost and the other std based solution didn't do it for me so I reimplemented it. As I was working on this I realized that I'd done it before too...
Anyway, it's not as complete as some of the others but might be useful to people. It's Windows-specific; changes to make it POSIX involve directory separator and case sensitivity in the string compare.
Shortly after I got this implemented and working I had to transfer the surrounding functionality to Python so all of this just boiled down to os.path.relpath(to, from).
static inline bool StringsEqual_i(const std::string& lhs, const std::string& rhs)
{
return _stricmp(lhs.c_str(), rhs.c_str()) == 0;
}
static void SplitPath(const std::string& in_path, std::vector<std::string>& split_path)
{
size_t start = 0;
size_t dirsep;
do
{
dirsep = in_path.find_first_of("\\/", start);
if (dirsep == std::string::npos)
split_path.push_back(std::string(&in_path[start]));
else
split_path.push_back(std::string(&in_path[start], &in_path[dirsep]));
start = dirsep + 1;
} while (dirsep != std::string::npos);
}
/**
* Get the relative path from a base location to a target location.
*
* \param to The target location.
* \param from The base location. Must be a directory.
* \returns The resulting relative path.
*/
static std::string GetRelativePath(const std::string& to, const std::string& from)
{
std::vector<std::string> to_dirs;
std::vector<std::string> from_dirs;
SplitPath(to, to_dirs);
SplitPath(from, from_dirs);
std::string output;
output.reserve(to.size());
std::vector<std::string>::const_iterator to_it = to_dirs.begin(),
to_end = to_dirs.end(),
from_it = from_dirs.begin(),
from_end = from_dirs.end();
while ((to_it != to_end) && (from_it != from_end) && StringsEqual_i(*to_it, *from_it))
{
++to_it;
++from_it;
}
while (from_it != from_end)
{
output += "..\\";
++from_it;
}
while (to_it != to_end)
{
output += *to_it;
++to_it;
if (to_it != to_end)
output += "\\";
}
return output;
}

Recursive file search using C++ MFC?

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``

How to get file extension from string in C++

Given a string "filename.conf", how to I verify the extension part?
I need a cross platform solution.
Is this too simple of a solution?
#include <iostream>
#include <string>
int main()
{
std::string fn = "filename.conf";
if(fn.substr(fn.find_last_of(".") + 1) == "conf") {
std::cout << "Yes..." << std::endl;
} else {
std::cout << "No..." << std::endl;
}
}
The best way is to not write any code that does it but call existing methods. In windows, the PathFindExtension method is probably the simplest.
So why would you not write your own?
Well, take the strrchr example, what happens when you use that method on the following string "c:\program files\AppleGate.Net\readme"? Is ".Net\readme" the extension? It is easy to write something that works for a few example cases, but can be much harder to write something that works for all cases.
With C++17 and its std::filesystem::path::extension (the library is the successor to boost::filesystem) you would make your statement more expressive than using e.g. std::string.
#include <iostream>
#include <filesystem> // C++17
namespace fs = std::filesystem;
int main()
{
fs::path filePath = "my/path/to/myFile.conf";
if (filePath.extension() == ".conf") // Heed the dot.
{
std::cout << filePath.stem() << " is a valid type."; // Output: "myFile is a valid type."
}
else
{
std::cout << filePath.filename() << " is an invalid type."; // Output: e.g. "myFile.cfg is an invalid type"
}
}
See also std::filesystem::path::stem, std::filesystem::path::filename.
You have to make sure you take care of file names with more then one dot.
example: c:\.directoryname\file.name.with.too.many.dots.ext would not be handled correctly by strchr or find.
My favorite would be the boost filesystem library that have an extension(path) function
Assuming you have access to STL:
std::string filename("filename.conf");
std::string::size_type idx;
idx = filename.rfind('.');
if(idx != std::string::npos)
{
std::string extension = filename.substr(idx+1);
}
else
{
// No extension found
}
Edit: This is a cross platform solution since you didn't mention the platform. If you're specifically on Windows, you'll want to leverage the Windows specific functions mentioned by others in the thread.
Someone else mentioned boost but I just wanted to add the actual code to do this:
#include <boost/filesystem.hpp>
using std::string;
string texture = foo->GetTextureFilename();
string file_extension = boost::filesystem::extension(texture);
cout << "attempting load texture named " << texture
<< " whose extensions seems to be "
<< file_extension << endl;
// Use JPEG or PNG loader function, or report invalid extension
actually the STL can do this without much code, I advise you learn a bit about the STL because it lets you do some fancy things, anyways this is what I use.
std::string GetFileExtension(const std::string& FileName)
{
if(FileName.find_last_of(".") != std::string::npos)
return FileName.substr(FileName.find_last_of(".")+1);
return "";
}
this solution will always return the extension even on strings like "this.a.b.c.d.e.s.mp3" if it cannot find the extension it will return "".
Actually, the easiest way is
char* ext;
ext = strrchr(filename,'.')
One thing to remember: if '.' doesn't exist in filename, ext will be NULL.
I've stumbled onto this question today myself, even though I already had a working code I figured out that it wouldn't work in some cases.
While some people already suggested using some external libraries, I prefer to write my own code for learning purposes.
Some answers included the method I was using in the first place (looking for the last "."), but I remembered that on linux hidden files/folders start with ".".
So if file file is hidden and has no extension, the whole file name would be taken for extension.
To avoid that I wrote this piece of code:
bool getFileExtension(const char * dir_separator, const std::string & file, std::string & ext)
{
std::size_t ext_pos = file.rfind(".");
std::size_t dir_pos = file.rfind(dir_separator);
if(ext_pos>dir_pos+1)
{
ext.append(file.begin()+ext_pos,file.end());
return true;
}
return false;
}
I haven't tested this fully, but I think that it should work.
I'd go with boost::filesystem::extension (std::filesystem::path::extension with C++17) but if you cannot use Boost and you just have to verify the extension, a simple solution is:
bool ends_with(const std::string &filename, const std::string &ext)
{
return ext.length() <= filename.length() &&
std::equal(ext.rbegin(), ext.rend(), filename.rbegin());
}
if (ends_with(filename, ".conf"))
{ /* ... */ }
Using std::string's find/rfind solves THIS problem, but if you work a lot with paths then you should look at boost::filesystem::path since it will make your code much cleaner than fiddling with raw string indexes/iterators.
I suggest boost since it's a high quality, well tested, (open source and commercially) free and fully portable library.
For char array-type strings you can use this:
#include <ctype.h>
#include <string.h>
int main()
{
char filename[] = "apples.bmp";
char extension[] = ".jpeg";
if(compare_extension(filename, extension) == true)
{
// .....
} else {
// .....
}
return 0;
}
bool compare_extension(char *filename, char *extension)
{
/* Sanity checks */
if(filename == NULL || extension == NULL)
return false;
if(strlen(filename) == 0 || strlen(extension) == 0)
return false;
if(strchr(filename, '.') == NULL || strchr(extension, '.') == NULL)
return false;
/* Iterate backwards through respective strings and compare each char one at a time */
for(int i = 0; i < strlen(filename); i++)
{
if(tolower(filename[strlen(filename) - i - 1]) == tolower(extension[strlen(extension) - i - 1]))
{
if(i == strlen(extension) - 1)
return true;
} else
break;
}
return false;
}
Can handle file paths in addition to filenames. Works with both C and C++. And cross-platform.
If you use Qt library, you can give a try to QFileInfo's suffix()
Good answers but I see most of them has some problems:
First of all I think a good answer should work for complete file names which have their path headings, also it should work for linux or windows or as mentioned it should be cross platform. For most of answers; file names with no extension but a path with a folder name including dot, the function will fail to return the correct extension: examples of some test cases could be as follow:
const char filename1 = {"C:\\init.d\\doc"}; // => No extention
const char filename2 = {"..\\doc"}; //relative path name => No extention
const char filename3 = {""}; //emputy file name => No extention
const char filename4 = {"testing"}; //only single name => No extention
const char filename5 = {"tested/k.doc"}; // normal file name => doc
const char filename6 = {".."}; // parent folder => No extention
const char filename7 = {"/"}; // linux root => No extention
const char filename8 = {"/bin/test.d.config/lx.wize.str"}; // ordinary path! => str
"brian newman" suggestion will fail for filename1 and filename4.
and most of other answers which are based on reverse find will fail for filename1.
I suggest including the following method in your source:
which is function returning index of first character of extension or the length of given string if not found.
size_t find_ext_idx(const char* fileName)
{
size_t len = strlen(fileName);
size_t idx = len-1;
for(size_t i = 0; *(fileName+i); i++) {
if (*(fileName+i) == '.') {
idx = i;
} else if (*(fileName + i) == '/' || *(fileName + i) == '\\') {
idx = len - 1;
}
}
return idx+1;
}
you could use the above code in your c++ application like below:
std::string get_file_ext(const char* fileName)
{
return std::string(fileName).substr(find_ext_idx(fileName));
}
The last point in some cases the a folder is given to file name as argument and includes a dot in the folder name the function will return folder's dot trailing so better first to user check that the given name is a filename and not folder name.
This is a solution I came up with. Then, I noticed that it is similar to what #serengeor posted.
It works with std::string and find_last_of, but the basic idea will also work if modified to use char arrays and strrchr.
It handles hidden files, and extra dots representing the current directory. It is platform independent.
string PathGetExtension( string const & path )
{
string ext;
// Find the last dot, if any.
size_t dotIdx = path.find_last_of( "." );
if ( dotIdx != string::npos )
{
// Find the last directory separator, if any.
size_t dirSepIdx = path.find_last_of( "/\\" );
// If the dot is at the beginning of the file name, do not treat it as a file extension.
// e.g., a hidden file: ".alpha".
// This test also incidentally avoids a dot that is really a current directory indicator.
// e.g.: "alpha/./bravo"
if ( dotIdx > dirSepIdx + 1 )
{
ext = path.substr( dotIdx );
}
}
return ext;
}
Unit test:
int TestPathGetExtension( void )
{
int errCount = 0;
string tests[][2] =
{
{ "/alpha/bravo.txt", ".txt" },
{ "/alpha/.bravo", "" },
{ ".alpha", "" },
{ "./alpha.txt", ".txt" },
{ "alpha/./bravo", "" },
{ "alpha/./bravo.txt", ".txt" },
{ "./alpha", "" },
{ "c:\\alpha\\bravo.net\\charlie.txt", ".txt" },
};
int n = sizeof( tests ) / sizeof( tests[0] );
for ( int i = 0; i < n; ++i )
{
string ext = PathGetExtension( tests[i][0] );
if ( ext != tests[i][1] )
{
++errCount;
}
}
return errCount;
}
A NET/CLI version using System::String
System::String^ GetFileExtension(System::String^ FileName)
{
int Ext=FileName->LastIndexOf('.');
if( Ext != -1 )
return FileName->Substring(Ext+1);
return "";
}
_splitpath, _wsplitpath, _splitpath_s, _wsplitpath_w
This is Windows (Platform SDK) only
You can use strrchr() to find last occurence of .(dot) and get .(dot) based extensions files.
Check the below code for example.
#include<stdio.h>
void GetFileExtension(const char* file_name) {
int ext = '.';
const char* extension = NULL;
extension = strrchr(file_name, ext);
if(extension == NULL){
printf("Invalid extension encountered\n");
return;
}
printf("File extension is %s\n", extension);
}
int main()
{
const char* file_name = "c:\\.directoryname\\file.name.with.too.many.dots.ext";
GetFileExtension(file_name);
return 0;
}
Here's a function that takes a path/filename as a string and returns the extension as a string. It is all standard c++, and should work cross-platform for most platforms.
Unlike several other answers here, it handles the odd cases that windows' PathFindExtension handles, based on PathFindExtensions's documentation.
wstring get_file_extension( wstring filename )
{
size_t last_dot_offset = filename.rfind(L'.');
// This assumes your directory separators are either \ or /
size_t last_dirsep_offset = max( filename.rfind(L'\\'), filename.rfind(L'/') );
// no dot = no extension
if( last_dot_offset == wstring::npos )
return L"";
// directory separator after last dot = extension of directory, not file.
// for example, given C:\temp.old\file_that_has_no_extension we should return "" not "old"
if( (last_dirsep_offset != wstring::npos) && (last_dirsep_offset > last_dot_offset) )
return L"";
return filename.substr( last_dot_offset + 1 );
}
I use these two functions to get the extension and filename without extension:
std::string fileExtension(std::string file){
std::size_t found = file.find_last_of(".");
return file.substr(found+1);
}
std::string fileNameWithoutExtension(std::string file){
std::size_t found = file.find_last_of(".");
return file.substr(0,found);
}
And these regex approaches for certain extra requirements:
std::string fileExtension(std::string file){
std::regex re(".*[^\\.]+\\.([^\\.]+$)");
std::smatch result;
if(std::regex_match(file,result,re))return result[1];
else return "";
}
std::string fileNameWithoutExtension(std::string file){
std::regex re("(.*[^\\.]+)\\.[^\\.]+$");
std::smatch result;
if(std::regex_match(file,result,re))return result[1];
else return file;
}
Extra requirements that are met by the regex method:
If filename is like .config or something like this, extension will be an empty string and filename without extension will be .config.
If filename doesn't have any extension, extention will be an empty string, filename without extension will be the filename unchanged.
EDIT:
The extra requirements can also be met by the following:
std::string fileExtension(const std::string& file){
std::string::size_type pos=file.find_last_of('.');
if(pos!=std::string::npos&&pos!=0)return file.substr(pos+1);
else return "";
}
std::string fileNameWithoutExtension(const std::string& file){
std::string::size_type pos=file.find_last_of('.');
if(pos!=std::string::npos&&pos!=0)return file.substr(0,pos);
else return file;
}
Note:
Pass only the filenames (not path) in the above functions.
Try to use strstr
char* lastSlash;
lastSlash = strstr(filename, ".");
Or you can use this:
char *ExtractFileExt(char *FileName)
{
std::string s = FileName;
int Len = s.length();
while(TRUE)
{
if(FileName[Len] != '.')
Len--;
else
{
char *Ext = new char[s.length()-Len+1];
for(int a=0; a<s.length()-Len; a++)
Ext[a] = FileName[s.length()-(s.length()-Len)+a];
Ext[s.length()-Len] = '\0';
return Ext;
}
}
}
This code is cross-platform
So, using std::filesystem is the best answer, but if for whatever reason you don't have C++17 features available, this will work even if the input string includes directories:
string getextn (const string &fn) {
int sep = fn.find_last_of(".\\/");
return (sep >= 0 && fn[sep] == '.') ? fn.substr(sep) : "";
}
I'm adding this because the rest of the answers here are either strangely complicated or fail if the path to the file contains a dot and the file doesn't. I think the fact that find_last_of can look for multiple characters is often overlooked.
It works with both / and \ path separators. It fails if the extension itself contains a slash but that's usually too rare to matter. It doesn't do any filtering for filenames that start with a dot and contain no other dots -- if this matters to you then this is the least unreasonable answer here.
Example input / output:
/ => ''
./ => ''
./pathname/ => ''
./path.name/ => ''
pathname/ => ''
path.name/ => ''
c:\path.name\ => ''
/. => '.'
./. => '.'
./pathname/. => '.'
./path.name/. => '.'
pathname/. => '.'
path.name/. => '.'
c:\path.name\. => '.'
/.git_ignore => '.git_ignore'
./.git_ignore => '.git_ignore'
./pathname/.git_ignore => '.git_ignore'
./path.name/.git_ignore => '.git_ignore'
pathname/.git_ignore => '.git_ignore'
path.name/.git_ignore => '.git_ignore'
c:\path.name\.git_ignore => '.git_ignore'
/filename => ''
./filename => ''
./pathname/filename => ''
./path.name/filename => ''
pathname/filename => ''
path.name/filename => ''
c:\path.name\filename => ''
/filename. => '.'
./filename. => '.'
./pathname/filename. => '.'
./path.name/filename. => '.'
pathname/filename. => '.'
path.name/filename. => '.'
c:\path.name\filename. => '.'
/filename.tar => '.tar'
./filename.tar => '.tar'
./pathname/filename.tar => '.tar'
./path.name/filename.tar => '.tar'
pathname/filename.tar => '.tar'
path.name/filename.tar => '.tar'
c:\path.name\filename.tar => '.tar'
/filename.tar.gz => '.gz'
./filename.tar.gz => '.gz'
./pathname/filename.tar.gz => '.gz'
./path.name/filename.tar.gz => '.gz'
pathname/filename.tar.gz => '.gz'
path.name/filename.tar.gz => '.gz'
c:\path.name\filename.tar.gz => '.gz'
If you happen to use Poco libraries you can do:
#include <Poco/Path.h>
...
std::string fileExt = Poco::Path("/home/user/myFile.abc").getExtension(); // == "abc"
If you consider the extension as the last dot and the possible characters after it, but only if they don't contain the directory separator character, the following function returns the extension starting index, or -1 if no extension found. When you have that you can do what ever you want, like strip the extension, change it, check it etc.
long get_extension_index(string path, char dir_separator = '/') {
// Look from the end for the first '.',
// but give up if finding a dir separator char first
for(long i = path.length() - 1; i >= 0; --i) {
if(path[i] == '.') {
return i;
}
if(path[i] == dir_separator) {
return -1;
}
}
return -1;
}
I used PathFindExtension() function to know whether it is a valid tif file or not.
#include <Shlwapi.h>
bool A2iAWrapperUtility::isValidImageFile(string imageFile)
{
char * pStrExtension = ::PathFindExtension(imageFile.c_str());
if (pStrExtension != NULL && strcmp(pStrExtension, ".tif") == 0)
{
return true;
}
return false;
}