boost库 文件系统操作库 filesystem
《Boost程序库完全开发指南》整理
使用该库需要编译
b2 install --build-type=complete --with-filesystem
头文件及命名空间
// filesystem #include <boost/filesystem.hpp> using namespace boost::filesystem;
一、filesystem
1.类摘要
class path // 路径表示类
{
public:
typedef char_or_wchar_t value_type; // 路径的字符类型
typedef std::basic_string<value_type> string_type; // 路径使用的字符串类型
constexpr value_type preferred_separator; // 路径分隔符
path(); // 各种构造函数
path(const path& p);
path(Source const& source);
path(InputIterator begin, InputIterator end);
~path(); // 析构函数
path& operator=(const path& p); // 赋值操作
path& operator=(Source const& source);
path& assign(Source const& source)
path& assign(InputIterator begin, InputIterator end);
path& operator/=(const path& p); // 重载/=
path& operator/=(Source const& source); // 追加路径
path& append(Source const& source);
path& append(InputIterator begin, InputIterator end);
path& operator+=(const path& x); // 重载+=
path& operator+=(const string_type& x); // 连接路径
path& operator+=(Source const& x);
path& concat(InputIterator begin, InputIterator end);
void clear(); // 清空路径表示
path& remove_filename(); // 删除文件名
path& replace_extension(); // 更改扩展名
void swap(path& rhs); // 交换操作
const string_type& native() const; // 本地路径表示
const value_type* c_str() const; // 转换为C字符串
const string string() const; // 转换为字符串
const wstring wstring() const; // 转换为宽字符串
int compare(const path& p) const; // 比较路径
int compare(const std::string& s) const;
int compare(const value_type* s) const;
path root_name() const; // 根名称
path root_directory() const; // 根目录
path root_path() const; // 根路径
path relative_path() const; // 相对路径
path parent_path() const; // 父路径
path filename() const; // 文件名
path stem() const; // 全路径名
path extension() const; // 扩展名
bool empty() const; // 是否空路径
bool has_root_name() const; // 是否有根名称
bool has_root_directory() const; // 是否有根目录
bool has_root_path() const; // 是否有根路径
bool has_relative_path() const; // 是否有相对路径
bool has_parent_path() const; // 是否有父路径
bool has_filename() const; // 是否有文件名
bool has_stem() const; // 是否有全路径名
bool has_extension() const; // 是否有扩展名
bool is_absolute() const; // 是否是绝对路径
bool is_relative() const; // 是否是相对路径
iterator begin() const; // 迭代路径
iterator end() const;
};
ostream& operator<<( ostream& os, const path& p ); // 流输出操作
path operator/ (const path& lhs, const path& rhs); // 连接两个路径
bool operator==(const path& lhs, const path& rhs); // 比较操作符
... // 其他比较操作符重载
2. 构造函数
// path的构造函数可以接受C字符串和string,也可以是一个指定首末迭代器字符串序列区间
// 路径的分隔符由类内部的静态常量preferred_separator定义,UNIX是斜杠(/),Windows是反斜杠(\)
// 使用标准的POSIX语法提供可移植的路径表示,使用斜杠(/)来分隔文件名和目录名,点号(.)表示当前目录,双点号(..)表示上一级目录
path p0; // 默认构造为空路径
path p1("./a_dir"); // linux
path p2("/usr/local/lib");
path p3("c:\\tmp\\test.text"); // windows
path p4("d:/boost/boost/filesystem/");
char str[] = "the path is (/root)."; // 指定首末迭代器字符串序列区间
path p5(str+13, str+14);
// path的构造函数没有被声明为explicit, 因此字符串可以被隐式转换为path对象
// 这在编写操作文件系统的代码时非常方便,可以不用创建一个临时的path对象
void test(path p){
....
}
test("/usr/local/lib");
3.拼接路径及查看全路径
// path重载了operator/=,可以像使用普通路径一样用“/”来追加路径,成员函数append()也可以追加一个字符串序列。
path p("/root"); // 区间方式,取字符串中的"/"
// 方式1
p /= "etc"; // 使用operator/=追加路径
// 方式2
string filename = "xinetd.conf";
p.append(filename.begin(), filename.end()); // 追加字符序列
cout << p << endl;
// -----------------------------------------------------------------------------------------
// operator+=和concat()的作用与operator/=类似,但它仅连接字符串,不会添加路径分隔符
path p("/r");
p += "etc"; // p = /retc
string filename = "xinetd.conf";
p.concat(filename.begin(), filename.end()); // 追加字符序列
cout << p << endl; // p = /retcxinetd.conf
// -----------------------------------------------------------------------------------------
// 自由函数system_complete()可以返回路径在当前文件系统中的完整路径(也就是通常所说的绝对路径)
cout << system_complete(p) << endl; // 在Linux 下的输出是:/etc/xinetd.conf
4. 路径合法性
// path 仅仅是用于表示路径,而并不关心路径中的文件或目录是否存在,路径也可能是个在当前文件系统中无效的名字,例如在Windows 下不允许文件名或目录名使用冒号、尖括号、星号、问号,等等,但path 并不禁止这种表示
path p("/::/*/?/<>");
// 完全合法
// -----------------------------------------------------------------------------------------
// 为了提高程序在不同文件系统上的可移植性,filesystem库提供了一系列的文件名(或目录名)检查函数,可以根据系统的命名规则判断一个文件名字符串的有效性,从而尽可能地为程序员编写可移植的程序创造便利。
// portable_posix_name() 检测文件名字符串是否符合POSIX规范(包括大小写字母、点号、下画线和连字符)
// windows_name() 检测文件名字符串是否符合Windows规范(范围要广一些,仅不允许<>?:|/\等少量字符)
// portable_name() 相当于 portable_posix_name()&&windows_name()
// 但名字不能以点号或者连字符开头,并允许表示当前目录的"."和父目录的".."
// portable_directory_name 包含了portable_name(),名字中不能出现点号,目录名可以移植到OpenVMS操作系统上
// portable_file_name 类似portable_directory_name()
// 提供更可移植的文件名,它要求文件名中最多有一个点号,且后缀名不能超过3个字符.
cout << "protable_posix_name, posix:" << portable_posix_name("w+abc.xxx")
<< "; windows" << windows_name("w+abc.xxx") << endl;
cout << "portable_name:" << portable_name("w+()abc.txt") << "; "
<< portable_name("./abc") << endl;
cout << "portable_directory_name:" << portable_directory_name("a.txt") << "; "
<< portable_directory_name("abc") << endl;
cout << "portable_file_name:" << portable_directory_name("a.bc") << "; "
<< portable_file_name("y.conf") << endl;
/*
protable_posix_name, posix:0; windows1
portable_name:0; 0
portable_directory_name:0; 1
portable_file_name:0; 0
*/
5. 路径处理及迭代器
path p("/usr/local/include/xxx.hpp");
// 以字符串的形式返回标准格式的路径
cout << p.string() << endl; // /usr/local/include/xxx.hpp
// 父路径、全文件名、不含扩展名的文件名和扩展名
cout << p.parent_path() << endl; // /usr/local/include/ (返回的是path类型)
cout << p.stem() << endl; // xxx
cout << p.filename() << endl; // xxx.hpp
cout << p.extension() << endl; // .hpp
// 判断是否是绝对路径
assert(p.is_absolute()); // Windows 断言不成立
assert(system_complete(p).is_absolute()); // 总是完整路径
// 根的名字、根目录和根路径 若Windows下 path p("c:/xxx/yyy")
cout << p.root_name() << endl; // linux 空 windows c:
cout << p.root_directory() << endl; // linux / windows /
cout << p.root_path() << endl; // linux / windows c:/
// 判断路径是否有文件名或者父路径
assert(!p.has_root_name());
assert( p.has_root_path());
assert( p.has_parent_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);
// 迭代器
path p = "/boost/tools/libs";
BOOST_FOREACH(auto& x, p) { // 使用foreach 算法,也可以用for
cout << "["<< x << "]"; // 输出路径字符串
}
/*
[/][boost][tools][libs]
*/
二、异常处理 filesystem_error类
1.类摘要
// filesystem库使用异常filesystem_error来处理文件操作时发生的错误
// 它是system库中system_error的子类。
class filesystem_error : public system_error
{
public:
filesystem_error(); // 各种构造函数
filesystem_error(const filesystem_error&);
filesystem_error(const std::string& what_arg, system::error_code ec);
filesystem_error(const std::string& what_arg,
const path& p1, system::error_code ec);
filesystem_error(const std::string& what_arg, const path& p1,
const path& p2, system::error_code ec);
const path& path1() const; // 获取路径对象
const path& path2() const;
const char* what() const; // 错误信息
};
2.例子
// 代码检查文件的大小,但文件不存在,将抛出异常
path p("/test.txt"); // 一个不存在的文件
try{
file_size(p); // 检查文件的大小
}
catch(filesystem_error& e) { // 捕获异常
cout << e.path1() << endl;
cout << e.what() << endl;
}
三、文件状态 file_status类
1.类摘要
// 检查文件的属性(如是否存在、是否是目录、是否是符号链接等)和访问权限(用户读写、组读写等)
class file_status
{
public:
file_status(); // 构造函数
explicit file_status(file_type ft, perms prms = perms_not_known);
file_type type() const; // 文件类型
void type( file_type v ); // 设置文件类型
perms permissions() const; // 访问权限
void permissions(perms prms); // 设置访问权限
};
2.枚举及示例代码
file_status的成员函数type()和permissions()可以获得文件的状态信息,它们都是枚举类型。
文件的类型file_type取值有:
-
status_error 获取文件类型出错
-
file_not_found 文件不存在
-
status_unknown 文件存在但状态未知
-
regular_file 是一个普通文件
-
directory_file 是一个目录
-
symlink_file 是一个链接文件
-
block_file 是一个块设备文件
-
character_file 是一个字符设备文件
-
fifo_file 是一个管道设备文件
-
socket_file 是一个 socket 设备文件
-
type_unknown 文件的类型未知
访问权限perms枚举模仿了POSIX的文件权限:(可以用位运算)
- owner_read 对应S_IRUSR(0400)
- owner_write 对应S_IWUSR(0200)
通常我们不会直接使用file_status类(就像是std::typeinfo),而是用相关函数操作file_status对象:
- status()/symlink_status() 测试路径p 的状态,如果路径p 不能被解析则会抛出异常 filesystem_error
- status_known() 检查文件状态s,返回 s.type != status_error。
assert(status("/dev/null").type() == character_file);
assert(status("/bin").type() == directory_file);
assert(status("/bin/sh").type() == regular_file);
// 取访问权限,使用位操作验证权限值
assert((status("/bin/sh").permissions() & owner_exe) == owner_exe);
四、文件属性
受可移植的限制,很多文件属性不是各平台共通的,因此filesystem库仅提供了少量的文件属性操作:
-
函数initial_path()返回程序启动时(进入main()函数)的路径;
-
函数current_path()返回当前路径。它和initial_path()返回的都是一个完整路径(绝对路径);
-
函数file_size()以字节为单位返回文件的大小;
-
函数last_write_time()返回文件的最后修改时间,是一个std::time_t。
last_write_time()还可以额外接受一个time_t参数,修改文件的最后修改时间,就像是使用UNIX的touch命令。
这些函数都要求操作的文件必须存在,否则会抛出异常,file_size()还要求文件必须是个普通文件(is_regular_file(name) == true)。
示例代码
cout << initial_path() << endl; // 输出初始路径
cout << current_path() << endl; // 输出当前路径
path p("./test.txt"); // 访问一个文件
cout << file_size(p) << endl;
time_t t = last_write_time(p); // 获取修改时间
last_write_time(p, time(0)); // 更新修改时间
// 函数space()可以返回一个space_info结构,它表明了该路径下的磁盘空间分配情况,space_info结构的定义如下:
struct space_info {
uintmax_t capacity;
uintmax_t free;
uintmax_t available;
};
// 使用
space_info si = space("/home/chrono");
cout << si.capacity / giga::num<< endl; // 使用ratio库的giga单位
cout << si.available / giga::num<< endl;
cout << si.free / giga::num<< endl;
五、文件操作
filesystem库基于path的路径表示提供了基本的文件操作函数,如创建目录(create_directory)、文件改名(rename)、文件删除(remove)、文件拷贝(copy_file)、创建符号链接(create_symlink),等等,这些函数的名字都很容易理解。
namespace fs = boost::filesystem; // 名字空间别名
path ptest = "./test";
if (exists(ptest)) { // 检查路径是否存在
if (fs::is_empty(ptest)) { // 注意名字空间限定
remove(ptest); // remove只能删除空目录或文件
}
else {
remove_all(ptest); // remove_all 可以递归删除
}
}
assert(!exists(ptest)); // 该目录已经被删除
create_directory(ptest); // 创建一个目录
copy_file("/usr/local/include/boost/version.hpp", ptest / "a.txt");
assert(exists(ptest / "a.txt"));
rename(ptest / "a.txt", ptest / "b.txt"); // 改名
assert(exists(ptest / "b.txt"));
// 使用create_directories 可以一次创建多级目录
create_directories(ptest / "sub_dir1" / "sub_dir2");
// 注意
// cpp标准的type_traits库中有一个同名的元函数is_empty,所以我们在这段代码中为is_empty()函数加上了名字空间限定,避免名字冲突。
六、迭代目录
filesystem库提供了迭代一个目录下的所有文件的功能的类:
- directory_iterator,基于boost.iterator库的iterator_facade。
- recursive_directory_iterator,递归遍历文件系统目录。
1. directory_iterator
1.1 类摘要
// directory_iterator只能迭代本层目录,不支持深度遍历目录
class directory_iterator : public boost::iterator_facade<
directory_iterator,
directory_entry, // 解引用返回类型
boost::single_pass_traversal_tag >
{
public:
directory_iterator(){}
directory_iterator(const directory_iterator&);
explicit directory_iterator(const path& p);
~directory_iterator();
directory_iterator& operator=(const directory_iterator&);
directory_iterator& operator++();
};
// 注意:directory_iterator迭代器返回的对象并不是path,而是一个directory_entry对象
class directory_entry
{
public:
const path& path() const; // 返回path 对象
file_status status() const; // 获取文件状态
file_status symlink_status() const;
};
1.2 示例
// directory_iterator,空的构造函数生成一个逾尾end迭代器,传入path对象构造将开始一个迭代操作,
// 反复调用operator++即可遍历目录下的所有文件。
directory_iterator end;
for (directory_iterator pos("/usr/local/lib/");pos != end; ++pos) {
cout << *pos << endl;
}
// 定义一个std::pair区间,使用foreach算法简化迭代
// 定义一个迭代器的区间
typedef std::pair<directory_iterator, directory_iterator> dir_range;
dir_range dr(directory_iterator("/usr/local/lib/"), // 迭代起点
directory_iterator()); // 迭代终点
BOOST_FOREACH(auto& x, dr) { // 迭代区间
cout << x << endl;
}
1.3 directory_iterator深度遍历目录(递归)
void recursive_dir(const path& dir) // 递归遍历目录
{
directory_iterator end; // 结束迭代器调用
for (directory_iterator pos(dir);pos != end; ++pos) {
if (is_directory(*pos)) {
recursive_dir(*pos); // 是目录则递归遍历
} else {
cout << *pos << endl; // 不是目录则输出路径
}
}
}
2. recursive_directory_iterator
2.1 类摘要
class recursive_directory_iterator // 省略构造、析构函数以及迭代器通用操作
{
public:
int level() const; // 目录深度
void pop(); // 退出当前目录的遍历
void no_push(); // 不遍历本目录
private:
int m_level; // 目录深度成员变量
};
/*
成员函数level()返回当前的目录深度m_level,当rd_iterator构造时(未开始遍历)m_level==0,每深入一层子目录则m_level增加,退出时减少。
成员函数pop()用于退出当前目录层次的遍历,同时--m_level。
当迭代到一个目录时,no_push()可以让目录不参与遍历,使rd_iterator的行为等价于directory_iterator。
*/
2.2 示例代码
typedef recursive_directory_iterator rd_iterator;
rd_iterator end;
for (rd_iterator pos("/usr/local/lib/");pos != end; ++pos) {
cout << "level" << pos.level() << ":" <<*pos << endl;
}
// 下面的代码使用no_push()令rd_iterator的行为等价于directory_iterator:
rd_iterator end;
for (rd_iterator pos("/usr/local/lib/");pos != end; ++pos) {
if (is_directory(*pos)) {
pos.no_push(); // 使用no_push(),不深度遍历
}
cout <<*pos << endl;
}
七、示例 查找文件、模糊查找文件、复制文件
1.查找文件
#include <boost/filesystem.hpp>
#include <boost/optional.hpp>
#include <iostream>
#include <list>
using namespace std;
using namespace boost;
using namespace boost::filesystem;
typedef recursive_directory_iterator rd_iterator;
optional<list<path>> find_file(path dir, string filename)
{
// 返回值类型定义
typedef optional<list<path>> result_type;
// 检查目录的有效性
if (!exists(dir) || !is_directory(dir)) {
return result_type();
}
// 递归迭代器
rd_iterator end;
list<path> lstPath = list<path>();
for (rd_iterator pos(dir); pos != end; ++pos) {
// 不是目录、文件名相等
if ((!is_directory(*pos)) && (pos->path().filename() == filename)) {
lstPath.push_back(pos->path());
}
}
return result_type(lstPath);
}
int main()
{
// 如果是Qt环境,路径有中文的时候会出现编码问题
path searchPath(""); // 查找的路径
string findFileName = ""; // 文件名
optional<list<path>> r = find_file(searchPath, findFileName);
list<path> lstPath = r.value();
// 不存在提示并return
if (lstPath.size() == 0) {
cout << "file not found." << endl;
return -1;
}
for (path p : lstPath) {
cout << p << endl;
}
return 0;
}
2.模糊文件查找
使用正则表达式处理字符串的强大功能,我们可以很容易地实现完全的模糊文件查找。
作为示范,我们只支持通配符*,大小写敏感并且不处理点号以外的正则表达式其他特殊符号(如括号和竖线),读者可以自己扩展增强它的功能(这很容易)。
下面是模糊查找的几个实现要点:
-
文件名中用于分隔主名与扩展名的点号必须转义,因为点号在正则表达式中是一个特殊的字符;
-
通配符应该转换为正则表达式的.,以表示任意多的字符;
-
在判断文件名是否查找到时我们应该使用正则表达式而不是简单的判断相等;
-
函数的返回值不能再使用optional
,因为模糊查找可能会返回多个结果,所以应该使用vector 。
find_files()的实现代码如下:
#include <boost/algorithm/string.hpp>
#include <boost/filesystem.hpp>
#include <boost/xpressive/xpressive_dynamic.hpp>
#include <iostream>
#include <list>
using namespace std;
using namespace boost;
using namespace boost::filesystem;
using namespace boost::xpressive;
typedef recursive_directory_iterator rd_iterator;
vector<path> find_files(const path& dir, const string& filename)
{
static xpressive::sregex_compiler rc; // 正则表达式工厂
if (!rc[filename].regex_id()) {
string str = replace_all_copy(
replace_all_copy(filename, ".", "\\."),
"*", ".*"); // 处理文件名
rc[filename] = rc.compile(str); // 创建正则表达式
}
typedef vector<path> result_type; // 返回值类型定义
result_type v;
if (!exists(dir) || !is_directory(dir)) // 目录检查
{
return v;
}
rd_iterator end; // 递归迭代器逾尾位置
for (rd_iterator pos(dir); pos != end; ++pos) {
if (!is_directory(*pos) && regex_match(pos->path().filename().string(), rc[filename])) {
v.push_back(pos->path()); // 找到,加入vector
}
}
return v; // 返回查找的结果
}
int main()
{
auto v = find_files("C:/Users/14759/Desktop/test1", "*.lib");
cout << v.size() << endl;
for(path &p : v) {
cout << p << endl;
}
return 0;
}
3.拷贝目录
find_files()是一个功能强大的函数,利用它可以做很多事情,比如为filesystem库再增加一个copy_files()函数,它对应于remove_all()。
copy_files()函数利用find_files()的功能,首先查找源目录里的所有文件(使用通配符*),把它们加入到一个vector
copy_files()还使用了progress_display(2.4节),为用户提供了一个友好的进度显示。
完整的实现代码如下:
// disable pragma warning
#define BOOST_ALLOW_DEPRECATED_HEADERS
#include <boost/algorithm/string.hpp>
#include <boost/filesystem.hpp>
#include <boost/optional.hpp>
#include <boost/progress.hpp>
#include <boost/xpressive/xpressive_dynamic.hpp>
#include <iostream>
#include <list>
using namespace boost::xpressive;
using namespace std;
using namespace boost;
using namespace boost::filesystem;
typedef recursive_directory_iterator rd_iterator;
vector<path> find_files(const path& dir, const string& filename)
{
static xpressive::sregex_compiler rc; // 正则表达式工厂
if (!rc[filename].regex_id()) {
string str = replace_all_copy(
replace_all_copy(filename, ".", "\\."),
"*", ".*"); // 处理文件名
rc[filename] = rc.compile(str); // 创建正则表达式
}
typedef vector<path> result_type; // 返回值类型定义
result_type v;
if (!exists(dir) || !is_directory(dir)) // 目录检查
{
return v;
}
rd_iterator end; // 递归迭代器逾尾位置
for (rd_iterator pos(dir); pos != end; ++pos) {
if (!is_directory(*pos) && regex_match(pos->path().filename().string(), rc[filename])) {
v.push_back(pos->path()); // 找到,加入vector
}
}
return v; // 返回查找的结果
}
size_t copy_files(const path& from_dir, const path& to_dir, const string& filename = "*")
{
if (!is_directory(from_dir)) { // 源必须是个目录
cout << "args is not a dir." << endl;
return 0;
}
cout << "prepare for copy, please wait..." << endl;
auto v = find_files(from_dir, filename); // 查找源的所有文件
if (v.empty()) { // 空目录则不拷贝
cout << "0 file copied." << endl;
return 0;
}
cout << "now begin copy files ..." << endl;
path tmp;
progress_display pd(v.size()); // 进度显示
for (auto& p : v) { // 变量容器
// 拆分基本路径与目标路径
tmp = to_dir / p.string().substr(from_dir.string().length());
if (!exists(tmp.parent_path())) { // 创建子目录
create_directories(tmp.parent_path());
}
copy_file(p, tmp); // 拷贝文件
++pd; // 更新进度
}
cout << v.size() << " file copied." << endl;
return v.size(); // 完成拷贝
}
int main()
{
copy_files("/usr/local/include/boost/timer", "./t");
return 0;
}
copy_files()不仅能够拷贝目录下的所有文件,也可以过滤只符合通配符的文件,这需要向copy_files()传递第三个参数filename,例如要拷贝所有的文本文件可以使用“*.txt”。
不过,这里实现的copy_files()也有小小的缺陷,它不能够拷贝空目录,这是因为find_files()在查找时不考虑目录匹配。
八、文件流操作
filesystem库提供了大量的文件系统操作方法,可以很方便地操作文件或目录,但它使用的是path对象,而cpp标准库中的文件流类ifstream/ofstream/fstream只支持char*或者std::string打开文件,因此使用起来很不方便,我们必须调用path对象的c_str()或string(),像这样:
path p("./INSTALL");
std::ifstream ifs(p.c_str());
在cpp17标准里文件流已经增加了对path的支持,如果编译器支持cpp17标准,那么filesystem库将会和标准库无缝对接,使cpp程序员获得完整的文件处理能力。
不过为了让尚未实现这个特性的编译器也能够享受这个便利,filesystem库在额外的头文件<boost/filesystem/fstream.hpp>中提供了在名字空间boost::filesystem下的同名文件流类,它们可以如标准文件流一样使用,而且支持path对象。
boost::filesystem的文件流的使用方法与标准文件流相同,例如下面的代码使用path对象打开了一个文本文件,并把它打印到标准输出上:
namespace newfs = boost::filesystem;
path p("./INSTALL");
newfs::ifstream ifs(p);
assert(ifs.is_open());
cout << ifs.rdbuf();
这段代码中我们使用了名字空间别名newfs,用来限定使用的是filesystem库的文件流类。
这种做法带来了一个好处,如果将来升级到支持cpp17标准编译器,那么只需要把newfs指向std名字空间,原有的所有代码都不需要变动。