如何在 C + + 中从路径中提取文件名和扩展名

我有一个存储在 .log中的文件列表,语法如下:

c:\foto\foto2003\shadow.gif
D:\etc\mom.jpg

我想从这个文件中提取名称和扩展名。你能举个简单的例子吗?

126809 次浏览

Not the code, but here is the idea:

  1. Read a std::string from the input stream (std::ifstream), each instance read will be the full path
  2. Do a find_last_of on the string for the \
  3. Extract a substring from this position to the end, this will now give you the file name
  4. Do a find_last_of for ., and a substring either side will give you name + extension.

If you want a safe way (i.e. portable between platforms and not putting assumptions on the path), I'd recommend to use boost::filesystem.

It would look somehow like this:

boost::filesystem::path my_path( filename );

Then you can extract various data from this path. Here's the documentation of path object.


BTW: Also remember that in order to use path like

c:\foto\foto2003\shadow.gif

you need to escape the \ in a string literal:

const char* filename = "c:\\foto\\foto2003\\shadow.gif";

Or use / instead:

const char* filename = "c:/foto/foto2003/shadow.gif";

This only applies to specifying literal strings in "" quotes, the problem doesn't exist when you load paths from a file.

You'll have to read your filenames from the file in std::string. You can use the string extraction operator of std::ostream. Once you have your filename in a std::string, you can use the std::string::find_last_of method to find the last separator.

Something like this:

std::ifstream input("file.log");
while (input)
{
std::string path;
input >> path;


size_t sep = path.find_last_of("\\/");
if (sep != std::string::npos)
path = path.substr(sep + 1, path.size() - sep - 1);


size_t dot = path.find_last_of(".");
if (dot != std::string::npos)
{
std::string name = path.substr(0, dot);
std::string ext  = path.substr(dot, path.size() - dot);
}
else
{
std::string name = path;
std::string ext  = "";
}
}

To extract a filename without extension, use boost::filesystem::path::stem instead of ugly std::string::find_last_of(".")

boost::filesystem::path p("c:/dir/dir/file.ext");
std::cout << "filename and extension : " << p.filename() << std::endl; // file.ext
std::cout << "filename only          : " << p.stem() << std::endl;     // file

I also use this snippet to determine the appropriate slash character:

boost::filesystem::path slash("/");
boost::filesystem::path::string_type preferredSlash = slash.make_preferred().native();

and then replace the slashes with the preferred slash for the OS. Useful if one is constantly deploying between Linux/Windows.

For C++17:

#include <filesystem>


std::filesystem::path p("c:/dir/dir/file.ext");
std::cout << "filename and extension: " << p.filename() << std::endl; // "file.ext"
std::cout << "filename only: " << p.stem() << std::endl;              // "file"

Reference about filesystem: http://en.cppreference.com/w/cpp/filesystem


As suggested by @RoiDanto, for the output formatting, std::out may surround the output with quotations, e.g.:

filename and extension: "file.ext"

You can convert std::filesystem::path to std::string by p.filename().string() if that's what you need, e.g.:

filename and extension: file.ext

For linux or unix machines, the os has two functions dealing with path and file names. use man 3 basename to get more information about these functions. The advantage of using the system provided functionality is that you don't have to install boost or needing to write your own functions.

#include <libgen.h>
char *dirname(char *path);
char *basename(char *path);

Example code from the man page:

   char *dirc, *basec, *bname, *dname;
char *path = "/etc/passwd";


dirc = strdup(path);
basec = strdup(path);
dname = dirname(dirc);
bname = basename(basec);
printf("dirname=%s, basename=%s\n", dname, bname);

Because of the non-const argument type of the basename() function, it is a little bit non-straight forward using this inside C++ code. Here is a simple example from my code base:

string getFileStem(const string& filePath) const {
char* buff = new char[filePath.size()+1];
strcpy(buff, filePath.c_str());
string tmp = string(basename(buff));
string::size_type i = tmp.rfind('.');
if (i != string::npos) {
tmp = tmp.substr(0,i);
}
delete[] buff;
return tmp;
}

The use of new/delete is not good style. I could have put it into a try/catch block in case something happened between the two calls.

Nickolay Merkin's and Yuchen Zhong's answers are great, but however from the comments you can see that it is not fully accurate.

The implicit conversion to std::string when printing will wrap the file name in quotations. The comments aren't accurate either.

path::filename() and path::stem() returns a new path object and path::string() returns a reference to a string. Thus something like std::cout << file_path.filename().string() << "\n" might cause problems with dangling reference since the string that the reference points to might have been destroyed.

The following trick to extract the file name from a file path with no extension in c++ (no external libraries required):

#include <iostream>
#include <string>


using std::string;


string getFileName(const string& s) {
char sep = '/';
#ifdef _WIN32
sep = '\\';
#endif
size_t i = s.rfind(sep, s.length());
if (i != string::npos)
{
string filename = s.substr(i+1, s.length() - i);
size_t lastindex = filename.find_last_of(".");
string rawname = filename.substr(0, lastindex);
return(rawname);
}


return("");
}


int main(int argc, char** argv) {


string path = "/home/aymen/hello_world.cpp";
string ss = getFileName(path);
std::cout << "The file name is \"" << ss << "\"\n";
}