从路径中获取文件名

时间:2011-12-15 13:05:02

标签: c++ visual-c++

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

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

在这个例子中,我应该得到" MyFile"。没有延期。

22 个答案:

答案 0 :(得分:47)

可能的解决方案:

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

答案 1 :(得分: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 == '/';
    }
};

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

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

答案 2 :(得分:34)

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

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::string&amp; std::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

答案 3 :(得分:26)

_splitpath应该做你需要的。你当然可以手动完成,但_splitpath也可以处理所有特殊情况。

编辑:

正如BillHoag所说,建议在可用时使用名为_splitpath_s的更安全的_splitpath版本。

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

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;

答案 4 :(得分:12)

您还可以使用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,但我不确定为什么这是一个缺点。

答案 5 :(得分:12)

如果你可以使用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,则只能通过一行sudo apt-get install libboost-all-dev安装boost库(参考How to Install boost on Ubuntu?

答案 6 :(得分:10)

<强>功能:

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

答案 7 :(得分:7)

cpp17中最简单的方法是:

将#include experimental / filesystem和filename()用于带扩展名的文件名和使用没有扩展名的stem()。

   #include <iostream>
    #include <experimental/filesystem>
    namespace fs = std::experimental::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

答案 8 :(得分:7)

来自C ++文档 - 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

答案 9 :(得分:5)

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

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

答案 10 :(得分:5)

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

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

答案 11 :(得分: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建议但不适用于wchar_t *, VS Enterprise 2015

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

答案 12 :(得分:3)

我会这样做......

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

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

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

... Simples

答案 13 :(得分: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包含fileName。

答案 14 :(得分:2)

您可以使用std :: filesystem很好地完成它:

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

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

答案 15 :(得分:2)

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

相反,请使用他们的安全版本,即_splitpath_s()_wsplitpath_s()

答案 16 :(得分: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(带有split,trim等),QFile,QPath,QFileInfo等来操作文件,文件名和目录。当然,这也是跨文化的。

答案 17 :(得分:1)

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

答案 18 :(得分:1)

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

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

要仅获取文件名不带扩展名,您可以将 c == '\0' 更改为 c == '.'

答案 19 :(得分:0)

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

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

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

答案 20 :(得分:0)

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

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

答案 21 :(得分:0)

我实现了可能满足您需求的功能。 它基于string_view的 constexpr 函数 find_last_of (自c ++ 17起),可以在编译时进行计算

 android:requestLegacyExternalStorage="true"