在C中提取文件名及其扩展名

时间:2014-07-26 22:03:05

标签: c string filenames

所以我们有一个路径字符串/home/user/music/thomas.mp3

从这个字符串中提取文件名(没有扩展名,“thomas”)和扩展名(“mp3”)的简单方法在哪里?用于文件名和扩展的函数。我们手中只有GNU libc。


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX_FILENAME_SIZE 256

char *filename(char *str) {
    char *result;
    char *last;
    if ((last = strrchr(str, '.')) != NULL ) {
        if ((*last == '.') && (last == str))
            return str;
        else {
            result = (char*) malloc(MAX_FILENAME_SIZE);
            snprintf(result, sizeof result, "%.*s", (int)(last - str), str);
            return result;
        }
    } else {
        return str;
    }
}

char *extname(char *str) {
    char *result;
    char *last;
    if ((last = strrchr(str, '.')) != NULL) {
        if ((*last == '.') && (last == str))
            return "";
        else {
            result = (char*) malloc(MAX_FILENAME_SIZE);
            snprintf(result, sizeof result, "%s", last + 1);
            return result;
        }
    } else {
        return ""; // Empty/NULL string
    }
}

5 个答案:

答案 0 :(得分:4)

使用basename获取文件名,然后您可以使用类似的内容来获取扩展名。

char *get_filename_ext(const char *filename) {
    const char *dot = strrchr(filename, '.');
    if(!dot || dot == filename) return "";
    return dot + 1;
}

编辑: 尝试类似的东西。

#include <string.h>
#include <libgen.h>

static void printFileInfo(char *path) {
    char *bname;
    char *path2 = strdup(path);
    bname = basename(path2);
    printf("%s.%s\n",bname, get_filename_ext(bname));
    free(path2);
}

答案 1 :(得分:2)

这是我用于解决该问题的例程:

将原始字符串 分隔为 路径 file_name 的单独字符串> 扩展程序

适用于Windows和Linux ,相对或绝对样式路径。将处理嵌入了“。”的目录名称。将处理没有扩展名的文件名。

/////////////////////////////////////////////////////////
//
// Example:
// Given path == "C:\\dir1\\dir2\\dir3\\file.exe"
// will return path_ as   "C:\\dir1\\dir2\\dir3"
// Will return base_ as   "file"
// Will return ext_ as    "exe"
//
/////////////////////////////////////////////////////////
void GetFileParts(char *path, char *path_, char *base_, char *ext_)
{
    char *base;
    char *ext;
    char nameKeep[MAX_PATHNAME_LEN];
    char pathKeep[MAX_PATHNAME_LEN];
    char pathKeep2[MAX_PATHNAME_LEN]; //preserve original input string
    char File_Ext[40];
    char baseK[40];
    int lenFullPath, lenExt_, lenBase_;
    char *sDelim={0};
    int   iDelim=0;
    int  rel=0, i;

    if(path)
    {   //determine type of path string (C:\\, \\, /, ./, .\\)
        if(  (strlen(path) > 1) &&

            (
            ((path[1] == ':' ) &&
             (path[2] == '\\'))||

             (path[0] == '\\') ||

             (path[0] == '/' ) ||

            ((path[0] == '.' ) &&
             (path[1] == '/' ))||

            ((path[0] == '.' ) &&
             (path[1] == '\\'))
            )
        )
        {
            sDelim = calloc(5, sizeof(char));
            /*  //   */if(path[0] == '\\') iDelim = '\\', strcpy(sDelim, "\\");
            /*  c:\\ */if(path[1] == ':' ) iDelim = '\\', strcpy(sDelim, "\\"); // also satisfies path[2] == '\\'
            /*  /    */if(path[0] == '/' ) iDelim = '/' , strcpy(sDelim, "/" );
            /* ./    */if((path[0] == '.')&&(path[1] == '/')) iDelim = '/' , strcpy(sDelim, "/" );
            /* .\\   */if((path[0] == '.')&&(path[1] == '\\')) iDelim = '\\' , strcpy(sDelim, "\\" );
            /*  \\\\ */if((path[0] == '\\')&&(path[1] == '\\')) iDelim = '\\', strcpy(sDelim, "\\");
            if(path[0]=='.')
            {
                rel = 1;
                path[0]='*';
            }

            if(!strstr(path, "."))  // if no filename, set path to have trailing delim,
            {                      //set others to "" and return
                lenFullPath = strlen(path);
                if(path[lenFullPath-1] != iDelim)
                {
                    strcat(path, sDelim);
                    path_[0]=0;
                    base_[0]=0;
                    ext_[0]=0;
                }
            }
            else
            {
                nameKeep[0]=0;         //works with C:\\dir1\file.txt
                pathKeep[0]=0;
                pathKeep2[0]=0;        //preserves *path
                File_Ext[0]=0;
                baseK[0]=0;

                //Get lenth of full path
                lenFullPath = strlen(path);

                strcpy(nameKeep, path);
                strcpy(pathKeep, path);
                strcpy(pathKeep2, path);
                strcpy(path_, path); //capture path

                //Get length of extension:
                for(i=lenFullPath-1;i>=0;i--)
                {
                    if(pathKeep[i]=='.') break; 
                }
                lenExt_ = (lenFullPath - i) -1;

                base = strtok(path, sDelim);
                while(base)
                {
                    strcpy(File_Ext, base);
                    base = strtok(NULL, sDelim);
                }


                strcpy(baseK, File_Ext);
                lenBase_ = strlen(baseK) - lenExt_;
                baseK[lenBase_-1]=0;
                strcpy(base_, baseK);

                path_[lenFullPath -lenExt_ -lenBase_ -1] = 0;

                ext = strtok(File_Ext, ".");
                ext = strtok(NULL, ".");
                if(ext) strcpy(ext_, ext);
                else strcpy(ext_, "");
            }
            memset(path, 0, lenFullPath);
            strcpy(path, pathKeep2);
            if(rel)path_[0]='.';//replace first "." for relative path
            free(sDelim);
        }
    }
}

