Get a file name from a path

C++Visual C++

C++ Problem Overview


What is the simplest way to get the file name that from a path?

string filename = "C:\\MyDirectory\\MyFile.bat"

In this example, I should get "MyFile". without extension.

C++ Solutions


Solution 1 - C++

The task is fairly simple as the base filename is just the part of the string starting at the last delimeter for folders:

std::string base_filename = path.substr(path.find_last_of("/\\") + 1)

If the extension is to be removed as well the only thing to do is find the last . and take a substr to this point

std::string::size_type const p(base_filename.find_last_of('.'));
std::string file_without_extension = base_filename.substr(0, p);

Perhaps there should be a check to cope with files solely consisting of extensions (ie .bashrc...)

If you split this up into seperate functions you're flexible to reuse the single tasks:

template<class T>
T base_name(T const & path, T const & delims = "/\\")
{
  return path.substr(path.find_last_of(delims) + 1);
}
template<class T>
T remove_extension(T const & filename)
{
  typename T::size_type const p(filename.find_last_of('.'));
  return p > 0 && p != T::npos ? filename.substr(0, p) : filename;
}

The code is templated to be able to use it with different std::basic_string instances (i.e. std::string & std::wstring...)

The downside of the templation is the requirement to specify the template parameter if a const char * is passed to the functions.

So you could either:

A) Use only std::string instead of templating the code
std::string base_name(std::string const & path)
{
  return path.substr(path.find_last_of("/\\") + 1);
}
B) Provide wrapping function using std::string (as intermediates which will likely be inlined / optimized away)
inline std::string string_base_name(std::string const & path)
{
  return base_name(path);
}
C) Specify the template parameter when calling with const char *.
std::string base = base_name<std::string>("some/path/file.ext");

Result

std::string filepath = "C:\\MyDirectory\\MyFile.bat";
std::cout << remove_extension(base_name(filepath)) << std::endl;

Prints

MyFile

Solution 2 - C++

A possible solution:

string filename = "C:\\MyDirectory\\MyFile.bat";

// Remove directory if present.
// Do this before extension removal incase directory has a period character.
const size_t last_slash_idx = filename.find_last_of("\\/");
if (std::string::npos != last_slash_idx)
{
    filename.erase(0, last_slash_idx + 1);
}

// Remove extension if present.
const size_t period_idx = filename.rfind('.');
if (std::string::npos != period_idx)
{
    filename.erase(period_idx);
}

Solution 3 - C++

The Simplest way in C++17 is:

use the #include <filesystem> and filename() for filename with extension and stem() without extension.

#include <iostream>
#include <string>
#include <filesystem>
namespace fs = std::filesystem;

int main()
{
  std::string filename = "C:\\MyDirectory\\MyFile.bat";

  std::cout << fs::path(filename).filename() << '\n'
    << fs::path(filename).stem() << '\n'
    << fs::path("/foo/bar.txt").filename() << '\n'
    << fs::path("/foo/bar.txt").stem() << '\n'
    << fs::path("/foo/.bar").filename() << '\n'
    << fs::path("/foo/bar/").filename() << '\n'
    << fs::path("/foo/.").filename() << '\n'
    << fs::path("/foo/..").filename() << '\n'
    << fs::path(".").filename() << '\n'
    << fs::path("..").filename() << '\n'
    << fs::path("/").filename() << '\n';
}

Which can be compiled with g++ -std=c++17 main.cpp -lstdc++fs, and outputs:

"MyFile.bat"
"MyFile"
"bar.txt"
"bar"
".bar"
""
"."
".."
"."
".."
"/"

Reference: cppreference

Solution 4 - C++

The simplest solution is to use something like boost::filesystem. If for some reason this isn't an option...

Doing this correctly will require some system dependent code: under Windows, either '\\' or '/' can be a path separator; under Unix, only '/' works, and under other systems, who knows. The obvious solution would be something like:

std::string
basename( std::string const& pathname )
{
	return std::string( 
		std::find_if( pathname.rbegin(), pathname.rend(),
		              MatchPathSeparator() ).base(),
		pathname.end() );
}

, with MatchPathSeparator being defined in a system dependent header as either:

