我正在使用C++流来读取配置文件。
#include <fstream>
std::ifstream my_file(my_filename);
现在,如果我传递目录的路径,它会默默地忽略这一点。 例如 my_file.good()
返回 true,即使my_filename
是一个目录。由于这是我的程序的意外输入,我喜欢检查它,并抛出异常。
如何检查刚刚打开的 fstream 是常规文件、目录还是流?
我似乎也找不到一种方法:
- 从给定的 ifstream 获取文件描述符。
- 使用其他机制在 IF流中查找此信息。
在一些论坛讨论中,有人建议两者都不可能,因为这依赖于操作系统,因此永远不可能成为 fstream C++标准的一部分。
我能想到的唯一选择是重写我的代码以完全摆脱ifstream,并诉诸文件描述符(*fp
(的C方法以及fstat()
:
#include <stdio.h>
#include <sys/stat.h>
FILE *fp = fopen(my_filename.c_str(), "r");
// skip code to check if fp is not NULL, and if fstat() returns != -1
struct stat fileInfo;
fstat(fileno(fp), &fileInfo);
if (!S_ISREG(fileInfo.st_mode)) {
fclose(fp);
throw std::invalid_argument(std::string("Not a regular file ") + my_filename);
}
我更喜欢流。因此,我的问题。
void assertGoodFile(const char* fileName) {
ifstream fileOrDir(fileName);
//This will set the fail bit if fileName is a directory (or do nothing if it is already set
fileOrDir.seekg(0, ios::end);
if( !fileOrDir.good()) {
throw BadFile();
};
}
从 C++17 开始,我们有了可用的filesystem
(基于 boost::filesystem
(。这将比C++中的其他任何东西都更便携(尽管我认为stat()
也运行良好(。
您有两个可用的函数,如果需要,其中一个返回错误代码。
bool is_regular_file( const path& p );
bool is_regular_file( const path& p, error_code& ec );
您可以将这些用于以下内容:
#include <filesystem>
...
if(!std::filesystem::is_regular_file(path))
{
throw std::runtime_error(path + " was expected to be a regular file.");
}
...
查找有关 cpp 首选项的更多详细信息。
(警告:一些编译器说他们有 C++17,但文件系统可能仍然是实验性的;即 GNU C++ 只有从 G++ v8.0 开始才有完整版本,有关详细信息,请参阅这个问题:在 C++17( 中使用
有不同的方法可以解决此问题:
- 忽略它。说真的,如果目录内容作为有效配置传递,我会感到惊讶。否则,解析无论如何都会失败,因此您没有导入错误数据的风险。此外,您不会阻止用户提供管道或不是文件的类似内容。
- 在打开路径之前检查路径。您可以使用
stat()
或直接使用 Boost.File system 或一些类似的库。我不是 100% 确定 C++11 中是否添加了类似内容。请注意,这会产生争用条件,因为在检查之后但在打开之前,某些攻击者可能会使用目录切换文件。 - 通常,有一些方法可以从
fstream
中检索低级句柄,在您的情况下可能是FILE*
。还有一些方法可以从FILE*
创建iostream
(不一定是fstream
!这些始终是特定于实现的扩展,因此您需要一些#ifdef
魔法来定制特定于所用 stdlibrary 实现的代码。我敢于依靠它们的存在,即使不是,如果您需要移植到一些不提供更简单方法的晦涩系统,您仍然可以在 tof 的FILE*
上创建一个streambuf
。
由于 IO 操作的操作系统依赖性,认为很复杂。
我在OS X 10.10.2,Linux 2.6.32和FreeBSD 8.2-RELEASE上尝试了一些技术(后两个是稍旧的操作系统,我使用了一些较旧的VirtualBox VM(。
- 我还没有找到万无一失的方法。如果真的想检查,请在路径上使用
stat()
,或者使用带有fstat()
的老式 Copen()
。 - PSkocik 建议的
seekg(0, std::ios::beg);
方法对我不起作用。 - 对于 fstreams,最好的方法是打开并从文件中读取,然后等待错误。
- 必须同时设置
badbit
和failbit
,尤其是在OS X上,才能引发所有必需的异常。 例如my_file.exceptions(std::ios::failbit | std::ios::badbit);
- 这还会导致在读取常规文件后出现文件结束 (EOF( 异常,这需要代码忽略这些正常异常。
-
my_file.eof()
也可能设置在更严重的错误上,因此它是对EOF
条件的不良检查。 -
errno
是一个更好的指标:如果引发异常,但errno
仍为 0,则很可能是EOF
条件。 - 这并不总是正确的。在 FreeBSD 8.2 上, 打开目录路径只会返回二进制 gobbledygook, 而没有引发任何异常。
这是我测试过的 3 个平台上似乎处理它的实现。
#include < iostream>
#include < fstream>
#include < cerrno>
#include < cstring>
int main(int argc, char *argv[]) {
for (int i = 1; i < argc; i++) {
std::ifstream my_file;
try {
// Ensure that my_file throws an exception if the fail or bad bit is set.
my_file.exceptions(std::ios::failbit | std::ios::badbit);
std::cout << "Read file '" << argv[i] << "'" << std::endl;
my_file.open(argv[i]);
my_file.seekg(0, std::ios::end);
} catch (std::ios_base::failure& err) {
std::cerr << " Exception during open(): " << argv[i] << ": " << strerror(errno) << std::endl;
continue;
}
try {
errno = 0; // reset errno before I/O operation.
std::string line;
while (std::getline(my_file, line))
{
std::cout << " read line" << std::endl;
// ...
}
} catch (std::ios_base::failure& err) {
if (errno == 0) {
std::cerr << " Exception during read(), but errno is 0. No real error." << std::endl;
continue; // exception is likely raised due to EOF, no real error.
}
std::cerr << " Exception during read(): " << argv[i] << ": " << strerror(errno) << std::endl;
}
}
}
我遇到了同样的问题,我用 fstream 替换了 ifstream,它有效
ifStream 仅用于读取文件fstream 用于读写
fstream 可能会在打开目录时尝试写入目录,这会导致您可以在 if 语句中使用 file.good(( 捕获的错误:
我正在学习C++并遇到了同样的问题,所以这里有一些对我有用的东西:
std::ifstream input_file(av[1]);
if (!input_file)
throw (OpenInfileError());
if (input_file.is_open() && input_file.peek() == std::ifstream::traits_type::eof())
throw (EmptyInfileError());
第一个if
语句检查input_file
是否存在.
第二个检查input_file
是否为空。如果它是一个文件夹,它也会抛出错误。