我使用C ++ fstream来读取配置文件。
#include <fstream>
std::ifstream my_file(my_filename);
现在,如果我传递目录的路径,它会默默地忽略它。例如。即使my_file.good()
是目录,my_filename
也会返回true。由于这是我的程序的意外输入,我喜欢检查它,并抛出异常。
如何检查刚打开的fstream是常规文件,目录还是流?
我似乎无法找到方法:
在some forum discussion中,有人认为这两者都不可能,因为这是依赖于操作系统的,因此永远不会成为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);
}
我更喜欢fstream。因此,我的问题。
答案 0 :(得分:3)
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();
};
}
答案 1 :(得分:1)
解决此问题有不同的方法:
stat()
或直接使用Boost.Filesystem或类似的库。我不能100%确定是否在C ++ 11中添加了类似内容。请注意,这会创建竞争条件,因为在您检查之后但在打开之前,某些攻击者可以使用目录切换文件。fstream
检索低级句柄,在您的情况下可能是FILE*
。还有一些方法可以从iostream
创建fstream
(不一定是FILE*
!)。这些都是特定于实现的扩展,因此您需要一些#ifdef
魔法来定制特定于已使用的stdlibrary实现的代码。我敢于依赖他们的存在,即使不是你仍然可以创建一个streambuf
的{{1}}如果你需要移植到一个不容易提供更简单方法的模糊系统答案 2 :(得分:1)
由于IO操作的操作系统依赖性,认为这很复杂。
我在OS X 10.10.2,Linux 2.6.32和FreeBSD 8.2-RELEASE上尝试了一些技术(后两个是稍微旧的操作系统,我使用了一些较旧的VirtualBox虚拟机)。
stat()
,或使用open()
使用旧版C fstat()
。seekg(0, std::ios::beg);
方法对我不起作用。my_file.exceptions(std::ios::failbit | std::ios::badbit);
my_file.eof()
也可能因更严重的错误而设置,因此对EOF条件的检查很差。errno
是一个更好的指标:如果引发异常,但是errno仍为0,则很可能是EOF条件。在我测试过的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 errono 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;
}
}
}
答案 3 :(得分:1)
从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.");
}
...
找到有关cppreference的更多详细信息。
(警告::某些编译器说它们具有c ++ 17,但文件系统可能仍在实验中;即GNU C ++仅从g ++ v8.0起才具有完整版本,有关详细信息,请参阅此问题:Link errors using <filesystem> members in C++17)