struct MatchPathSeparator
{
	bool operator()( char ch ) const
	{
		return ch == '/';
	}
};

for Unix, or:

struct MatchPathSeparator
{
	bool operator()( char ch ) const
	{
		return ch == '\\' || ch == '/';
	}
};

for Windows (or something still different for some other unknown system).

EDIT: I missed the fact that he also wanted to suppress the extention. For that, more of the same:

std::string
removeExtension( std::string const& filename )
{
	std::string::const_reverse_iterator
						pivot
			= std::find( filename.rbegin(), filename.rend(), '.' );
	return pivot == filename.rend()
		? filename
		: std::string( filename.begin(), pivot.base() - 1 );
}

The code is a little bit more complex, because in this case, the base of the reverse iterator is on the wrong side of where we want to cut. (Remember that the base of a reverse iterator is one behind the character the iterator points to.) And even this is a little dubious: I don't like the fact that it can return an empty string, for example. (If the only '.' is the first character of the filename, I'd argue that you should return the full filename. This would require a little bit of extra code to catch the special case.) }

Solution 5 - C++

_splitpath should do what you need. You could of course do it manually but _splitpath handles all special cases as well.

EDIT:

As BillHoag mentioned it is recommended to use the more safe version of _splitpath called _splitpath_s when available.

Or if you want something portable you could just do something like this

std::vector<std::string> splitpath(
  const std::string& str
  , const std::set<char> delimiters)
{
  std::vector<std::string> result;

  char const* pch = str.c_str();
  char const* start = pch;
  for(; *pch; ++pch)
  {
    if (delimiters.find(*pch) != delimiters.end())
    {
      if (start != pch)
      {
        std::string str(start, pch);
        result.push_back(str);
      }
      else
      {
        result.push_back("");
      }
      start = pch + 1;
    }
  }
  result.push_back(start);

  return result;
}

...
std::set<char> delims{'\\'};

std::vector<std::string> path = splitpath("C:\\MyDirectory\\MyFile.bat", delims);
cout << path.back() << endl;

Solution 6 - C++

If you can use boost,

#include <boost/filesystem.hpp>
boost::filesystem::path p("C:\\MyDirectory\\MyFile.bat");
string basename = p.filename().string();
//or 
//string basename = boost::filesystem::path("C:\\MyDirectory\\MyFile.bat").filename().string();

This is all.

