95

I have a list of files stored in a .log in this syntax:

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

I want to extract the name and the extension from this files. Can you give a example of a simple way to do this?

Kiril Kirov
  • 37,467
  • 22
  • 115
  • 187
Octavian
  • 4,519
  • 8
  • 28
  • 39

9 Answers9

210

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
Nickolay Merkin
  • 2,673
  • 1
  • 16
  • 14
  • 15
    Actually, p.filename() is of type path, and will be surrounded by quotes when implicitly converted, so you will get: filename and extension: "file.ext" You may want p.filename().string() instead. – James Hirschorn Feb 18 '16 at 19:54
  • 9
    With C++14/C++17 you can use `std::experimental::filesystem` resp `std::filesystem`. See post from Yuchen Zhong below. – Roi Danton Apr 12 '17 at 14:15
  • To get the extension(with dot), use p.ext().string() – Yibo Jan 04 '19 at 01:55
  • 5
    The asker wanted a simple way. Adding boost to a project for this functionality alone is not a simple way. std::filesystem is the simple way. – KKlouzal Nov 30 '19 at 13:26
  • 3
    C++17 included into the standard library. Use fresh compiler... or import boost. – Nickolay Merkin Dec 02 '19 at 16:29
  • This doesn't work for paths that are not native: on Mac this won't work when given a Windows path. Is there a cross-platform method? – gil_mo Aug 02 '21 at 12:57
52

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
Yuchen
  • 30,852
  • 26
  • 164
  • 234
  • Hey @RoiDanton, thanks for the edit! I just checked the example code in the reference link again, it doesn't seem that it is necessary to convert the return type from `std::filesystem::path` to `std::string` in order to be able to use `std::cout`. http://en.cppreference.com/w/cpp/filesystem/path/filename But if you think otherwise, feel free to comment or edit the post again. – Yuchen Apr 12 '17 at 14:39
  • Thats true, `std::cout` can rely on implicit conversion. However since the comments after `std::cout` say file.ext and file, either `.string()` has to be added to the comments or they should be "file.ext" and "file". With Visual C++ there is indeed no difference (even without `string()` the output is without quotation marks), but with gcc 6.1 the output is with quotation marks if `.string()` is omitted. See http://coliru.stacked-crooked.com/view?id=a55ea60bbd36a8a3 – Roi Danton Apr 12 '17 at 14:56
  • @RoiDanton, hey, that's interesting insight. I will update the post again. Thanks for sharing this! – Yuchen Apr 12 '17 at 15:21
19

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.

Kos
  • 70,399
  • 25
  • 169
  • 233
  • 2
    +1 Definately the way to go. The example on the main site gives a way to search a directory: Use path.extension() method to search for logs (see http://www.boost.org/doc/libs/1_36_0/libs/filesystem/doc/index.htm) – Tom Dec 13 '10 at 16:18
  • Indeed this is in most cases the way to go however it involves adding in some cases an undesirable dependency on an external library. If you want to work only what the C++ standard provides I suggest looking at the C++ regex, where you can define a regular expression to do what you want to (plenty of examples on the internet). The advantage - no overhead due to some additional dependencies. However this also leave one question open - is multiplatforming required? Boost takes care of the path-style no matter if you are using Windows or Linux. Using regex you have to do that on your own. – rbaleksandar Jul 11 '14 at 20:13
18

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  = "";
    }
}
chappjc
  • 30,359
  • 6
  • 75
  • 132
Sylvain Defresne
  • 42,429
  • 12
  • 75
  • 85
4

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.
Nim
  • 33,299
  • 2
  • 62
  • 101
  • And -1 for being non-portable :) – Kos Dec 13 '10 at 16:12
  • Why the down vote? If there is anything wrong with what I said, let me know and I'll fix! – Nim Dec 13 '10 at 16:13
  • 2
    @Kos, well that's harsh! it matches what the OP wants, the file is windows based, and there was no portability requirement! – Nim Dec 13 '10 at 16:14
  • At the very least, a valid Windows path can have the directories separated by `/` as well. And I don't even know whether there exist more caveats in path specifications, so my thinking is simple - if there's a good library which does what I want, I should use it, because it will achieve my goal probably better than i can. ;) – Kos Dec 13 '10 at 16:17
  • @Kos, ditto, however, sometimes all that is required is a fly swatter... ;) – Nim Dec 13 '10 at 16:56
  • 3
    @Nim, but isn't there a `boost::insects::disperser` generic template for that? :) – Kos Dec 13 '10 at 17:28
1

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";
}
Aymen Alsaadi
  • 1,329
  • 1
  • 11
  • 12
0

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.

0

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.

Kemin Zhou
  • 6,264
  • 2
  • 48
  • 56
-1

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.