【发布时间】:2011-08-24 18:17:27
【问题描述】:
Linux API/POSIX 中应该有一些优雅的东西可以从完整路径中提取基本文件名
【问题讨论】:
标签: c
Linux API/POSIX 中应该有一些优雅的东西可以从完整路径中提取基本文件名
【问题讨论】:
标签: c
或者在您的目标 UNIX/POSIX 系统上运行命令“man 3 basename”。
【讨论】:
使用basename(它有奇怪的极端情况语义)或通过调用strrchr(pathname, '/') 自己做,如果它不包含'/' 字符,则将整个字符串视为基本名称。
【讨论】:
这是一个单行示例(给定 char * whoami),它说明了基本算法:
(whoami = strrchr(argv[0], '/')) ? ++whoami : (whoami = argv[0]);
如果可能为 NULL,则需要进行额外检查。另请注意,这只是指向原始字符串——“strdup()”可能是合适的。
【讨论】:
如果您也对目录名称感兴趣,可以使用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);
【讨论】:
当然,如果这是 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;
}
【讨论】:
basename() 函数返回路径的最后一个组成部分,它可以是文件夹名而不是文件名。 basename() 函数有两个版本:GNU 版本和 POSIX 版本。
添加#define _GNU_SOURCE后,可以在string.h中找到GNU版本:
GNU 版本使用const 并且不修改参数。
char * basename (const char *path)
如果包含libgen.h,XPG (POSIX) 版本会覆盖此函数。
char * basename (char *path)
此函数可以通过删除尾随 '/' 字节来修改参数。在这种情况下,结果可能与 GNU 版本不同:
basename("foo/bar/")
如果您使用 XPG 版本,将返回字符串“bar”,如果您使用 GNU 版本,则返回空字符串。
参考资料:
【讨论】:
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++)
您可以将斜杠转义为反斜杠并使用以下代码:
#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;
}
【讨论】:
@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() 具有返回字符串索引超出找到的字符的副作用。
保重
【讨论】:
const 是接口声明的关键部分:我认为您不想告诉函数的调用者“我将返回基本名称,但我也可能在我喜欢的位置放置一些随机字节”。此外,这是一个无限循环,因为 size_t 始终是 >= 0。
.L5 : rdx 在没有被检查为零的情况下被递减,这意味着它将被“递减”到SIZE_MAX (2 ^64 - 1 在这种特殊情况下)如果在i 达到零之前不存在/。这意味着您将使用负索引(即 UB)访问 path。
我的例子(改进):
#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 *;误导性名称(结果不保证是文件)。
strrchr() 呢?
size_t 始终是>= 0。