我需要一个跨平台的解决方案。这样的解决方案是不是太简单了?
123456789101112#include iostream
#include stringint main

std::"/>

如何从C中的字符串获取文件扩展名

如何从C中的字符串获取文件扩展名

How to get file extension from string in C++

给定一个字符串"filename.conf",我如何验证扩展部分?

我需要一个跨平台的解决方案。


这样的解决方案是不是太简单了?

1
2
3
4
5
6
7
8
9
10
11
12
#include iostream
#include string

int main()
{
  std::string fn ="filename.conf";
  if(fn.substr(fn.find_last_of(".") + 1) =="conf") {
    std::cout "Yes..."  std::endl;
  } else {
    std::cout "No..."  std::endl;
  }
}

最好的方法是不编写任何代码,而是调用现有的方法。在 windows 中,PathFindExtension 方法可能是最简单的。

那你为什么不自己写呢?

好吧,以 strrcr 为例,当您在以下字符串 "c:\\\\\\\\program files\\\\\\\\AppleGate.Net\\\\ 上使用该方法时会发生什么
eadme"? 是".Net\\\\
eadme" 扩展?编写适用于一些示例案例的内容很容易,但编写适用于所有案例的内容可能要困难得多。


您必须确保处理多于一个点的文件名。
示例: strchrfind.

无法正确处理 c:\\.directoryname\\file.name.with.too.many.dots.ext

我最喜欢的是具有扩展(路径)功能的 boost 文件系统库


假设您可以访问 STL:

1
2
3
4
5
6
7
8
9
10
11
12
13
std::string filename("filename.conf");
std::string::size_type idx;

idx = filename.rfind('.');

if(idx != std::string::npos)
{
    std::string extension = filename.substr(idx+1);
}
else
{
    // No extension found
}

编辑:这是一个跨平台解决方案,因为您没有提及平台。如果您专门使用 Windows,您将希望利用线程中其他人提到的 Windows 特定功能。


使用 C 17 及其 std::filesystem::path::extension(该库是 boost::filesystem 的继任者),您将使您的语句比使用例如更具表现力。 std::string.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include iostream
#include filesystem // C++17
namespace fs = std::filesystem;

int main()
{
    fs::path filePath ="my/path/to/myFile.conf";
    if (filePath.extension() ==".conf") // Heed the dot.
    {
        std::cout  filePath.stem() " is a valid type."; // Output:"myFile is a valid type."
    }
    else
    {
        std::cout  filePath.filename() " is an invalid type."; // Output: e.g."myFile.cfg is an invalid type"
    }
}

参见 std::filesystem::path::stem, std::filesystem::path::filename。


其他人提到了 boost,但我只是想添加实际代码来执行此操作:

1
2
3
4
5
6
7
8
#include boost/filesystem.hpp
using std::string;
string texture         = foo-GetTextureFilename();
string file_extension  = boost::filesystem::extension(texture);
cout "attempting load texture named"  texture
     "    whose extensions seems to be"
      file_extension  endl;
// Use JPEG or PNG loader function, or report invalid extension

其实STL不需要太多代码就可以做到这一点,我建议你了解一下STL,因为它可以让你做一些花哨的事情,反正我就是用这个。

1
2
3
4
5
6
std::string GetFileExtension(const std::string& FileName)
{
    if(FileName.find_last_of(".") != std::string::npos)
        return FileName.substr(FileName.find_last_of(".")+1);
    return"";
}

此解决方案将始终返回扩展名,即使在诸如 "this.a.b.c.d.e.s.mp3" 之类的字符串上,如果它找不到将返回 ".

的扩展名


其实最简单的方法是

1
2
char* ext;
ext = strrchr(filename,'.')

要记住一件事:如果文件名中不存在 '.',则 ext 将是 NULL.


我自己今天偶然发现了这个问题,尽管我已经有一个工作代码,但我发现它在某些情况下不起作用。

虽然有些人已经建议使用一些外部库,但我更喜欢编写自己的代码用于学习目的。

一些答案??包括我最初使用的方法(寻找最后一个 "."),但我记得在 linux 上隐藏文件/文件夹以 "." 开头。
因此,如果文件文件被隐藏并且没有扩展名,则整个文件名将被用作扩展名。
为了避免这种情况,我写了这段代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
bool getFileExtension(const char * dir_separator, const std::string & file, std::string & ext)
{
    std::size_t ext_pos = file.rfind(".");
    std::size_t dir_pos = file.rfind(dir_separator);

    if(ext_posdir_pos+1)
    {
        ext.append(file.begin()+ext_pos,file.end());
        return true;
    }

    return false;
}

我还没有完全测试过这个,但我认为它应该可以工作。


