从路径获取文件名


82

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

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

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


1
从后面搜索,直到遇到退格?
Kerrek SB 2011年

2
@KerrekSB,您的意思是反斜线;)
Nim

我有一个std :: string,其中包含文件“ c:\\ MyDirectory \\ Myfile.pdf”的路径,我需要将此文件重命名为myfile_md.pdf,因此我需要从该路径获取文件名。
nidhal 2011年

1
如果您需要对文件路径进行大量工作,请考虑使用Boost FileSystem boost.org/doc/libs/release/libs/filesystem/v3/doc/index.htm
edA-qa mort-ora-y

2
@Nim:是的!我一定是一直在间隔...
Kerrek SB 2011年

Answers:


29

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

2
_splitpath我的机器上没有任何附件。
James Kanze 2011年

9
我的Visual Studio,G ++和Sun CC。有完善的便携式解决方案时,为什么要使用非标准的产品。
James Kanze 2011年

2
@James,链接到的页面说它在中<stdlib.h>。至于可移植性,也许您可​​以列举“完美的便携式解决方案”的一些例子?
Synetech

2
@Synetech链接到的页面描述了Microsoft扩展名,而不是<stdlib.h>。而最明显的便携式解决方案是boost::filesystem
James Kanze 2012年

3
@詹姆斯,你不必_splitpathstdlib.h你的VS的副本?然后,您可能需要进行VS的修复安装。
Synetech

62

可能的解决方案:

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

最简单总是最好!
让弗朗索瓦法布尔

60

任务非常简单,因为基本文件名只是字符串的一部分,从文件夹的最后一个分隔符开始:

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...)一起使用

如果将aconst 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

在这个用例中,一切都很好(原始问题得到了解答),但是扩展删除器并不完美-如果我们在其中传递“ /home/user/my.dir/myfile”之类的信息,它将失败
avtomaton

@avtomaton扩展名删除功能应用于文件名而不是路径。(base_name请先申请。)
Pixelchemist 2015年

我理解了(这就是为什么我写了原始问题并且在此用例中一切正常的原因)。只是想为那些尝试使用这些摘要的人指出这个问题。
2015年

非常好的解释。它增强了对问题的结构理解。谢谢
hell_ical_vortex

38

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

正确执行此操作将需要一些与系统相关的代码:在Windows中,可以为'\\''/'可以是路径分隔符;在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 );
}

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


9
使用string::find_last_of而不是操纵反向迭代器怎么样?
卢·图拉

@LucTouraille为什么要学习两种做事的方式?您需要除之外的所有容器的反向迭代器string,因此无论如何都必须学习它们。并且已经学习了它们,因此没有理由去烦恼所有to肿的接口std::string
James Kanze 2011年

注意:<filesystem>标头随Visual Studio 2015及更高版本一起提供,因此您不必添加对boost的依赖即可使用它。
IInspectable '17

15

您还可以使用外壳程序路径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/zh-CN/library/windows/desktop/bb773589(v=vs.85).aspx

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


我从路径获取文件名的首选解决方案。
安德里亚斯(Andreas)

15

如果可以使用boost,

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

这就是全部。

我建议您使用Boost库。使用C ++时,Boost给您带来许多便利。它支持几乎所有平台。如果您使用Ubuntu,则只能通过一行安装boost库sudo apt-get install libboost-all-dev(请参阅如何在Ubuntu上安装boost?


14

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

对扩展名和不扩展名使用#include <filesystem>filename()表示文件stem()名。

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

    int main()
    {
        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';
    }

输出:

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

参考:cppreference


它不再处于“实验性”状态
Vit

13

功能:

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

8

从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:\windows\winhelp.exe
 path: c:\windows
 file: winhelp.exe

如果什么也没发现,别忘了(并处理)find_last_of返回的string::npos结果。
congusbongus

@congusbongus是的,但是当只是文件名(没有路径)时,没有分割文件路径的感觉:)
jave.web

@ jave.web确实有意义,必须处理返回“ string :: npos”。为此实现一个功能应该能够处理不同的输入,包括“ just filename”。否则,如果它在实际实现中存在错误,将毫无用处。
winux

