- 目录、文件处理是脚本语言如shell、python所擅长的领域,C++语言缺乏对操作系统文件的查询和操作能力,因此C++程序员经常需要再掌握另外一门脚本语言以方便自己的工作,这增加了学习成本
- filesystem是一个可移植的文件系统操作符,已被收入C++17版本。它在底层做了大量的工作,使用了POSIX标准表示文件系统的路径,接口很类似标准库的容器和迭代器。使C++具有了类似脚本语言的功能,可以跨平台操作目录、文件,写出通用的脚本程序
- filesystem库需要system库支持(
-lboost_system -lboost_filesystem
),位于名字空间boost::filesystem,需要包含头文件<boost/filesystem.hpp>
类摘要
filesystem库的核心类是path,它屏蔽了不同文件系统的差异,使用可移植的POSIX语法提供了通用的目录、路径表示,并且支持POSIX的符号链接概率
路径表示
- path的构造函数接受C字符串和string,也可以是一个指定首末迭代器字符串序列区间,路径的分隔符由类内部的静态常量
preferred_separator
定义,UNIX是斜杠/
,Windows是\
- path使用标准的POSIX语法提供可移植的路径表示,使用
/
来分割文件名和目录名,.
表示当前目录,..
表示上一层目录。比如:
path p1("./a_dir");
path p2("/usr/local/lib");
- path也支持操作系统原生路径表示,在windows下使用盘符,分隔符使用
\
path p3("D:\\workspace\\cpp");
path p4("D:/workspace/cpp");
- 因为
\
被C++定义为转义符,在字符串中使用\
表示路径的时候必须连续写\\
才能被识别成一个\
(或者使用C++的原始字符串R(..)
的形式),因此这种方式通常设有UNIX风格的斜杠方式方便 - 空构造函数创建一个空路径对象,不表示任何路径,成员函数
empty()
可以判断路径是否为空:
path p5; //默认构造为空路径
assert(p5.empty());
- path的构造函数没有被声明为explicit,因此字符串可以被隐式转换为path对象,这在编写操作文件系统的代码时非常方便,可以不用创建一个临时的path对象
- path重载了
operator/=
,可以像使用普通路径一样用/
来追加路径,成员函数append()也可以追加一个字符串序列:
char str[] = "the path is (/root)";
path p(str + 13, str + 14); //区间方式,取字符串中的'/'
assert(!p.empty()); //路径非空
p /= "ect"; //使用operator/=追加路径
string filename = "xinetd.conf";
p.append(filename.begin(), filename.end()); //追加字符序列
cout << p << endl; // p = /etc/xinetd.conf
- operator+=和concat()的作用与operator/=类似,但它仅连接字符串,不会添加路径分隔符:
path p(str + 13, str + 14); //区间方式,取字符串中的"/r"
p += "etc"; // p = /rect
string filename = "xinetd.conf";
p.concat(filename.begin(), filename.end()); //追加字符序列
cout << p << endl; // p = /retcxinetd.conf
- 自由函数system_complete()可以返回路径在当前文件系统中的完整路径(绝对路径):
cout << system_complete(p) << endl; // /etc/xinetd.conf
- 注意:path仅仅用于表示路径,不关心路径中文件或者目录是否存在,路径有可能是个完全无效的名字,比如在windows下不允许文件名或目录名使用
::、<>、?、*
等等,但是path并不禁止这种表示:
path p("/::/*/?/<>"); //完全合法
可移植的文件名
为了提供程序在不同文件系统上的可移植性,filesystem库提供了一系列的文件名(或目录名)检查函数,可以根据系统的命名规则判断一个文件名字符串的有效性,从而尽可能的为程序员编写可移植的程序创建便利
- POSIX规范只有一个很小的字符集用于文件名,包括大小写字母、点号、下划线、连字符;而Windows则访问要广一些,仅不允许
<>?:/\
等少量字符。比如:
string fname("w+abc.xxx");
assert(!portable_posix_name(fname)); //posix非法文件名
assert(windows_name(fname)); //windows合法文件名
- 自由函数
portable_posix_name()
和windows_name()
分别检测文件名字符串是否符号POSIX规范和Windows规范,保证名字可以移植到符号POSIX的UNIX操作系统和Windows操作系统上 portable_name()
相当于portable_posix_name()&&windows_name()
,用来判断名字是否是一个可移植文件名,但名字不能以.
或者连字符开头,并允许表示当前目录的.
和父目录的..
。它保证文件名可以移植到所有现代操作系统和一些旧有的操作系统上。portable_directory_name()
的判断规则更严格,它包含了portable_name()
,并且要求名字中不能出现.
,目录名可以移植到OpenVMS操作系统上portable_file_name()
类似portable_directory_name()
,提供可移植的文件名,它要求文件名中最多有一个.
,而且后缀名不能超过3个字符native()
判断文件名是否符号本地文件系统命名规则,在windows下等同于windows_name()
,而在其他操作系统下只是判断文件名不是空格而且不能包含斜杠
路径处理
path类提供了丰富的函数用于处理路径,可以获取文件名、目录名、判断文件属性等
- 成员函数
string()
、native()
以字符串的形式返回标准格式的路径表示,而parent_path()、filename()、stem()、extension()分别返回路径中的父路径、全文件名、不含扩展名的文件名和扩展名的path对象:
path p("/usr/local/include/xxx.hpp");
cout << p.string() << "\n"; // output: /usr/local/include/xxx.hpp
cout << p.parent_path() << "\n"; // output: "/usr/local/include"
cout << p.stem() << "\n"; // output: "xxx"
cout << p.filename() << "\n"; // output: "xxx.hpp"
cout << p.extension() << "\n"; // output: ".hpp"
- 成员函数is_absolute()用于检测path是否是一个绝对路径,这需要根据具体的文件系统的表示,比如下面代码的第一个断言在Unix是成立的,但是Windows下则不成立,因为Windows系统的完整路径需要包括盘符:
assert(p.is_absolute()); //windows断言不成立
assert(system_complete(p).is_absolute()); //总是完整路径
- root_name()、root_directory()、root_path()这三个成员函数用于处理根目录,如果path中含有根,那么它们返回根的名字,根目录,根路径。(windows下的path(“c::/xxx/yyy”),分别返回”c:“,”/“,”c:/“)
cout << p.root_name() << "\n"; // output: "" --- 因为linux下的根目录没有名字
cout << p.root_directory() << "\n"; // output: "/"
cout << p.root_path() << "\n"; // output: "/"
- 成员函数relative_path()返回path的相对路径,相当于去掉了root_path()。
- 根路径和相对路径的这四个函数又有对应的has_xxx()的形式,用来判断是否存在对应的路径。同样,has_filename()和has_parent_path()用于判断路径是否有文件名或者父路径:
assert(!p.has_root_name());
assert(p.has_root_path());
assert(p.has_parent_path());
- 之前讨论的成员函数都不会改变path的值,接下来的两个函数能够原地修改path:
- remove_filename()函数可以删除路径中最后的文件名,把path变为纯路径表示
- replace_extension()可以变更文件扩展名
cout << p.remove_filename() << "\n"; // output: /usr/local/include
p /= "xxx.hpp";
cout << p.replace_extension("hxx") << "\n"; // /usr/local/include/xxx.hxx
cout << p.replace_extension() << "\n"; // /usr/local/include/xxx
- 也可以对两个path对象执行比较操作,它使用成员函数compare()基于字典序并且大小写敏感比较路径字符串,提供operator=、operator!=、operator<等操作符:
path p1("/test/1.cpp");
path p2("/TEST/1.cpp");
path p3("/abc/1.cpp");
assert(p1 != p2);
assert(p2 < p3);
异常处理
filesystem库使用异常filesystem_error来处理文件操作时发生的错误,它是system库中system_error的子类。