我会使用 boost::filesystem::extension(std::filesystem::path::extension 使用 C 17),但如果你不能使用 Boost 并且你只需要验证扩展名,一个简单的解决方案是:

1
2
3
4
5
6
7
8
bool ends_with(const std::string &filename, const std::string &ext)
{
  return ext.length() = filename.length() &&
         std::equal(ext.rbegin(), ext.rend(), filename.rbegin());
}

if (ends_with(filename,".conf"))
{ /* ... */ }


使用 std::string\\ 的 find/rfind 可以解决这个问题,但是如果您经常使用路径,那么您应该查看 boost::filesystem::path,因为它会使您的代码比摆弄 raw 更干净字符串索引/迭代器。

我建议使用 boost,因为它是一个高质量、经过良好测试、(开源和商业)免费且完全可移植的库。


使用 System::String

的 NET/CLI 版本

1
2
3
4
5
6
7
   System::String^ GetFileExtension(System::String^ FileName)
   {
       int Ext=FileName-LastIndexOf('.');
       if( Ext != -1 )
           return FileName-Substring(Ext+1);
       return"";
   }

您可以使用 strrchr() 查找最后出现的 .(dot) 并获取基于 .(dot) 的扩展文件。
例如检查下面的代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#includestdio.h

void GetFileExtension(const char* file_name) {

    int ext = '.';
    const char* extension = NULL;
    extension = strrchr(file_name, ext);

    if(extension == NULL){
        printf("Invalid extension encountered\
"
);
        return;
    }

    printf("File extension is %s\
"
, extension);
}

int main()
{
    const char* file_name ="c:\\\\.directoryname\\\\file.name.with.too.many.dots.ext";
    GetFileExtension(file_name);
    return 0;
}

这是我想出的解决方案。然后,我注意到它与@serengeor 发布的内容相似。