答案 2 :(得分:2)

关于你的实际代码(到目前为止所有其他答案都说要废弃并做其他事情,这是一个很好的建议,但是我正在解决你的代码,因为它包含错误,提前了解它是好的下次你尝试写点东西时。)

首先:

strncpy(str, result, (size_t) (last-str) + 1);

不好。你有错误的方式 dest src ;此外,此函数不会对输出进行空值终止(除非输入足够短,否则不是)。一般来说,strncpy几乎不是解决问题的好方法;如果你知道长度,可以strcpy,或snprintf

更简单,更不容易出错:

snprintf(result, sizeof result, "%.*s", (int)(last - str), str);

在其他功能中相似,

snprintf(result, sizeof result, "%s", last + 1);

snprintf函数永远不会溢出缓冲区并始终生成以空字符结尾的字符串,只要你得到正确的缓冲区长度!

现在,即使您修复了那些,那么您还有另一个基本问题,即您返回一个指向缓冲区的指针,该缓冲区在函数返回时被销毁。您可以通过返回ext来修复last + 1,因为无论如何它都是空终止的。但是对于filename,你有一套通常的选择:

  • 返回指针和长度,并将其视为长度计数字符串,而不是以空值终止的字符串
  • 返回指向malloc内存的指针
  • 返回指向静态缓冲区的指针
  • 期望调用者传入缓冲区和缓冲区长度,您只需将其写入

最后,在失败时返回NULL可能是一个坏主意;如果没有.,则返回filename的整个字符串,以及ext的空字符串。然后调用代码不必使用NULL的检查来扭曲自己。

答案 3 :(得分:0)

这是一个老派算法,可以解决这个问题。

char path[100] = "/home/user/music/thomas.mp3";
int offset_extension, offset_name;
int len = strlen(path);
int i;
for (i = len; i >= 0; i--) {
    if (path[i] == '.')
        break;
    if (path[i] == '/') {
        i = len;
        break;
    }
}
if (i == -1) {
    fprintf(stderr,"Invalid path");
    exit(EXIT_FAILURE);
}
offset_extension = i;
for (; i >= 0; i--)
    if (path[i] == '/')
        break;
if (i == -1) {
    fprintf(stderr,"Invalid path");
    exit(EXIT_FAILURE);
}
offset_name = i;

char *extension, name[100];
extension = &path[offset_extension+1];
memcpy(name, &path[offset_name+1], offset_extension - offset_name - 1);

然后,您可以在变量nameextension

下获得这两种信息
printf("%s %s", name, extension);

这将打印:

thomas mp3

答案 4 :(得分:0)

我知道这很老了。但是我倾向于将strtok用于此类事情。

/* strtok example */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#define MAX_TOKENS  20  /* Some reasonable values */
#define MAX_STRING  128 /* Easy enough to make dynamic with mallocs */

int main ()
{
  char str[] ="/home/user/music/thomas.mp3";
  char sep[] = "./";
  char collect[MAX_TOKENS][MAX_STRING];

  /* Not really necessary, since \0 is added inplace. I do this out of habit. */
  memset(collect, 0, MAX_TOKENS * MAX_STRING);
  char * pch = strtok (str, sep);
  int ccount = 0;    

  if(pch != NULL) {
    /* collect all seperated text */
    while(pch != NULL) { 
        strncpy( collect[ccount++], pch, strlen(pch));
        pch = strtok (NULL, sep);
    }
  }

  /* output tokens. */
  for(int i=0; i<ccount; ++i)
    printf ("Token: %s\n", collect[i]);
  return 0;
}

这是一个粗糙的示例,它使以后处理令牌变得很容易。即最后一个标记是扩展名。第二倒数是基本名称,依此类推。 我还发现它对于重建不同平台的路径很有用-用\替换/。