__FILE__宏显示完整路径

217
标准预定义宏__FILE__在C语言中显示文件的完整路径。有没有办法缩短路径并只获取文件名?我的意思是,不要显示完整路径,只显示文件名。
/full/path/to/file.c

我明白了。
to/file.c

或者

file.c

34
希望找到一个仅使用预处理器的解决方案将非常好。我担心基于字符串操作的建议将在运行时执行。 - cdleonard
9
因为你正在使用gcc,所以我认为你可以通过更改传递给命令行的文件名来改变__FILE__的内容。因此,不要使用gcc /full/path/to/file.c,而是尝试cd /full/path/to; gcc file.c; cd -;。当然,如果你依赖于gcc的当前目录来设置包含路径或输出文件位置,则需要更多的修改。编辑:gcc文档表明它是完整路径,而不是输入文件名参数,但在Cygwin上的gcc 4.5.3中,我看到的并非如此。因此,你可以在Linux上尝试一下。 - Steve Jessop
4
GCC 4.5.1(专为arm-none-eabi构建)在其命令行上使用文件名的确切文本。在我的情况下,这是IDE的问题,因为它以所有文件名都被完全限定的方式调用GCC,而不是将当前目录放在某个合理的位置(例如项目文件的位置?)或可配置并从那里使用相对路径。我怀疑很多IDE都会这样做(尤其是在Windows上),出于某种与解释“当前”目录在GUI应用程序中真正位置有关的不适感。 - RBerteig
3
@SteveJessop - 希望您能看到这条评论。 我遇到了一种情况,其中__FILE__被打印为../../../../../../../../rtems/c/src/lib/libbsp/sparc/leon2/../../shared/bootcard.c,我想知道gcc编译文件的位置,以便该文件相对于所示的位置定位。 - Chan Kim
2
这个问题并不是与链接中的问题重复。首先,链接中的问题是关于C++的,因此答案涉及到了C++宏奥秘。其次,在OP的问题中没有强制要求使用宏解决方案。它只是郑重指出一个问题,并提出了一个开放式问题。 - Prof. Falken
显示剩余4条评论
31个回答

2
在MSVC中,将FILENAME宏添加为FILENAME=%(FileName)%(Extension)到C++项目的预处理器定义中。恐怕这完全是一个编译器杀手。不知何故,它会破坏并行构建。

问题使用“/”作为路径分隔符,这表明使用的编译器不是Windows。我怀疑@mahmood没有使用MSVC;一些评论指出他在使用gcc,但我找不到他说过的地方。 - Ramón Gil Moreno

2
如果您进入此页面是为了寻找一种方法,以从您正在发货的二进制文件中删除指向丑陋构建位置的绝对源路径,则下面的内容可能适合您的需求。
虽然这并不完全符合作者的愿望,因为它假定使用了CMake,但它非常接近。很遗憾之前没有人提到过这一点,否则我会节省很多时间。
OPTION(CMAKE_USE_RELATIVE_PATHS "If true, cmake will use relative paths" ON)

将上述变量设置为ON将生成格式为的构建命令: "最初的回答"。
cd /ugly/absolute/path/to/project/build/src && 
    gcc <.. other flags ..> -c ../../src/path/to/source.c

作为结果,__FILE__宏将解析为../../src/path/to/source.c 请注意文档页面上的警告:

使用相对路径(可能无法正常工作!)

虽然不能保证在所有情况下都能正常工作,但在我的情况下可以使用 - CMake 3.13 + gcc 4.5。
原始答案: CMake documentation

1
CMake 3.4+ 文档指出:“此变量不起作用。在之前的版本中部分实现的效果已在 CMake 3.4 中删除。” 如果您在 3.13 中使用它有效,那么就有其他原因。 - mike.dld

2
这是一个可在Linux(路径'/')和Windows(混合使用'\'和'/')上运行的便携式函数。
可使用gcc、clang和vs进行编译。
#include <string.h>
#include <stdio.h>

const char* GetFileName(const char *path)
{
    const char *name = NULL, *tmp = NULL;
    if (path && *path) {
        name = strrchr(path, '/');
        tmp = strrchr(path, '\\');
        if (tmp) {
             return name && name > tmp ? name + 1 : tmp + 1;
        }
    }
    return name ? name + 1 : path;
}

int main() {
    const char *name = NULL, *path = NULL;

    path = __FILE__;
    name = GetFileName(path);
    printf("path: %s, filename: %s\n", path, name);

    path ="/tmp/device.log";
    name = GetFileName(path);
    printf("path: %s, filename: %s\n", path, name);

    path = "C:\\Downloads\\crisis.avi";
    name = GetFileName(path);
    printf("path: %s, filename: %s\n", path, name);

    path = "C:\\Downloads/nda.pdf";
    name = GetFileName(path);
    printf("path: %s, filename: %s\n", path, name);

    path = "C:/Downloads\\word.doc";
    name = GetFileName(path);
    printf("path: %s, filename: %s\n", path, name);

    path = NULL;
    name = GetFileName(NULL);
    printf("path: %s, filename: %s\n", path, name);

    path = "";
    name = GetFileName("");
    printf("path: %s, filename: %s\n", path, name);

    return 0;
}

标准输出:

path: test.c, filename: test.c
path: /tmp/device.log, filename: device.log
path: C:\Downloads\crisis.avi, filename: crisis.avi
path: C:\Downloads/nda.pdf, filename: nda.pdf
path: C:/Downloads\word.doc, filename: word.doc
path: (null), filename: (null)
path: , filename: 

2

适用于Windows和*nix的简短、可行的答案:

#define __FILENAME__ std::max<const char*>(__FILE__,\
    std::max(strrchr(__FILE__, '\\')+1, strrchr(__FILE__, '/')+1))