I recommend you to use boost library. Boost gives you a lot of conveniences when you work with C++. It supports almost all platforms. If you use Ubuntu, you can install boost library by only one line sudo apt-get install libboost-all-dev (ref. https://stackoverflow.com/questions/12578499/how-to-install-boost-on-ubuntu)

Solution 7 - C++

You can also use the shell Path APIs PathFindFileName, PathRemoveExtension. Probably worse than _splitpath for this particular problem, but those APIs are very useful for all kinds of path parsing jobs and they take UNC paths, forward slashes and other weird stuff into account.

wstring filename = L"C:\\MyDirectory\\MyFile.bat";
wchar_t* filepart = PathFindFileName(filename.c_str());
PathRemoveExtension(filepart); 

http://msdn.microsoft.com/en-us/library/windows/desktop/bb773589(v=vs.85).aspx

The drawback is that you have to link to shlwapi.lib, but I'm not really sure why that's a drawback.

Solution 8 - C++

Function:

#include <string>

std::string
basename(const std::string &filename)
{
    if (filename.empty()) {
        return {};
    }

    auto len = filename.length();
    auto index = filename.find_last_of("/\\");

    if (index == std::string::npos) {
        return filename;
    }

    if (index + 1 >= len) {

        len--;
        index = filename.substr(0, len).find_last_of("/\\");

        if (len == 0) {
            return filename;
        }

        if (index == 0) {
            return filename.substr(1, len - 1);
        }

        if (index == std::string::npos) {
            return filename.substr(0, len);
        }

        return filename.substr(index + 1, len - index - 1);
    }

    return filename.substr(index + 1, len - index);
}

Tests:

#define CATCH_CONFIG_MAIN
#include <catch/catch.hpp>

TEST_CASE("basename")
{
    CHECK(basename("") == "");
    CHECK(basename("no_path") == "no_path");
    CHECK(basename("with.ext") == "with.ext");
    CHECK(basename("/no_filename/") == "no_filename");
    CHECK(basename("no_filename/") == "no_filename");
    CHECK(basename("/no/filename/") == "filename");
    CHECK(basename("/absolute/file.ext") == "file.ext");
    CHECK(basename("../relative/file.ext") == "file.ext");
    CHECK(basename("/") == "/");
    CHECK(basename("c:\\windows\\path.ext") == "path.ext");
    CHECK(basename("c:\\windows\\no_filename\\") == "no_filename");
}

Solution 9 - C++

From C++ Docs - string::find_last_of

#include <iostream>       // std::cout
#include <string>         // std::string

void SplitFilename (const std::string& str) {
  std::cout << "Splitting: " << str << '\n';
  unsigned found = str.find_last_of("/\\");
  std::cout << " path: " << str.substr(0,found) << '\n';
  std::cout << " file: " << str.substr(found+1) << '\n';
}

int main () {
  std::string str1 ("/usr/bin/man");
  std::string str2 ("c:\\windows\\winhelp.exe");

  SplitFilename (str1);
  SplitFilename (str2);

  return 0;
}

Outputs:

Splitting: /usr/bin/man
 path: /usr/bin
 file: man
Splitting: c:\windows\winhelp.exe
 path: c:\windows
 file: winhelp.exe

Solution 10 - C++

C++11 variant (inspired by James Kanze's version) with uniform initialization and anonymous inline lambda.

std::string basename(const std::string& pathname)
{
    return {std::find_if(pathname.rbegin(), pathname.rend(),
                         [](char c) { return c == '/'; }).base(),
            pathname.end()};
}

It does not remove the file extension though.

Solution 11 - C++

The boost filesystem library is also available as the experimental/filesystem library and was merged into ISO C++ for C++17. You can use it like this:

#include <iostream>
#include <experimental/filesystem>

namespace fs = std::experimental::filesystem;

int main () {
    std::cout << fs::path("/foo/bar.txt").filename() << '\n'
}

Output:

"bar.txt"

It also works for std::string objects.

Solution 12 - C++

this is the only thing that actually finally worked for me:

#include "Shlwapi.h"

CString some_string = "c:\\path\\hello.txt";
LPCSTR file_path = some_string.GetString();
LPCSTR filepart_c = PathFindFileName(file_path);
LPSTR filepart = LPSTR(filepart_c);
PathRemoveExtension(filepart);

pretty much what Skrymsli suggested but doesn't work with wchar_t*, VS Enterprise 2015

_splitpath worked as well, but I don't like having to guess at how many char[?] characters I'm going to need; some people probably need this control, i guess.

CString c_model_name = "c:\\path\\hello.txt";
char drive[200];
char dir[200];
char name[200];
char ext[200];
_splitpath(c_model_name, drive, dir, name, ext);

I don't believe any includes were needed for _splitpath. No external libraries (like boost) were needed for either of these solutions.

Solution 13 - C++

std::string getfilename(std::string path)
{
	path = path.substr(path.find_last_of("/\\") + 1);
	size_t dot_i = path.find_last_of('.');
	return path.substr(0, dot_i);
}

Solution 14 - C++

I would do it by...

Search backwards from the end of the string until you find the first backslash/forward slash.

Then search backwards again from the end of the string until you find the first dot (.)

You then have the start and end of the file name.

Simples...

Solution 15 - C++

You can use the std::filesystem to do it quite nicely:

#include <filesystem>
namespace fs = std::experimental::filesystem;

fs::path myFilePath("C:\\MyDirectory\\MyFile.bat");
fs::path filename = myFilePath.stem();

Solution 16 - C++

m_szFilePath.MakeLower();
CFileFind finder;
DWORD buffSize = MAX_PATH;
char longPath[MAX_PATH];
DWORD result = GetLongPathName(m_szFilePath, longPath, MAX_PATH );

if( result == 0)
{
	m_bExists = FALSE;
	return;
}
m_szFilePath = CString(longPath);
m_szFilePath.Replace("/","\\");
m_szFilePath.Trim();
//check if it does not ends in \ => remove it
int length = m_szFilePath.GetLength();
if( length > 0 && m_szFilePath[length - 1] == '\\' )
{
	m_szFilePath.Truncate( length - 1 );
}
BOOL bWorking = finder.FindFile(this->m_szFilePath);
if(bWorking){
	bWorking = finder.FindNextFile();
	finder.GetCreationTime(this->m_CreationTime);
	m_szFilePath = finder.GetFilePath();
	m_szFileName = finder.GetFileName();

	this->m_szFileExtension = this->GetExtension( m_szFileName );

	m_szFileTitle = finder.GetFileTitle();
	m_szFileURL = finder.GetFileURL();
	finder.GetLastAccessTime(this->m_LastAccesTime);
	finder.GetLastWriteTime(this->m_LastWriteTime);
	m_ulFileSize = static_cast<unsigned long>(finder.GetLength());
	m_szRootDirectory = finder.GetRoot();
	m_bIsArchive = finder.IsArchived();
	m_bIsCompressed = finder.IsCompressed();
	m_bIsDirectory = finder.IsDirectory();
	m_bIsHidden = finder.IsHidden();
	m_bIsNormal = finder.IsNormal();
	m_bIsReadOnly = finder.IsReadOnly();
	m_bIsSystem = finder.IsSystem();
	m_bIsTemporary = finder.IsTemporary();
	m_bExists = TRUE;
	finder.Close();
}else{
	m_bExists = FALSE;
}

The variable m_szFileName contains the fileName.

Solution 17 - C++

This should work too :

// strPath = "C:\\Dir\\File.bat" for example
std::string getFileName(const std::string& strPath)
{
    size_t iLastSeparator = 0;
    return strPath.substr((iLastSeparator = strPath.find_last_of("\\")) != std::string::npos ? iLastSeparator + 1 : 0, strPath.size() - strPath.find_last_of("."));
}

If you can use it, Qt provide QString (with split, trim etc), QFile, QPath, QFileInfo etc to manipulate files, filenames and directories. And of course it's also cross plaftorm.

Solution 18 - C++

Dont use _splitpath() and _wsplitpath(). They are not safe, and they are obsolete!

Instead, use their safe versions, namely _splitpath_s() and _wsplitpath_s()

Solution 19 - C++

A really simple and short function that returns the filename+path that I made which uses no dependencies:

const char* GetFileNameFromPath(const char* _buffer)
{
	char c;
	int  i;
	for (i = 0; ;++i) {
		c = *((char*)_buffer+i);
		if (c == '\\' || c == '/')
			return GetFileNameFromPath((char*)_buffer + i + 1);
		if (c == '\0')
			return _buffer;
	}
	return "";
}

To only get the filename without the extension you could change c == '\0' to c == '.'.

Solution 20 - C++

For long time I was looking for a function able to properly decompose file path. For me this code is working perfectly for both Linux and Windows.

void decomposePath(const char *filePath, char *fileDir, char *fileName, char *fileExt)
{
    #if defined _WIN32
        const char *lastSeparator = strrchr(filePath, '\\');
    #else
        const char *lastSeparator = strrchr(filePath, '/');
    #endif

    const char *lastDot = strrchr(filePath, '.');
    const char *endOfPath = filePath + strlen(filePath);
    const char *startOfName = lastSeparator ? lastSeparator + 1 : filePath;
    const char *startOfExt = lastDot > startOfName ? lastDot : endOfPath;

    if(fileDir)
        _snprintf(fileDir, MAX_PATH, "%.*s", startOfName - filePath, filePath);

    if(fileName)
        _snprintf(fileName, MAX_PATH, "%.*s", startOfExt - startOfName, startOfName);

    if(fileExt)
        _snprintf(fileExt, MAX_PATH, "%s", startOfExt);
}

Example results are:

[]
  fileDir:  ''
  fileName: ''
  fileExt:  ''

[.htaccess]
  fileDir:  ''
  fileName: '.htaccess'
  fileExt:  ''

[a.exe]
  fileDir:  ''
  fileName: 'a'
  fileExt:  '.exe'

[a\b.c]
  fileDir:  'a\'
  fileName: 'b'
  fileExt:  '.c'

[git-archive]
  fileDir:  ''
  fileName: 'git-archive'
  fileExt:  ''

[git-archive.exe]
  fileDir:  ''
  fileName: 'git-archive'
  fileExt:  '.exe'

[D:\Git\mingw64\libexec\git-core\.htaccess]
  fileDir:  'D:\Git\mingw64\libexec\git-core\'
  fileName: '.htaccess'
  fileExt:  ''

[D:\Git\mingw64\libexec\git-core\a.exe]
  fileDir:  'D:\Git\mingw64\libexec\git-core\'
  fileName: 'a'
  fileExt:  '.exe'

[D:\Git\mingw64\libexec\git-core\git-archive.exe]
  fileDir:  'D:\Git\mingw64\libexec\git-core\'
  fileName: 'git-archive'
  fileExt:  '.exe'

[D:\Git\mingw64\libexec\git.core\git-archive.exe]
  fileDir:  'D:\Git\mingw64\libexec\git.core\'
  fileName: 'git-archive'
  fileExt:  '.exe'

[D:\Git\mingw64\libexec\git-core\git-archiveexe]
  fileDir:  'D:\Git\mingw64\libexec\git-core\'
  fileName: 'git-archiveexe'
  fileExt:  ''

[D:\Git\mingw64\libexec\git.core\git-archiveexe]
  fileDir:  'D:\Git\mingw64\libexec\git.core\'
  fileName: 'git-archiveexe'
  fileExt:  ''

I hope this helps you also :)

Solution 21 - C++

shlwapi.lib/dll uses the HKCU registry hive internally.

It's best not to link to shlwapi.lib if you're creating a library or the product does not have a UI. If you're writing a lib then your code can be used in any project including those that don't have UIs.

If you're writing code that runs when a user is not logged in (e.g. service [or other] set to start at boot or startup) then there's no HKCU. Lastly, shlwapi are settlement functions; and as a result high on the list to deprecate in later versions of Windows.

Solution 22 - C++

A slow but straight forward regex solution:

	std::string file = std::regex_replace(path, std::regex("(.*\\/)|(\\..*)"), "");

Solution 23 - C++

I implemented a function that might meet your needs. It is based on string_view's constexpr function find_last_of (since c++17) which can be calculated at compile time

constexpr const char* base_filename(const char* p) {
    const size_t i = std::string_view(p).find_last_of('/');
    return std::string_view::npos == i ? p : p + i + 1 ;
}

//in the file you used this function
base_filename(__FILE__);

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionnidhalView Question on Stackoverflow
Solution 1 - C++PixelchemistView Answer on Stackoverflow
Solution 2 - C++hmjdView Answer on Stackoverflow
Solution 3 - C++eliasetmView Answer on Stackoverflow
Solution 4 - C++James KanzeView Answer on Stackoverflow
Solution 5 - C++AndersKView Answer on Stackoverflow
Solution 6 - C++plhnView Answer on Stackoverflow
Solution 7 - C++SkrymsliView Answer on Stackoverflow
Solution 8 - C++Rian QuinnView Answer on Stackoverflow
Solution 9 - C++jave.webView Answer on Stackoverflow
Solution 10 - C++alvekoView Answer on Stackoverflow
Solution 11 - C++Adam EricksonView Answer on Stackoverflow
Solution 12 - C++FractalView Answer on Stackoverflow
Solution 13 - C++BeyondoView Answer on Stackoverflow
Solution 14 - C++TomP89View Answer on Stackoverflow
Solution 15 - C++GuidedHackingView Answer on Stackoverflow
Solution 16 - C++LucianView Answer on Stackoverflow
Solution 17 - C++typedefView Answer on Stackoverflow
Solution 18 - C++hkBattousaiView Answer on Stackoverflow
Solution 19 - C++AlexView Answer on Stackoverflow
Solution 20 - C++no one specialView Answer on Stackoverflow
Solution 21 - C++BrookView Answer on Stackoverflow
Solution 22 - C++Sava B.View Answer on Stackoverflow
Solution 23 - C++ZhiyongZHAOView Answer on Stackoverflow