它适用于 std::stringfind_last_of,但如果修改为使用 char 数组和 strrchr,基本思想也将起作用。
它处理隐藏文件和代表当前目录的额外点。它独立于平台。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
string PathGetExtension( string const & path )
{
  string ext;

  // Find the last dot, if any.
  size_t dotIdx = path.find_last_of("." );
  if ( dotIdx != string::npos )
  {
    // Find the last directory separator, if any.
    size_t dirSepIdx = path.find_last_of("/\" );

    // If the dot is at the beginning of the file name, do not treat it as a file extension.
    // e.g., a hidden file: "
.alpha".
    // This test also incidentally avoids a dot that is really a current directory indicator.
    // e.g.: "
alpha/./bravo"
    if ( dotIdx  dirSepIdx + 1 )
    {
      ext = path.substr( dotIdx );
    }
  }

  return ext;
}

单元测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
int TestPathGetExtension( void )
{
  int errCount = 0;

  string tests[][2] =
  {
    {"/alpha/bravo.txt",".txt" },
    {"/alpha/.bravo","" },
    {".alpha","" },
    {"./alpha.txt",".txt" },
    {"alpha/./bravo","" },
    {"alpha/./bravo.txt",".txt" },
    {"./alpha","" },
    {"c:\\\\alpha\\\\bravo.net\\\\charlie.txt",".txt" },
  };

  int n = sizeof( tests ) / sizeof( tests[0] );

  for ( int i = 0; i  n; ++i )
  {
    string ext = PathGetExtension( tests[i][0] );
    if ( ext != tests[i][1] )
    {
      ++errCount;
    }
  }

  return errCount;
}

对于 char 数组类型的字符串,您可以使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include ctype.h
#include string.h

int main()
{
    char filename[] ="apples.bmp";
    char extension[] =".webp";

    if(compare_extension(filename, extension) == true)
    {
        // .....
    } else {
        // .....
    }

    return 0;
}

bool compare_extension(char *filename, char *extension)
{
    /* Sanity checks */

    if(filename == NULL || extension == NULL)
        return false;

    if(strlen(filename) == 0 || strlen(extension) == 0)
        return false;

    if(strchr(filename, '.') == NULL || strchr(extension, '.') == NULL)
        return false;

    /* Iterate backwards through respective strings and compare each char one at a time */

    for(int i = 0; i  strlen(filename); i++)
    {
        if(tolower(filename[strlen(filename) - i - 1]) == tolower(extension[strlen(extension) - i - 1]))
        {
            if(i == strlen(extension) - 1)
                return true;
        } else
            break;
    }

    return false;
}

除了文件名之外,还可以处理文件路径。适用于 C 和 C 。并且跨平台。


很好的答案,但我发现他们中的大多数都有一些问题:
首先,我认为一个好的答案应该适用于具有路径标题的完整文件名,它也应该适用于 linux 或 windows,或者如前所述,它应该是跨平台的。对于大多数答案;没有扩展名的文件名,但路径中包含点的文件夹名称,该函数将无法返回正确的扩展名:一些测试用例的示例如下:

1
2
3
4
5
6
7
8
    const char filename1 = {"C:\\\\init.d\\\\doc"}; // = No extention
    const char filename2 = {"..\\\\doc"}; //relative path name = No extention
    const char filename3 = {""}; //emputy file name = No extention
    const char filename4 = {"testing"}; //only single name = No extention
    const char filename5 = {"tested/k.doc"}; // normal file name = doc
    const char filename6 = {".."}; // parent folder = No extention
    const char filename7 = {"/"}; // linux root = No extention
    const char filename8 = {"/bin/test.d.config/lx.wize.str"}; // ordinary path! = str

"brian newman" 对文件名 1 和文件名 4 的建议将失败。
对于 filename1,大多数基于反向查找的其他答案都将失败。
我建议在您的来源中包含以下方法:
这是函数返回扩展的第一个字符的索引或给定字符串的长度,如果没有找到。

1
2
3
4
5
6
7
8
9
10
11
12
13
size_t find_ext_idx(const char* fileName)
{
    size_t len = strlen(fileName);
    size_t idx = len-1;
    for(size_t i = 0; *(fileName+i); i++) {
        if (*(fileName+i) == '.') {
            idx = i;
        } else if (*(fileName + i) == '/' || *(fileName + i) == '\\\') {
            idx = len - 1;
        }
    }
    return idx+1;
}

您可以在您的 c 应用程序中使用上述代码,如下所示:

1
2
3
4
std::string get_file_ext(const char* fileName)
{
    return std::string(fileName).substr(find_ext_idx(fileName));
}

最后一点,在某些情况下,a 文件夹被赋予文件名作为参数,并在文件夹名称中包含一个点,该函数将返回文件夹的点尾,因此最好首先让用户检查给定名称是否为文件名而不是文件夹名称。


1
_splitpath, _wsplitpath, _splitpath_s, _wsplitpath_w

这仅适用于 Windows(平台 SDK)


如果你使用Qt库,可以试试QFileInfo的suffix()


我使用这两个函数来获取扩展名和不带扩展名的文件名:

1
2
3
4
5
6
7
8
9
10
11
12
std::string fileExtension(std::string file){

    std::size_t found = file.find_last_of(".");
    return file.substr(found+1);

}

std::string fileNameWithoutExtension(std::string file){

    std::size_t found = file.find_last_of(".");
    return file.substr(0,found);    
}

这些 regex 方法可以满足某些额外的要求:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
std::string fileExtension(std::string file){

    std::regex re(".*[^\\\\.]+\\\\.([^\\\\.]+$)");
    std::smatch result;
    if(std::regex_match(file,result,re))return result[1];
    else return"";

}

std::string fileNameWithoutExtension(std::string file){

    std::regex re("(.*[^\\\\.]+)\\\\.[^\\\\.]+$");
    std::smatch result;
    if(std::regex_match(file,result,re))return result[1];
    else return file;

}

正则表达式方法满足的额外要求:

  • 如果文件名类似于 .config 或类似的名称,则扩展名将是一个空字符串,而没有扩展名的文件名将是 .config
  • 如果文件名没有任何扩展名,则扩展名将是一个空字符串,没有扩展名的文件名将是文件名不变。
  • 编辑:

    还可以通过以下方式满足额外要求:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    std::string fileExtension(const std::string& file){
        std::string::size_type pos=file.find_last_of('.');
        if(pos!=std::string::npos&&pos!=0)return file.substr(pos+1);
        else return"";
    }


    std::string fileNameWithoutExtension(const std::string& file){
        std::string::size_type pos=file.find_last_of('.');
        if(pos!=std::string::npos&&pos!=0)return file.substr(0,pos);
        else return file;
    }

    注意:

    在上述函数中只传递文件名(而不是路径)。


    这是一个函数,它将路径/文件名作为字符串并将扩展名作为字符串返回。它都是标准的 c ,并且应该适用于大多数平台的跨平台。

    与此处的其他几个答案不同,它根据 PathFindExtensions 的文档处理 windows\\' PathFindExtension 处理的奇怪情况。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    wstring get_file_extension( wstring filename )
    {
        size_t last_dot_offset = filename.rfind(L'.');
        // This assumes your directory separators are either \\ or /
        size_t last_dirsep_offset = max( filename.rfind(L'\\\'), filename.rfind(L'/') );

        // no dot = no extension
        if( last_dot_offset == wstring::npos )
            return L"";

        // directory separator after last dot = extension of directory, not file.
        // for example, given C:\\temp.old\\file_that_has_no_extension we should return"" not"old"
        if( (last_dirsep_offset != wstring::npos) && (last_dirsep_offset  last_dot_offset) )
            return L"";

        return filename.substr( last_dot_offset + 1 );
    }

    或者你可以使用这个:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
        char *ExtractFileExt(char *FileName)
        {
            std::string s = FileName;
            int Len = s.length();
            while(TRUE)
            {
                if(FileName[Len] != '.')
                    Len--;
                else
                {
                    char *Ext = new char[s.length()-Len+1];
                    for(int a=0; as.length()-Len; a++)
                        Ext[a] = FileName[s.length()-(s.length()-Len)+a];
                    Ext[s.length()-Len] = '\\0';
                    return Ext;
                }
            }
        }

    此代码是跨平台的


    所以,使用 std::filesystem 是最好的答案,但是如果出于某种原因您没有可用的 C 17 功能,即使输入字符串包含目录,这也可以工作:

    1
    2
    3
    4
    string getextn (const string &fn) {
      int sep = fn.find_last_of(".\\\\/");
      return (sep = 0 && fn[sep] == '.') ? fn.substr(sep) :"";
    }

    我之所以添加这个,是因为如果文件的路径包含点而文件不包含,则此处的其余答案要么异常复杂,要么失败。我认为 find_last_of 可以查找多个字符这一事实经常被忽视。

    它适用于 /\\ 路径分隔符。如果扩展名本身包含斜杠,则它会失败,但这通常很少见。它不会对以点开头且不包含其他点的文件名进行任何过滤 - 如果这对您很重要,那么这是这里最不合理的答案。

    示例输入/输出:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    / = ''
    ./ = ''
    ./pathname/ = ''
    ./path.name/ = ''
    pathname/ = ''
    path.name/ = ''
    c:\\path.name\\ = ''
    /. = '.'
    ./. = '.'
    ./pathname/. = '.'
    ./path.name/. = '.'
    pathname/. = '.'
    path.name/. = '.'
    c:\\path.name\\. = '.'
    /.git_ignore = '.git_ignore'
    ./.git_ignore = '.git_ignore'
    ./pathname/.git_ignore = '.git_ignore'
    ./path.name/.git_ignore = '.git_ignore'
    pathname/.git_ignore = '.git_ignore'
    path.name/.git_ignore = '.git_ignore'
    c:\\path.name\\.git_ignore = '.git_ignore'
    /filename = ''
    ./filename = ''
    ./pathname/filename = ''
    ./path.name/filename = ''
    pathname/filename = ''
    path.name/filename = ''
    c:\\path.name\\filename = ''
    /filename. = '.'
    ./filename. = '.'
    ./pathname/filename. = '.'
    ./path.name/filename. = '.'
    pathname/filename. = '.'
    path.name/filename. = '.'
    c:\\path.name\\filename. = '.'
    /filename.tar = '.tar'
    ./filename.tar = '.tar'
    ./pathname/filename.tar = '.tar'
    ./path.name/filename.tar = '.tar'
    pathname/filename.tar = '.tar'
    path.name/filename.tar = '.tar'
    c:\\path.name\\filename.tar = '.tar'
    /filename.tar.gz = '.gz'
    ./filename.tar.gz = '.gz'
    ./pathname/filename.tar.gz = '.gz'
    ./path.name/filename.tar.gz = '.gz'
    pathname/filename.tar.gz = '.gz'
    path.name/filename.tar.gz = '.gz'
    c:\\path.name\\filename.tar.gz = '.gz'

    尝试使用strstr

    1
    2
    char* lastSlash;
    lastSlash = strstr(filename,".");

    如果你碰巧使用 Poco 库,你可以这样做:

    1
    2
    3
    4
    5
    #include Poco/Path.h

    ...

    std::string fileExt = Poco::Path("/home/user/myFile.abc").getExtension(); // =="abc"

    如果您认为扩展名是最后一个点以及它后面的可能字符,但前提是它们不包含目录分隔符,则以下函数返回扩展名起始索引,如果未找到扩展名,则返回 -1。当你拥有它时,你可以做任何你想做的事情,比如剥离扩展名、更改它、检查它等等。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    long get_extension_index(string path, char dir_separator = '/') {
        // Look from the end for the first '.',
        // but give up if finding a dir separator char first
        for(long i = path.length() - 1; i = 0; --i) {
            if(path[i] == '.') {
                return i;
            }
            if(path[i] == dir_separator) {
                return -1;
            }
        }
        return -1;
    }

    我使用 PathFindExtension() 函数来判断它是否是一个有效的 tif 文件。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    #include Shlwapi.h
    bool A2iAWrapperUtility::isValidImageFile(string imageFile)
    {
        char * pStrExtension = ::PathFindExtension(imageFile.c_str());

        if (pStrExtension != NULL && strcmp(pStrExtension,".tif") == 0)
        {
            return true;
        }

        return false;
    }

    推荐阅读