@winux这将考虑已经有效的 PATH ...如果您不信任输入,则应该首先验证路径。
jave.web 16/09/23

@winux无论如何string::npos由于这和string::substr实现的方式,不需要进行检查。a)string::npos 作为“长度”传递=>substr具有记录所有读取直到结束的行为。b)substr被赋予“ string::npos + 1”且没有长度:string::npos记录为具有值-1,因此计算结果为0=>字符串的开始,并且长度的默认值substrnpos=>也可以“仅用于文件名” cplusplus.com/reference / string / string / substr cplusplus.com/reference/string/string/npos
jave.web

5

具有统一初始化和匿名内联lambda的C ++ 11变体(受James Kanze版本启发)。

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

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


简短而有趣,尽管它仅适用于非Windows路径。
Volomike

您可以随时更改lambda return以return c == '/' || c == '\\';使其在Windows上运行
ziomq1991 '16

要处理诸如“”,“ ///”和“ dir1 / dir2 /”之类的路径,请在上面的return语句之前添加以下代码(参见POSIX basename()): if (pathname.size() == 0) return "."; auto iter = pathname.rbegin(); auto rend = pathname.rend(); while (iter != rend && *iter == '/') ++iter; if (iter == rend) /* pathname has only path separators */ return "/"; pathname = std::string(pathname.begin(), iter.base());
Gidfiddle

5

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对象。


4

这是唯一真正对我有用的东西:

#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建议的内容几乎与VS Enterprise 2015的wchar_t *不兼容

_splitpath也可以正常工作,但是我不希望猜测我需要多少个char [?]字符;我猜有些人可能需要此控件。

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


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

3

我会...

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

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

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

简单...


这对我所知道的任何系统都不起作用。(一个接受'\\'为路径分隔符的系统使用'/',因此您需要匹配两者。)而且我不确定您会期待什么。
James Kanze 2011年

好吧,修改它以匹配其中的任何一个,不要笨拙。并期待第一个点(。)
TomP89 2011年

您仍然必须找到最后一个点,而不是第一个。(反向迭代器是您的朋友!)
James Kanze 2011年

是的,很好。因此,对于file.ext.ext,您将不是想要提取file.ext。:)
TomP89 2011年

想必。这是通常的惯例,在任何情况下:my.source.cpp被编译到my.source.obj,例如(扩展.cpp替换为.obj)。
James Kanze 2011年

2
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包含文件名。


3
哇-从路径“获取文件名”中有很多代码... :)
Nim

4
@Nim我的印象也是如此。在我自己的代码中,我使用单线:boost::filesystem::path( path ).filename()
James Kanze 2011年

我有一个具有该代码的CFileInfo类。我只是将代码转储到这里,因为它已经过测试,并且我不想冒险。在此示例中,您仅可以使用约5行代码。
Lucian


2

这也应该工作:

// 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(带有分割,修剪等),QFile,QPath,QFileInfo等来操纵文件,文件名和目录。当然,它也跨界。


4
为了将来的代码读者使用,请使用具有有意义名称的临时变量,而不要将所有内容填充到一行代码中(在使用时,请将所有这些内容封装到一个函数getFilename或类似的东西中)。
Luc Touraille

编辑。但是关键是要简短,因为已经给出了几个可行的答案。
typedef

1
我认为这是错误的。您是否不应该将最后一部分:“ strPath.size()-strPath.find_last_of(“。”)”替换为“ strPath.find_last_of(“。”)-iLastSeparator”
taktak004 2014年

@ taktak004是正确的,应该是`return strPath.substr((iLastSeparator = strPath.find_last_of(“ /”))!= std :: string :: npos?iLastSeparator + 1:0,strPath.find_last_of(“。” )-iLastSeparator);`
phenmod

2

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

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

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

0

长期以来,我一直在寻找一种能够正确分解文件路径的函数。对我来说,这段代码可以在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'

[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:  ''

希望对您有所帮助:)


0

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

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

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


0

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

    std::string file = std::regex_replace(path, std::regex("(.*\\/)|(\\..*)"), "");
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.