Cách lấy phần mở rộng tệp từ chuỗi trong C ++


80

Cho một chuỗi "filename.conf", làm cách nào để xác minh phần mở rộng?

Tôi cần một giải pháp đa nền tảng.

Câu trả lời:


34

Bạn phải đảm bảo rằng bạn quan tâm đến các tên tệp có nhiều hơn một dấu chấm. ví dụ: c:\.directoryname\file.name.with.too.many.dots.extsẽ không được xử lý chính xác bởi strchrhoặcfind.

Yêu thích của tôi là thư viện hệ thống tệp tăng cường có chức năng mở rộng (đường dẫn)


12
Tên thư mục của bạn có thể dễ dàng xử lý bằng cách tìm ngược :).
17 của ngày 26

30
Theo ý kiến ​​cá nhân của tôi, các giải pháp tăng cường không nên được liệt kê như câu trả lời cho các vấn đề c ++. Yêu cầu một thư viện bên ngoài cho một cái gì đó quá đơn giản có vẻ hơi ngớ ngẩn.
marsh

4
@marsh: tuy nhiên, vấn đề quá đơn giản cũng có những trường hợp đặc biệt của nó, đặc biệt là khi xử lý hệ thống tệp - một khái niệm mà hầu hết mọi hệ điều hành chính (và không quá lớn) đều có cách giải thích riêng. Hãy xem xét, ví dụ: các tệp ẩn linux (`/home/oren/.conf ') hoặc trường hợp được đề cập bởi @Torlack . @ 17 của 26, cố gắng đề cập đến tên một mình nên nêu bật những vấn đề có thể phát sinh từ quá đơn giản hóa cách thức mọi người sử dụng dạng tự do đặt tên;)
Oren S

@OrenS Tuy nhiên, giải pháp boost không nên được chấp nhận là câu trả lời cho một câu hỏi không hỏi cách thực hiện với boost. Nó gây hiểu lầm.
Silidrone

@MuhamedCicak ... tốt, giải pháp di động cho othervise liên quan đến một số đoạn mã dài có tính đến việc mã hóa tên tệp hoặc / và sử dụng các thư viện khác (tôi nghi ngờ boost không triển khai nó từ đầu, thay vào đó sử dụng các gói hoặc API khác ở đó khả thi). Lưu ý, mà thậm chí nhận được con đường chính tắc từ một phần như một nhiệm vụ là một vấn đề lớn với nửa tá các trường hợp cạnh ...
Swift - Thứ Sáu Pie

156

Đây là một giải pháp quá đơn giản phải không?

#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;
  }
}

12
@ Điều gì xảy ra khi tên tệp không có phần mở rộng và thư mục trước đó có. trong tên của nó?
Mircea Ispas

4
Tôi đang trả lời câu hỏi; chỉ định "filename.conf", không phải giả thuyết của bạn.
brian newman

5
Theo logic đó, bạn chỉ có thể nói return "Yes...";mà không cần kiểm tra - ngụ ý rằng giải pháp sẽ hoạt động cho các đầu vào khác. Như một ví dụ về bộ đếm khác, một tệp có tên đơn giản là "conf" không có phần mở rộng cũng sẽ trả về "Có ..." như ở trên.
Rollie

4
Cảnh báo cho người khác: Đây là một giải pháp quá đơn giản để sử dụng trong mã sản xuất ngoại trừ các dự án hẹp và cụ thể không cần xử lý nhiều tình huống người dùng cuối trong thế giới thực. Việc phân tích cú pháp và xử lý tên tệp là không hề nhỏ. Cá nhân tôi hầu như luôn luôn sử dụng boost::filesystem, điều này là nhỏ để sử dụng, nhưng cung cấp sự hỗ trợ cần thiết. Xem boost.org/doc/libs/1_55_0/libs/filesystem/doc/index.htm
Dan Nissenbaum

1
std :: filesystem :: path :: extension hiện là một phần của tiêu chuẩn, hãy kiểm tra câu trả lời ví dụ: Roi Danton bên dưới.
yves

42

Cách tốt nhất là không viết bất kỳ mã nào thực hiện nó mà hãy gọi các phương thức hiện có. Trong cửa sổ, PathFindExtension phương thức có lẽ là đơn giản nhất.

Vì vậy, tại sao bạn không viết của riêng bạn?

Lấy ví dụ về strrchr, điều gì sẽ xảy ra khi bạn sử dụng phương thức đó trên chuỗi sau "c: \ program files \ AppleGate.Net \ readme"? ".Net \ readme" có phải là tiện ích mở rộng không? Viết một cái gì đó phù hợp với một số trường hợp ví dụ thì dễ, nhưng có thể khó hơn nhiều để viết một cái gì đó phù hợp với mọi trường hợp.


3
+1 Không viết mã mới thường là câu trả lời tốt nhất! Phiên bản C # của điều này là những gì tôi đã làm bây giờ, nhưng câu trả lời của bạn đã đưa tôi đến đó. msdn.microsoft.com/en-us/library/…
Tom Nghỉ ngơi

Chức năng này (trong Windows 7) sẽ không xử lý đúng "file.i i". Có, điều này là hợp lệ, hãy chú ý khoảng trống.
pcunite,

Anh ấy đã hỏi về việc truy xuất tiện ích mở rộng từ một tệp, không phải đường dẫn đầy đủ. Ngoài ra, một hàm API Windows sẽ không phải là một câu trả lời hay. Đây hoàn toàn không phải là một câu trả lời, mà là một bình luận.
Didac Perez Parera,

4
-1 để cung cấp giải pháp dành riêng cho nền tảng khi OP yêu cầu giải pháp di động.
jb

+1 Từ tôi. Câu hỏi này là câu hỏi đầu tiên xuất hiện khi bạn google 'mfc get file extension', và câu trả lời của bạn là câu trả lời đơn giản nhất.
Eternal 21

32

Giả sử bạn có quyền truy cập vào STL:

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
}

Chỉnh sửa: Đây là một giải pháp đa nền tảng vì bạn không đề cập đến nền tảng. Nếu bạn đặc biệt sử dụng Windows, bạn sẽ muốn tận dụng các chức năng cụ thể của Windows mà những người khác đề cập trong chuỗi.


6
+1, đây là giải pháp đơn giản nhất trong trường hợp bạn có tệp trong chuỗi chứ không phải đường dẫn!
Thomas Bonini

25

Ai đó đã đề cập đến boost nhưng tôi chỉ muốn thêm mã thực để thực hiện việc này:

#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

20

thực sự STL có thể làm điều này mà không cần nhiều mã, tôi khuyên bạn nên tìm hiểu một chút về STL bởi vì nó cho phép bạn làm một số điều thú vị, dù sao thì đây là những gì tôi sử dụng.

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

giải pháp này sẽ luôn trả về phần mở rộng ngay cả trên các chuỗi như "this.abcdesmp3" nếu nó không thể tìm thấy phần mở rộng, nó sẽ trả về "".


15

Với C ++ 17 và nó std::filesystem::path::extension(thư viện là sự kế thừa để thúc đẩy :: hệ thống tập tin), bạn sẽ làm cho câu lệnh của mình diễn đạt hơn là sử dụng ví dụ std::string.

#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"
    }
}

Xem thêm std :: filesystem :: path :: stem , std :: filesystem :: path :: filename .


7

Trên thực tế, cách dễ nhất là

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

Một điều cần nhớ: nếu '.'không tồn tại trong tên tệp, ext sẽ là NULL.


4
Đây sẽ không phải là giải pháp hoàn hảo cho các tệp ẩn UNIX bắt đầu bằng dấu chấm
Mark Kahn

nó phải là const char * ext?
Vlad

4

Hôm nay bản thân tôi cũng tình cờ gặp câu hỏi này, mặc dù tôi đã có một đoạn mã làm việc, tôi nhận ra rằng nó sẽ không hoạt động trong một số trường hợp.

Mặc dù một số người đã đề xuất sử dụng một số thư viện bên ngoài, nhưng tôi thích viết mã của riêng mình cho mục đích học tập.

Một số câu trả lời bao gồm phương pháp tôi đã sử dụng ngay từ đầu (tìm kiếm "." Cuối cùng), nhưng tôi nhớ rằng trên linux các tệp / thư mục ẩn bắt đầu bằng ".". Vì vậy, nếu tệp tệp bị ẩn và không có phần mở rộng, toàn bộ tên tệp sẽ được lấy để mở rộng. Để tránh điều đó, tôi đã viết đoạn mã này:

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_pos>dir_pos+1)
    {
        ext.append(file.begin()+ext_pos,file.end());
        return true;
    }

    return false;
}

Tôi đã không kiểm tra điều này đầy đủ, nhưng tôi nghĩ rằng nó sẽ hoạt động.


3

Sử dụng std :: string's find / rfind giải quyết được vấn đề NÀY, nhưng nếu bạn làm việc nhiều với các đường dẫn thì bạn nên xem đường dẫn boost :: filesystem :: vì nó sẽ làm cho mã của bạn sạch hơn nhiều so với việc loay hoay với các chỉ mục / trình vòng lặp chuỗi thô.

Tôi khuyên bạn nên tăng cường vì đó là một thư viện miễn phí và hoàn toàn di động (mã nguồn mở và thương mại) chất lượng cao, đã được kiểm tra kỹ lưỡng.


3

Đối với chuỗi kiểu mảng char, bạn có thể sử dụng điều này:

#include <ctype.h>
#include <string.h>

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

    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ó thể xử lý các đường dẫn tệp ngoài tên tệp. Hoạt động với cả C và C ++. Và đa nền tảng.


Bạn có thể giảm số lượng điều kiện. Sử dụng strlen(extension)trong forđiều kiện. Sau đó, nếu các ký tự không khớp, trả về false. forVòng lặp bên ngoài trả về true.
LRDPRDX

3

Câu trả lời tốt nhưng tôi thấy hầu hết chúng đều có một số vấn đề: Trước hết, tôi nghĩ rằng một câu trả lời tốt sẽ phù hợp với các tên tệp hoàn chỉnh có tiêu đề đường dẫn của chúng, nó cũng phải hoạt động cho linux hoặc windows hoặc như đã đề cập, nó phải là nền tảng chéo. Đối với hầu hết các câu trả lời; tên tệp không có phần mở rộng nhưng một đường dẫn có tên thư mục bao gồm dấu chấm, hàm sẽ không trả lại đúng phần mở rộng: ví dụ về một số trường hợp thử nghiệm có thể như sau:

    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

Đề xuất " brian newman " sẽ không thành công đối với tên tệp1 và tên tệp 4. và hầu hết các câu trả lời khác dựa trên tìm kiếm ngược sẽ không thành công cho tên tệp1. Tôi khuyên bạn nên bao gồm phương pháp sau trong nguồn của bạn: đó là hàm trả về chỉ mục của ký tự đầu tiên của phần mở rộng hoặc độ dài của chuỗi đã cho nếu không tìm thấy.

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;
}

bạn có thể sử dụng mã trên trong ứng dụng c ++ của mình như dưới đây:

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

Điểm cuối cùng trong một số trường hợp, một thư mục được đặt cho tên tệp làm đối số và bao gồm một dấu chấm trong tên thư mục, hàm sẽ trả về dấu chấm của thư mục, vì vậy trước tiên người dùng nên kiểm tra xem tên đã cho có phải là tên tệp chứ không phải tên thư mục hay không.


3

Phiên bản NET / CLI sử dụng System :: String

   System::String^ GetFileExtension(System::String^ FileName)
   {
       int Ext=FileName->LastIndexOf('.');
       if( Ext != -1 )
           return FileName->Substring(Ext+1);
       return "";
   }

Đây không phải là Visual C ++, nó là .NET / CLI .
Victor

1
@Victor Tôi đã sửa câu trả lời. Cảm ơn đã làm rõ.
Leopoldo Sanczyk

3

Tôi muốn sử dụng boost::filesystem::extension( std::filesystem::path::extensionvới C ++ 17) nhưng nếu bạn không thể sử dụng Boost và bạn chỉ cần xác minh tiện ích mở rộng, một giải pháp đơn giản là:

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"))
{ /* ... */ }

3
_splitpath, _wsplitpath, _splitpath_s, _wsplitpath_w

Đây chỉ là Windows (SDK nền tảng)


2

Đây là một giải pháp mà tôi đã nghĩ ra. Sau đó, tôi nhận thấy rằng nó giống với những gì @serengeor đã đăng.

Nó hoạt động với std::stringfind_last_of, nhưng ý tưởng cơ bản cũng sẽ hoạt động nếu được sửa đổi để sử dụng charmảng và strrchr. Nó xử lý các tệp ẩn và các dấu chấm phụ đại diện cho thư mục hiện tại. Nó độc lập với nền tảng.

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;
}

Bài kiểm tra đơn vị:

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;
}

2

Tôi sử dụng hai hàm này để lấy phần mở rộngtên tệp không có phần mở rộng :

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);    
}

Và những regexcách tiếp cận này cho một số yêu cầu bổ sung nhất định:

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;

}

Các yêu cầu bổ sung được đáp ứng bằng phương pháp regex:

  1. Nếu tên tệp giống .confighoặc tương tự như thế này, phần mở rộng sẽ là một chuỗi trống và tên tệp không có phần mở rộng sẽ là .config.
  2. Nếu tên tệp không có bất kỳ phần mở rộng nào, phần mở rộng sẽ là một chuỗi trống, tên tệp không có phần mở rộng sẽ là tên tệp không thay đổi.

BIÊN TẬP:

Các yêu cầu bổ sung cũng có thể được đáp ứng bằng cách sau:

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;
}

Ghi chú:

Chỉ chuyển tên tệp (không phải đường dẫn) trong các hàm trên.



1

Hoặc bạn có thể sử dụng cái này:

    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; a<s.length()-Len; a++)
                    Ext[a] = FileName[s.length()-(s.length()-Len)+a];
                Ext[s.length()-Len] = '\0';
                return Ext;
            }
        }
    }

Mã này đa nền tảng


1

Nếu bạn sử dụng thư viện Qt, bạn có thể thử dùng hậu tố () của QFileInfo


2
Qt có liên quan gì đến câu hỏi này? Tại sao lại giới thiệu sự phụ thuộc lớn của bên thứ ba cho một thao tác chuỗi đơn giản? Nếu đi theo con đường đó, tại sao không chỉ sử dụng boost?
derpface,

1

Đây là một hàm nhận đường dẫn / tên tệp dưới dạng chuỗi và trả về phần mở rộng dưới dạng chuỗi. Tất cả đều là c ++ tiêu chuẩn và sẽ hoạt động đa nền tảng cho hầu hết các nền tảng.

Không giống như một số câu trả lời khác ở đây, nó xử lý các trường hợp kỳ lạ mà PathFindExtension của windows xử lý, dựa trên tài liệu của PathFindExtensions.

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 );
}

Xin chào, có một vấn đề với giải pháp của bạn: max( filename.rfind(L'\\'), filename.rfind(L'/') )sẽ so sánh hai giá trị không dấu, một trong số chúng có thể nposlà số nguyên không dấu lớn nhất có thể. Vì vậy, nó có thể trông giống như không có thư mục ngay cả khi nó ở đó!
Andrii Kovalevskyi

0

Nếu bạn tình cờ sử dụng thư viện Poco, bạn có thể làm:

#include <Poco/Path.h>

...

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

0

Nếu bạn coi phần mở rộng là dấu chấm cuối cùng và các ký tự có thể có sau nó, nhưng chỉ khi chúng không chứa ký tự phân tách thư mục, hàm sau sẽ trả về chỉ mục bắt đầu của phần mở rộng hoặc -1 nếu không tìm thấy phần mở rộng. Khi có điều đó, bạn có thể làm bất cứ điều gì bạn muốn, chẳng hạn như tách tiện ích mở rộng, thay đổi nó, kiểm tra nó, v.v.

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;
}

0

Tôi đã sử dụng hàm PathFindExtension () để biết liệu nó có phải là tệp tif hợp lệ hay không.

#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;
}

0

Bạn có thể sử dụng strrchr () để tìm lần xuất hiện cuối cùng của các tệp phần mở rộng dựa trên. (Dot) và get. (Dot). Kiểm tra mã dưới đây chẳng hạn.

#include<stdio.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\n");
        return;
    }

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

int main()
{
    const char* file_name = "c:\\.directoryname\\file.name.with.too.many.dots.ext";
    GetFileExtension(file_name);
    return 0;
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.