从路径中提取文件名的方法

46

Linux API/POSIX应该有一些优雅的方法从完整路径中提取基本文件名

11个回答

62

17

使用 basename(其具有奇怪的边缘情况语义),或通过调用 strrchr(pathname, '/') 并将整个字符串视为基本名称(basename),如果它不包含 '/' 字符,您可以自己完成此操作。


6

以下是一个单行示例(假设已给定 char * whoami),它说明了基本算法:

(whoami = strrchr(argv[0], '/')) ? ++whoami : (whoami = argv[0]);

如果有可能为 NULL,则需要进行额外的检查。还请注意,这只是指向原始字符串 - 可能需要使用 "strdup()"。


4

如果你也对目录名感兴趣,可以使用strstr

char *path ="ab/cde/fg.out";
char *ssc;
int l = 0;
ssc = strstr(path, "/");
do{
    l = strlen(ssc) + 1;
    path = &path[strlen(path)-l+2];
    ssc = strstr(path, "/");
}while(ssc);
printf("%s\n", path);

4
basename()函数返回路径的最后一个组成部分,可能是文件夹名称而不是文件名。有两个版本的basename()函数:GNU版本和POSIX版本。
在包含#define _GNU_SOURCE之后,可以在string.h中找到GNU版本:
    #define _GNU_SOURCE
#include <string.h>
GNU版本使用const并且不修改参数。
    char * basename (const char *path)
如果包含了libgen.h,则XPG(POSIX)版本将覆盖此函数。
    char * basename (char *path)
此函数可能通过删除尾随的'/'字节来修改参数。在这种情况下,结果可能与GNU版本不同:
    basename("foo/bar/")
如果您使用XPG版本,则将返回字符串"bar",如果您使用GNU版本,则返回空字符串。
参考资料:
    basename(3) - Linux Man Pages
    Function: char * basename (const char *filename), Finding Tokens in a String.

3
当然,如果这是一个仅限于Gnu/Linux的问题,那么您可以使用库函数。
您可以参考以下链接:https://linux.die.net/man/3/basename 虽然一些人可能不赞成,但这些符合POSIX标准的Gnu库函数并不使用const。因为库实用程序函数很少使用。如果这对您很重要,那么我想您将不得不坚持使用自己的功能或者也许以下内容更符合您的口味?
#include <stdio.h>
#include <string.h>

int main(int argc, char *argv[])
{
    char *fn;
    char *input;

    if (argc > 1)
        input = argv[1];
    else
        input = argv[0];

    /* handle trailing '/' e.g. 
       input == "/home/me/myprogram/" */
    if (input[(strlen(input) - 1)] == '/')
        input[(strlen(input) - 1)] = '\0';

    (fn = strrchr(input, '/')) ? ++fn : (fn = input);


    printf("%s\n", fn);
    return 0;
}

3
template<typename charType>
charType* getFileNameFromPath( charType* path )
{
    if( path == NULL )
        return NULL;

    charType * pFileName = path;
    for( charType * pCur = path; *pCur != '\0'; pCur++)
    {
        if( *pCur == '/' || *pCur == '\\' )
            pFileName = pCur+1;
    }
    
    return pFileName;
}

调用:

wchar_t * fileName = getFileNameFromPath < wchar_t > ( filePath );

(这是 c++ 代码)


7
欢迎来到 Stack Overflow!请注意,这个问题被标记为 [tag:c],而你的答案是用 [tag:c++] 写的。请修改一下你的答案,确保与问题标签相符。 - Hasturkun

1

您可以通过反斜杠来转义斜杠并使用以下代码:

#include <stdio.h>
#include <string.h>

int main(void)
{
  char path[] = "C:\\etc\\passwd.c"; //string with escaped slashes
  char temp[256]; //result here
  char *ch; //define this
  ch = strtok(path, "\\"); //first split
  while (ch != NULL) {
      strcpy(temp, ch);//copy result
      printf("%s\n", ch);
      ch = strtok(NULL, "\\");//next split
  }

  printf("last filename: %s", temp);//result filename

  return 0;

}

0
我使用了一种更简单的方法来获取路径中的文件名或最后一部分。
char * extract_file_name(char *path)
{
    int len = strlen(path);
    int flag=0;
    printf("\nlength of %s : %d",path, len);

    
    for(int i=len-1; i>0; i--)
    {
        if(path[i]=='\\' || path[i]=='//' || path[i]=='/' )
        {
            flag=1;
            path = path+i+1;
            break;
        }
    }
    return path;
}