为什么你需要使用 std::max<const char*> 而不是只用 std::max - chqrlie
好的解决方案,除了std::max需要在头文件中包含<algorithm>之外,我宁愿不这样做。另一个选项是通过#ifdef _WIN32检查平台,因为__FILE__将具有一致的分隔符字符。 - Burak
1
@Burak,然后任何其他简单的“max”函数都可以。当然,#define PATH_SEPARATOR也会有所帮助。 - Luc Bloom

1
以下是一种适用于没有字符串库的环境(如Linux内核、嵌入式系统等)的解决方案:
#define FILENAME ({ \
    const char* filename_start = __FILE__; \
    const char* filename = filename_start; \
    while(*filename != '\0') \
        filename++; \
    while((filename != filename_start) && (*(filename - 1) != '/')) \
        filename--; \
    filename; })

现在只需要使用 FILENAME 替代 __FILENAME__。是的,它仍然是一个运行时的东西,但它有效。

根据不同的平台,可能需要检查 '/' 和 '' 两个字符。 - Technophile

0

由于您正在使用GCC,因此您可以利用以下内容:

__BASE_FILE__ 这个宏会展开为一个C字符串常量形式的主输入文件名。这是在预处理器或C编译器命令行中指定的源文件。

然后,通过在编译时更改源文件表示(完整路径/相对路径/基本名称),您可以控制如何显示文件名。


6
没有区别。我使用了__FILE____BASE_FILE__,但它们都显示文件的完整路径。 - mahmood
你如何调用编译器? - ziu
2
那么我打赌SCONS是这样调用gcc的:gcc /absolute/path/to/file.c。如果你找到了改变这种行为的方法(在SO上开另一个问题,哈哈),你就不需要在运行时修改字符串。 - ziu
19
这个回答是完全错误的。正如文档所说(虽然不够清晰),__BASE_FILE__ 生成在命令行上指定的文件名,例如 test.c/tmp/test.c 取决于你如何调用编译器。这与 __FILE__ 完全相同,除非你在头文件中,在这种情况下,__FILE__ 会产生当前文件的名称(例如 foo.h),而 __BASE_FILE__ 仍会产生 test.c - Quuxplusone

0

顶部的答案不够好,因为它不是编译时常量表达式。这里有一个非常简单的解决方案:

 #define FILESTEM(x)                                                        \
  std::string_view(x).substr(std::string_view(x).rfind(OS_PATH_SLASH) + 1, \
                             std::string_view(x).rfind('.') -              \
                                 std::string_view(x).rfind(OS_PATH_SLASH) - 1)

这是一个constexpr,可以在头文件中使用。


这是一个C++解决方案。OP在'C'环境中提出了问题。这对于'C'程序无效。 - Brad

0

我认为这比使用strrchr函数更好。 strfnchr将搜索最后一个分隔符'/'并从__FILE__中获取文件名, 您可以使用__FILE__NAME__而不是__FILE__来获取没有完整文件路径的文件名。 strrchr解决方案每次使用都会搜索文件名两次,但是此代码只需搜索1次。 即使在__FILE__中没有分隔符'/',它也可以有效地工作。 您可以根据需要将其替换为\以使用它。 通过使用下面的strrchr源代码改进了strfnchr的源代码。我认为它比strrchr更有效。

https://code.woboq.org/userspace/glibc/string/strrchr.c.html
inline const char* strfnchr(const char* s, int c) {
  const char* found = s;
  while (*(s++)) {
    if (*s == c)
      found = s;
  }
  if (found != s)
    return found + 1;
  return s;
}

#define __FILE_NAME__ strfnchr(__FILE__, '/')

3
请不要仅仅发布代码作为答案,还要提供解释您的代码是如何解决问题的。带有解释的答案通常更有帮助和更高质量,并且更有可能吸引赞同。 - Mark Rotteveel

0
#include <algorithm>
#include <string>
using namespace std;
string f( __FILE__ );
f = string( (find(f.rbegin(), f.rend(), '/')+1).base() + 1, f.end() );

// searches for the '/' from the back, transfers the reverse iterator 
// into a forward iterator and constructs a new sting with both

0

一个经过调整的、更加“臃肿”的版本,来自red1ynx的回答:

#define __FILENAME__ \
  (strchr(__FILE__, '\\') \
  ? ((strrchr(__FILE__, '\\') ? strrchr(__FILE__, '\\') + 1 : __FILE__)) \
  : ((strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__)))

如果我们发现反斜杠,我们就在反斜杠上分割。否则,在正斜杠上分割。非常简单。
任何替代方案都会更加清晰(在我看来,C++的constexpr真的是黄金标准)。然而,如果你正在使用一些编译器,其中__BASE_FILE__不可用,这可能会有所帮助。

为什么应该先搜索反斜杠?如果 __FILE__ 包含了混合分隔符,例如 c:\abc\xyz/file.h,那么宏就无法工作。 - insaneinvader

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