【问题标题】:How to extract filename from path如何从路径中提取文件名
【发布时间】:2011-08-24 18:17:27
【问题描述】:

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

【问题讨论】:

    标签: c


    【解决方案1】:

    char *basename(char *path)

    或者在您的目标 UNIX/POSIX 系统上运行命令“man 3 basename”。

    【讨论】:

      【解决方案2】:

      使用basename(它有奇怪的极端情况语义)或通过调用strrchr(pathname, '/') 自己做,如果它不包含'/' 字符,则将整个字符串视为基本名称。

      【讨论】:

        【解决方案3】:

        这是一个单行示例(给定 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);
          

          【讨论】:

            【解决方案5】:

            当然,如果这是 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;
            }
            

            【讨论】:

              【解决方案6】:

              basename() 函数返回路径的最后一个组成部分,它可以是文件夹名而不是文件名。 basename() 函数有两个版本:GNU 版本和 POSIX 版本。

              添加#define _GNU_SOURCE后,可以在string.h中找到GNU版本:

              #define _GNU_SOURCE #include

              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 手册页
                Function: char * basename (const char *filename),在字符串中查找标记。

              【讨论】:

                【解决方案7】:
                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 * 文件名 = getFileNameFromPath ( 文件路径 );

                (这是一个 c++)

                【讨论】:

                • 欢迎来到 Stack Overflow!你应该注意到这个问题被标记为c,而你的答案在c++..
                【解决方案8】:

                您可以将斜杠转义为反斜杠并使用以下代码:

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

                【讨论】:

                  【解决方案9】:

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

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

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

                  函数名很长但描述性强,没有问题。没有办法确保函数会返回文件名,只有在函数编码正确的情况下才能确保它可以,这是你实现的。虽然如果有人在一个不是路径的字符串上使用它显然会失败。我可能会将其命名为 basename,因为它将向许多程序员传达其目的是什么。这只是我的偏好,尽管基于我的偏见,你的名字很好。至于这个函数将处理的字符串的长度,为什么有人认为这很重要?您处理的路径名不太可能比此函数在 ANSI C 编译器上可以处理的路径名长。因为 size_t 被定义为一个 unsigned long int,其范围为 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 确实发现了一个我在上面更正的错误。该错误只会显示格式错误的绝对路径,但该函数仍然应该能够处理虚假输入。不要听每个批评你的人。有些人就是喜欢发表意见,即使它毫无价值。

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

                  strrchar() 执行起来会很麻烦,因为它搜索的是字符而不是字符串,所以我发现它不像这个解决方案那样可行或简洁。 我接受 Rad Lexus 的纠正,这不会是正如我认为的那样麻烦 strrchar() 具有返回字符串索引超出找到的字符的副作用。

                  保重

                  【讨论】:

                  • 我不能同意“回到使用 char *,绝对没有充分的理由使用 const.”这句话。使用 const 绝对有一个很好的理由:你为什么还要修改传递给这个函数的字符串呢? const 是接口声明的关键部分:我认为您不想告诉函数的调用者“我将返回基本名称,但我也可能在我喜欢的位置放置一些随机字节”。此外,这是一个无限循环,因为 size_t 始终是 &gt;= 0
                  • ".. strrchar()" (sic) "... 实现起来会很麻烦,因为它搜索的是字符而不是字符串 .." 但您正在搜索单个字符。事实,而不是意见。
                  • @JamieRRobillardSr。 :尽管我不喜欢通过检查程序集输出来证明存在未定义行为,但我认为这是说服你的唯一剩余方法。看看godbolt 使用gcc 6.3 编译你的代码,标签.L5 : rdx 在没有被检查为零的情况下被递减,这意味着它将被“递减”到SIZE_MAX (2 ^64 - 1 在这种特殊情况下)如果在i 达到零之前不存在/。这意味着您将使用负索引(即 UB)访问 path
                  • @JamieRRobillardSr。 : 我觉得这样就解决了。谢谢。
                  • @JamieRRobillardSr.,虽然我认识到这里的简洁紧张,但我真的错过了一些东西。如果循环“将为 0 中断”,如您所说,条件读取 ">= 0",因此对于 i == 0,条件仍然为真,并且执行至少再继续一次。如果您看到我遗漏了什么,请解释一下。
                  【解决方案10】:

                  我的例子(改进):

                      #include <string.h>
                  
                      const char* getFileNameFromPath(const char* path, char separator = '/')
                      {
                          if(path != nullptr)
                          {
                              for(size_t i = strlen(path);  i > 0; --i)
                              {
                                  if (path[i-1] == separator)
                                  {
                                      return &path[i];
                                  }
                              }
                          }
                      
                          return path;
                      }
                  

                  【讨论】:

                  • 不处理很长的字符串;可能应该采用const char *,而不是char *;误导性名称(结果不保证是文件)。
                  • @melpomene:非常感谢您的评论,功能已调整。但是根据命名说明,我想您应该另外检查给定路径是否是文件,以防万一。
                  • strrchr() 呢?
                  • 这是一个无限循环,因为size_t 始终是&gt;= 0
                  • @Jamie R Robillard Sr.:感谢建设性的 cmets 和想法!
                  猜你喜欢
                  • 2011-04-28
                  • 1970-01-01
                  • 2010-10-01
                  • 2016-06-19
                  • 2013-08-19
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多