Input path = "C:/Users/me/Documents/somefile.txt"
Output = "somefile.txt"

-2

@Nikolay Khilyuk提供了最好的解决方案,除此之外。

1)回到使用char *,没有使用const的好理由。

2)这段代码不可移植,并且可能在非POSIX系统上失败,在这些系统中,“/”不是文件系统分隔符,具体取决于编译器实现。对于某些Windows编译器,您可能需要测试“\”而不是“/”。您甚至可以测试系统并根据结果设置分隔符。

函数名很长但描述性强,没有问题。永远无法确定函数是否会返回文件名,只有在函数编写正确的情况下才能确定它是否可以。虽然如果有人在不是路径的字符串上使用它,显然它会失败。我可能会将其命名为basename,因为它会向许多程序员传达其目的。这只是基于我的偏见,您的名称也很好。至于此函数将处理的字符串长度以及为什么有人认为这将是一个要点?您不太可能在ANSI C编译器上处理比此函数可以处理的路径名更长的路径名。因为size_t被定义为无符号长整型,其范围为0到4,294,967,295。

我使用以下内容证明了您的函数。

    #include <stdio.h>
    #include <string.h>

    char* getFileNameFromPath(char* path);

    int main(int argc, char *argv[])
    {
        char *fn;

        fn = getFileNameFromPath(argv[0]);
        printf("%s\n", fn);
        return 0;
    }

    char* getFileNameFromPath(char* path)
    {
       for(size_t i = strlen(path) - 1; i; i--)  
       {
            if (path[i] == '/')
            {
                return &path[i+1];
            }
        }
        return path;
    }

工作得很好,尽管Daniel Kamil Kozar发现了一个我已经纠正的1个错误。该错误只会在格式不正确的绝对路径下显示,但是该函数仍应能够处理虚假输入。不要听取每个批评你的人的意见。有些人只是喜欢发表自己的看法,即使这些看法毫无价值。

我不喜欢strstr()的解决方案,因为如果文件名与路径中的目录名相同,则会失败,而且这种情况确实会发生,特别是在POSIX系统上,可执行文件通常没有扩展名,至少第一次是这样的,这意味着您必须进行多次测试和搜索定界符,使用strstr()更加麻烦,因为无法知道可能有多少个定界符。如果您想知道为什么一个人想要可执行文件的基本名称,请考虑busybox、egrep、fgrep等...

strrchar()的实现将会很麻烦,因为它搜索字符而不是字符串,所以我认为它不如这个解决方案那么可行或简洁。 我被Rad Lexus纠正了,这并不像我想象的那么麻烦,因为strrchar()具有返回找到的字符之后字符串的索引的副作用。

保重


5
我不同意"回到使用char*,绝对没有使用const的好理由”这句话。使用const是有一个非常好的理由的:为什么要修改传入函数的字符串?'const'是接口声明的重要组成部分: 我想你不希望告诉函数调用者“我将返回基本名称,但我可能在我喜欢的位置放置一些随机字节”。此外,因为'size_t'始终是'>=0',所以这是一个无限循环。 - Daniel Kamil Kozar
@JamieRRobillardSr。- i 的类型是 size_t,根据定义它是无符号的。因此,如果字符串不包含 /,该函数将永远不会返回。而且,你可以将 char* 传递给一个需要 const char* 的函数,而不需要在中间进行任何复制。 - Daniel Kamil Kozar
1
@JamieRRobillardSr.:虽然我不喜欢通过检查汇编输出来证明未定义行为的存在,但我认为这是唯一剩下的说服你的方法。使用gcc 6.3编译您的代码并查看标签.L5rdx在没有检查为零的情况下被减少,这意味着如果在i到达零之前没有/,它将被“减少”到SIZE_MAX(在这种特殊情况下为2^64-1)。这意味着您将使用负索引访问path,这是UB。请参考godbolt - Daniel Kamil Kozar
1
@JamieRRobillardSr.:我想这就解决了。谢谢。 - Daniel Kamil Kozar
1
@JamieRRobillardSr.,虽然我意识到这里有一些紧张的气氛,但我确实缺少了一些东西。如果循环“将在0处中断”,正如你所说,那么条件应该是“>= 0”,因此对于i == 0,条件仍然为真,并且执行至少还会继续一次。如果您知道我错过了什么,请解释一下。 - donjuedo
显示剩余11条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接