从路径中获取文件名

Get a file name from a path

本文关键字:文件名 获取 路径      更新时间:2023-10-16

从路径中获取文件名的最简单方法是什么?

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

在这个例子中,我应该得到"MyFile"。 没有扩展名。

该任务相当简单,因为基本文件名只是从文件夹的最后一个分位数开始的字符串的一部分:

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

如果扩展名也要删除,唯一要做的就是找到最后一个.并为此substr

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

也许应该进行检查以处理仅由扩展名组成的文件(即.bashrc...

如果将其拆分为单独的函数,则可以灵活地重用单个任务:

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;
}

代码被模板化,以便能够将其用于不同的std::basic_string实例(即 std::stringstd::wstring...

模板

的缺点是,如果将const char *传递给函数,则需要指定模板参数。

因此,您可以:

A( 仅使用std::string而不是模板化代码

std::string base_name(std::string const & path)
{
  return path.substr(path.find_last_of("/\") + 1);
}

B(使用std::string提供包装功能(作为中间体,可能会被内联/优化(

inline std::string string_base_name(std::string const & path)
{
  return base_name(path);
}

C( 使用 const char * 调用时指定模板参数。

std::string base = base_name<std::string>("some/path/file.ext");

结果

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

指纹

MyFile

一个可能的解决方案:

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);
}

C++17 中最简单的方法是:

对带扩展

名的文件名和不带扩展名的stem()使用#include <filesystem>filename()

#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';
}

可以用 g++ -std=c++17 main.cpp -lstdc++fs 编译,输出:

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

参考:cpp首选项

最简单的解决方案是使用 boost::filesystem . 如果出于某种原因,这不是一个选择...

正确执行此操作将需要一些与系统相关的代码:在窗口('''/'可以是路径分隔符;在Unix下,只有'/'有效,在其他系统下,谁知道呢。 显而易见的解决方案是这样的:

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

MatchPathSeparator在系统依赖标头中定义作为:

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

对于 Unix,或者:

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

Windows(或者对于其他一些未知的东西仍然不同系统(。

编辑:我错过了他也想抑制扩展的事实。为此,更多相同:

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 );
}

代码稍微复杂一些,因为在这种情况下,基础反向迭代器位于我们要剪切的错误一侧。(请记住,反向迭代器的基是迭代器指向的字符。 甚至这有点可疑:我例如,不喜欢它可以返回空字符串的事实。(如果唯一的'.'是文件名的第一个字符,我会争辩您应该返回完整的文件名。 这需要一点一些额外的代码来捕捉特殊情况。 }

_splitpath应该做你需要的。您当然可以手动执行此操作,但_splitpath也可以处理所有特殊情况。

编辑:

正如BillHoag所提到的,建议使用更安全的_splitpath版本,称为_splitpath_s。

或者如果你想要一些便携式的东西,你可以做这样的事情

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;

如果你可以使用提升,

#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();

仅此而已。

我建议您使用提升库。当您使用C++时,Boost 为您提供了很多便利。它支持几乎所有平台。如果你使用 Ubuntu,你只需要一行sudo apt-get install libboost-all-dev来安装 boost 库(ref.如何在 Ubuntu 上安装 Boost(

你也可以使用 shell Path API PathFindFileName, PathRemoveExtension。 对于这个特定问题,可能比_splitpath更糟糕,但这些 API 对于各种路径解析作业都非常有用,它们考虑了 UNC 路径、正斜杠和其他奇怪的东西。

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

缺点

是你必须链接到shlwapi.lib,但我不确定为什么这是一个缺点。

函数:

#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);
}

测试:

#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");
}

来自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;
}

输出:

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

C++11 变体(灵感来自 James Kanze 的版本(,具有统一初始化和匿名内联 lambda。

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

但它不会删除文件扩展名。

boost filesystem库也可用作experimental/filesystem库,并于 C++17 合并到 ISO C++中。你可以像这样使用它:

#include <iostream>
#include <experimental/filesystem>
namespace fs = std::experimental::filesystem;
int main () {
    std::cout << fs::path("/foo/bar.txt").filename() << 'n'
}

输出:

"bar.txt"

它也适用于std::string对象。

这是

唯一最终对我有用的东西:

#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);

几乎是 Skrymsli 建议的,但不适用于 wchar_t*,VS 企业版 2015

_splitpath也有效,但我不喜欢猜测我需要多少个字符;我猜有些人可能需要这种控制。

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);

我不认为_splitpath需要任何包含。这两种解决方案都不需要外部库(如 boost(。

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);
}

我会通过以下方式做到这一点...

从字符串末尾向后搜索,直到找到第一个反斜杠/正斜杠。

然后从字符串的末尾再次向后搜索,直到找到第一个点 (.(

然后,您将拥有文件名的开头和结尾。

简单...

你可以使用 std::filesystem 来很好地做到这一点:

#include <filesystem>
namespace fs = std::experimental::filesystem;
fs::path myFilePath("C:\MyDirectory\MyFile.bat");
fs::path filename = myFilePath.stem();
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;
}

变量m_szFileName包含文件名。

不要使用 _splitpath()_wsplitpath() 。它们不安全,而且已经过时了!

相反,请使用其安全版本,即_splitpath_s()_wsplitpath_s()

这也

应该有效:

// 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("."));
}

如果你能使用它,Qt提供了QString(带有split,trim等(,QFile,QPath,QFileInfo等来操作文件,文件名和目录。当然,它也是交叉平台。

一个非常简单而简短的函数,它返回我所做的文件名+路径,它不使用依赖项:

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 == '')
            return _buffer;
    }
    return "";
}

要仅获取没有扩展名的文件名,您可以将c == ''更改为 c == '.'

这是最简单的版本:

#include <iostream>
#include <string>
int main()
{
  std::string filepath = "directory/file-name.txt";
    
  std::string filename = filepath.substr(filepath.find_last_of("/")+1, filepath.find_last_of(".") - filepath.find_last_of("/") - 1);
  
  std::cout << filename << std::endl;
}

返回:

file-name

很长一段时间以来,我一直在寻找能够正确分解文件路径的函数。对我来说,这段代码在Linux和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);
}

示例结果如下:

[]
  fileDir:  ''
  fileName: ''
  fileExt:  ''
[.htaccess]
  fileDir:  ''
  fileName: '.htaccess'
  fileExt:  ''
[a.exe]
  fileDir:  ''
  fileName: 'a'
  fileExt:  '.exe'
[ab.c]
  fileDir:  'a'
  fileName: 'b'
  fileExt:  '.c'
[git-archive]
  fileDir:  ''
  fileName: 'git-archive'
  fileExt:  ''
[git-archive.exe]
  fileDir:  ''
  fileName: 'git-archive'
  fileExt:  '.exe'
[D:Gitmingw64libexecgit-core.htaccess]
  fileDir:  'D:Gitmingw64libexecgit-core'
  fileName: '.htaccess'
  fileExt:  ''
[D:Gitmingw64libexecgit-corea.exe]
  fileDir:  'D:Gitmingw64libexecgit-core'
  fileName: 'a'
  fileExt:  '.exe'
[D:Gitmingw64libexecgit-coregit-archive.exe]
  fileDir:  'D:Gitmingw64libexecgit-core'
  fileName: 'git-archive'
  fileExt:  '.exe'
[D:Gitmingw64libexecgit.coregit-archive.exe]
  fileDir:  'D:Gitmingw64libexecgit.core'
  fileName: 'git-archive'
  fileExt:  '.exe'
[D:Gitmingw64libexecgit-coregit-archiveexe]
  fileDir:  'D:Gitmingw64libexecgit-core'
  fileName: 'git-archiveexe'
  fileExt:  ''
[D:Gitmingw64libexecgit.coregit-archiveexe]
  fileDir:  'D:Gitmingw64libexecgit.core'
  fileName: 'git-archiveexe'
  fileExt:  ''

我希望这也可以帮助您:)

shlwapi.lib/dll内部使用HKCU注册表配置单元。

如果您

正在创建库或产品没有 UI,最好不要链接到shlwapi.lib。 如果您正在编写库,那么您的代码可以在任何项目中使用,包括那些没有 UI 的项目。

如果您正在编写在用户未登录时运行的代码(例如,服务[或其他]设置为在启动或启动时启动(,则没有HKCU。 最后,shlwapi是结算函数;因此,在更高版本的 Windows 中弃用列表名列前茅。

一个缓慢但直接的正则表达式解决方案:

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

我实现了一个可能满足你需求的函数。它基于 string_view 的 constexpr 函数 find_last_of(自 c++17 以来(,可以在编译时计算

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__);