如何在创建文件之前检查文件是否已存在? (C ++,Unicode,跨平台)

时间:2014-01-15 11:06:30

标签: c++ unicode filesystems cross-platform file-exists

我有以下适用于Windows的代码:

bool fileExists(const wstring& src)
{
#ifdef PLATFORM_WINDOWS
        return (_waccess(src.c_str(), 0) == 0);
#else   
        // ???? how to make C access() function to accept the wstring on Unix/Linux/MacOS ?
#endif
}

考虑到scr是一个Unicode字符串并且可能包含带有Unicode字符的文件路径,我如何使代码在* nix平台上以与在Windows上相同的方式工作?

我已经看到了各种StackOverflow的答案,这些答案部分回答了我的问题,但我把它们放在一起有问题。我的系统依赖于宽字符串,尤其是在Windows上,文件名可能包含非ASCII字符。我知道通常最好写入文件并检查错误,但我的情况恰恰相反 - 如果文件已存在,我需要跳过该文件。我只想检查文件是否存在,无论我是否可以读/写。

3 个答案:

答案 0 :(得分:1)

在FAT和NTFS以外的许多文件系统上,文件名不能很好地定义为字符串。它们在技术上是字节序列。这些字节序列的含义是一个解释问题。常见的解释是类似UTF-8。不完全是UTF-8,因为Unicode指定字符串相等而不管编码。大多数系统使用字节相等。 (同样,FAT和NTFS是例外,使用不区分大小写的比较)

答案 1 :(得分:1)

我使用的一个好的便携式解决方案是使用以下内容:

ifstream my_file(myFilenameHere);
if (my_file.good())
{
  // file exists and do what you need to do when it exists
}
else
{
  // the file doesn't exist do what you need to do to create it etc.
}

例如,一个小文件存在检查器功能可能是(这个在windows,linux和unix中工作):

inline bool doesMyFileExist (const std::string& myFilename) 
{
#if defined(__unix__) || defined(__posix__) || defined(__linux__ )
 // all UNIXes, POSIX (including OS X I think (cant remember been a while)) and 
 // all the various flavours of Linus Torvalds digital offspring:)

    struct stat buffer;   
    return (stat (myFilename.c_str(), &buffer) == 0);

#elif defined(__APPLE__)|| defined(_WIN32)  
// this includes IOS AND OSX and Windows (x64 and x86)
// note the underscore in the windows define, without it can cause problems
    if (FILE *file = fopen(myFilename.c_str(), "r")) 
    {
        fclose(file);
        return true;
    }
    else
    {
        return false;
    }
#else  // a catch-all fallback, this is the slowest method, but works on them all:)
    ifstream myFile(myFilename.c_str());
    if (myFile.good()) 
    {
      myFile.close();
      return true;
    } 
    else 
    {
      myFile.close();
      return false;
    }   
#endif
}

上面的函数使用最快的方法来检查每个操作系统变体的文件,并且如果您使用的是除了明确列出的操作系统之外的其他操作系统(例如原始的Amiga OS),则会有后备。这已在GCC4.8.x和VS 2010/2012中使用。

good方法将检查所有内容是否正常,这样您实际上打开了文件。

唯一需要注意的是如何在操作系统中表示文件名(如另一个答案中所述)。

到目前为止,这对我来说已经过了很好的交叉平台:)

答案 2 :(得分:0)

我花了几个小时在我的Ubuntu机器上进行实验。它花了很多试验和错误,但最后我得到了它的工作。我不确定它是否适用于MacOS甚至其他* nixes。

许多人怀疑,直接投射到char*不起作用 - 然后我只得到了我的测试路径/home/progmars/абвгдāēī的第一个斜线。诀窍是使用wcstombs()setlocale()结合使用虽然在转换后我无法在控制台中显示文本,但access()函数仍然正确。

以下代码对我有用:

bool fileExists(const wstring& src)
{
#ifdef PLATFORM_WINDOWS

    return (_waccess(src.c_str(), 0) == 0);

#else   
    // hopefully this will work on most *nixes...

    size_t outSize = src.size() * sizeof(wchar_t) + 1;// max possible bytes plus \0 char
    char* conv = new char[outSize]; 
    memset(conv, 0, outSize);

    // MacOS claims to have wcstombs_l which has locale argument, 
    // but I could not find something similar on Ubuntu
    // thus I had to use setlocale();
    char* oldLocale = setlocale(LC_ALL, NULL);
    setlocale(LC_ALL, "en_US.UTF-8"); // let's hope, most machines will have "en_US.UTF-8" available
    // "Works on my machine", that is, Ubuntu 12.04

    size_t wcsSize = wcstombs(conv, src.c_str(), outSize);
    // we might get an error code (size_t-1) in wcsSize, ignoring for now

    // now be good, restore the locale
    setlocale(LC_ALL, oldLocale);

    return (access(conv, 0) == 0);

#endif
}

这里有一些实验性代码让我找到了解决方案:

// this is crucial to output correct unicode characters in console and for wcstombs to work!
// empty string also works instead of en_US.UTF-8
// setlocale(LC_ALL, "en_US.UTF-8");

wstring unicoded = wstring(L"/home/progmars/абвгдāēī");
int outSize = unicoded.size() * sizeof(wchar_t) + 1;// max possible bytes plus \0 char
char* conv = new char[outSize]; 
memset(conv, 0, outSize);
size_t szt = wcstombs(conv, unicoded.c_str(), outSize); // this needs setlocale - only then it returns 31. else it returns some big number - most likely, an error message
wcout << "wcstombs result " << szt << endl;

int resDirect = access("/home/progmars/абвгдāēī", 0);   //  works fine always 
int resCast = access((char*)unicoded.c_str(), 0);
int resConv = access(conv, 0);  

wcout << "Raw " << unicoded.c_str() << endl; // output /home/progmars/абвгдāēī but only if setlocale has been called; else output is /home/progmars/????????
wcout << "Casted " << (char*)unicoded.c_str() << endl; // output /      
wcout << "Converted " << conv << endl; // output /home/progmars/ - for some reason, Unicode chars are cut away in the console, but still they are there because access() picks them up correctly

wcout << "resDirect " << resDirect << endl; // gives correct result depending on the file existence 
wcout << "resCast " << resCast << endl; // wrong result  - always 0 because it looks for / and it's the filesystem root which always exists     
wcout << "resConv " << resConv << endl; 
// gives correct result but only if setlocale() is present

当然,我可以避免所有麻烦ifdef来定义我自己的字符串版本,在Windows上为wstring,在* nix上为string因为* nix似乎对UTF8符号更加自由,并且不介意在纯字符串中使用它们。尽管如此,我仍希望保持我的函数声明对所有平台都一致,同时我也想了解Unicode文件名在Linux中是如何工作的。