如何在C++中从路径中提取文件名和扩展名

99

我有一个文件列表存储在一个名为.log的文件中,格式如下:

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

我想从这些文件中提取名称和扩展名。你能给出一个简单的示例来解决这个问题吗?

9个回答

213
使用boost::filesystem::path::stem来提取没有扩展名的文件名,而不是丑陋的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

同意。最简洁地回答了问题。 - AndyUK
15
实际上,p.filename() 是路径类型,并且在隐式转换时会被引号括起来,因此你将得到:文件名和扩展名:"file.ext"你可能想使用 p.filename().string() 代替。 - James Hirschorn
9
使用C++14/C++17,您可以使用 std::experimental::filesystemstd::filesystem。请参见下面的Yuchen Zhong的帖子。 - Roi Danton
5
问者希望有一个简单的方法。为了实现这个功能,添加boost到项目中并不是一个简单的方法。std::filesystem是简单的方法。 - KKlouzal
3
C++17将<filesystem>包含到标准库中。使用新的编译器...或者导入Boost库。 - Nickolay Merkin
显示剩余2条评论

54

对于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"

关于文件系统的参考资料:http://en.cppreference.com/w/cpp/filesystem


@RoiDanto所建议,对于输出的格式化,std::out可以用引号括起来,例如:

filename and extension: "file.ext"

如果需要,您可以通过p.filename().string()std :: filesystem :: path转换为std :: string,例如:

filename and extension: file.ext

嘿@RoiDanton,感谢您的编辑!我刚刚再次检查了参考链接中的示例代码,似乎没有必要将返回类型从std :: filesystem :: path转换为std :: string才能使用std :: cout。 http://en.cppreference.com/w/cpp/filesystem/path/filename 但是,如果您有不同的看法,请随时发表评论或再次编辑帖子。 - Yuchen
这是真的,std::cout 可以依赖于隐式转换。然而,由于 std::cout 后面的注释说 file.ext 和 file,因此必须在注释中添加 .string() 或者将它们改为 "file.ext" 和 "file"。使用 Visual C++ 确实没有区别(即使没有 string(),输出也没有引号),但是使用 gcc 6.1 如果省略 .string(),则输出会带有引号。请参见 http://coliru.stacked-crooked.com/view?id=a55ea60bbd36a8a3 - Roi Danton
@RoiDanton,嘿,这是有趣的见解。我会再次更新帖子。谢谢你分享! - Yuchen

19
如果你想要一个安全的方式(即在不同平台间可移植且不依赖路径假设),我建议使用boost::filesystem。它的用法大致如下:
boost::filesystem::path my_path( filename );

然后,您可以从此路径中提取各种数据。这是路径对象的文档。


顺便提一下:还要记得为了像使用路径

c:\foto\foto2003\shadow.gif

您需要在字符串字面值中转义\

const char* filename = "c:\\foto\\foto2003\\shadow.gif";

或者使用 / 代替:

const char* filename = "c:/foto/foto2003/shadow.gif";

这仅适用于在 "" 引号中指定字面字符串的情况,当您从文件加载路径时,问题不存在。


2
+1 绝对是正确的方法。主站点上的示例提供了一种搜索目录的方法:使用 path.extension() 方法来搜索日志(请参见 http://www.boost.org/doc/libs/1_36_0/libs/filesystem/doc/index.htm)。 - Tom
实际上,在大多数情况下,这是可行的方法,但它涉及在某些情况下添加一个不必要的外部库依赖。如果您只想使用C++标准提供的内容,我建议查看C++正则表达式,在那里您可以定义一个正则表达式来完成您想要的操作(互联网上有很多示例)。优点是 - 没有由于一些额外的依赖而产生的开销。然而,这也留下了一个问题 - 是否需要多平台支持?Boost会处理路径样式,无论您是使用Windows还是Linux。使用正则表达式,您必须自己处理。 - rbaleksandar

18

你需要从文件中读取文件名并存储到std::string中。你可以使用std::ostream的字符串提取运算符。一旦你将文件名存储在std::string中,你可以使用std::string::find_last_of方法查找最后一个分隔符。

类似这样的操作:

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

2
不想装逼,但应该是使用 path.substr 而不是 path.substring,对吧? - Björn

4
不是代码,但这是思路:
  1. 从输入流(std::ifstream)中读取一个std::string,每个实例读取的将是完整路径。
  2. 对该字符串执行find_last_of,查找\
  3. 从该位置提取子串到末尾,这将给出文件名。
  4. 查找.的最后出现位置,并在其两侧提取子串,这将给出名称+扩展名。

而且-1是因为它不可移植 :) - Kos
为什么要点踩?如果我说的有什么问题,请告诉我,我会进行修正! - Nim
2
@Kos,这太苛刻了!它符合OP的要求,文件是基于Windows的,并且没有可移植性要求! - Nim
至少,一个有效的Windows路径也可以用“/”分隔目录。而且我甚至不知道路径规范中是否存在更多的注意事项,所以我的想法很简单 - 如果有一个好的库可以做我想要的事情,我应该使用它,因为它可能比我做得更好。;) - Kos
@Kos,同意,然而有时候只需要一只苍蝇拍... ;) - Nim
3
@Nim,但是难道不有一个名为 boost::insects::disperser<T> 的通用模板可以用来做这个吗? :) - Kos

1
以下是在C++中提取没有扩展名的文件路径的文件名的技巧(不需要外部库):
#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";
}

自从C++17引入了std::filesystem::path以及它的stem()函数,这种方法已经过时了。 - Craig Reynolds

0

我也使用这个代码片段来确定适当的斜杠字符:

boost::filesystem::path slash("/");
    boost::filesystem::path::string_type preferredSlash = slash.make_preferred().native();

然后将斜杠替换为操作系统的首选斜杠。如果经常在Linux/Windows之间部署,则非常有用。


0

对于 Linux 或 Unix 系统,操作系统有两个处理路径和文件名的函数。使用 man 3 basename 获取有关这些函数的更多信息。 使用系统提供的功能的优点是您不必安装 Boost 或编写自己的函数。

#include <libgen.h>
       char *dirname(char *path);
       char *basename(char *path);

来自 man 页面的示例代码:

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

由于basename()函数的非const参数类型,在C++代码中使用它有点不太直观。以下是我代码库中的一个简单示例:
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;
}

使用new/delete不是好的编程风格。我本可以将其放入try/catch块中,以防在两个调用之间发生了什么。


-1
Nickolay Merkin和Yuchen Zhong的回答很好,但是从评论中可以看出它并不完全准确。
在打印时隐式转换为std::string将会用引号包裹文件名。评论也不准确。 path::filename()和path::stem()返回一个新的路径对象,而path::string()返回一个字符串的引用。因此,像std::cout << file_path.filename().string() << "\n"这样的代码可能会导致悬空引用的问题,因为引用所指向的字符串可能已经被销毁。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接