一个打印出其可执行文件名的 C 语言程序

6
假设源代码文件名为test.cpp。编译后,它将生成test.exe文件。执行时,它应该能够识别自己的文件名test.exe并打印输出。
以下代码可以获取当前目录中所有文件和目录的列表:
DIR * directory;
struct dirent * direct;
direct = readdir(directory);

但是,在这种情况下,我该如何识别关联的文件名,即 "test.exe"


2
为什么你的C源代码文件扩展名是C++的?你知道这两种语言并不相同吗? - crashmstr
7
当前目录不一定是可执行文件所在的位置,大多数情况下它距离可执行文件很远。 - molbdnilo
1
重新打开,因为它不是重复项。我相当确定有重复项存在,但这应该是正确的。 - Lundin
1
一般情况下不可能。程序在执行时可能没有任何文件名(虽然很奇怪,但至少在Linux上是可能的)。 - Basile Starynkevitch
我很惊讶这还没有被提到:https://dev59.com/mHNA5IYBdhLWcg3wS7sm 这个问题已经足够接近,可能已经被关闭为重复问题了。 - Andrew Henle
显示剩余2条评论
5个回答

18

在你的主函数中,argv[0] 是从命令行输入的可执行文件的名称

#include <stdio.h>
int main(int argc, char ** argv)
{
    printf("%s", argv[0]);
    return 0;
}

演示

这会打印出命令名,即相对于当前工作目录的目录以及可执行文件名(如果有的话,不保证一定存在)。要获取当前工作目录,请使用 getcwd() 标准库 C 函数。

argv[0] 中提取命令路径中的文件名是平台特定的:Unix 使用斜杠 '/',Windows 允许混合使用斜杠 / 和反斜杠 \,其他任何平台都可以使用任何其他路径分隔符。从路径中提取文件名需要跨平台库,例如 Qt 或 Boost。在 POSIX 环境中,可以使用 basename。


3
argv[0] 表示你的命令,不一定是可执行文件的名称。 - Jonatan Goebel
@JonatanGoebel:如果是符号链接,就返回true(你看到其他情况可能不同吗?) - galinette
1
@galinette 请参见https://dev59.com/SnI-5IYBdhLWcg3wBjhR - Peter M
1
argv[0] 可以是任何东西。Shell 将用户输入的命令作为 argv[0] 传递。它可能与文件名有关,也可能与文件名无关,这取决于 Shell。通常情况下,运行其他程序的程序可以自由地将任何内容作为 argv 传递。 - n. m.
getcwd() 不是标准的 C 库,它只适用于 Linux 系统。 - Haseeb Mir
显示剩余2条评论

13
#include <stdio.h>

int main(int argc, char *argv[])
{
    printf("%s\n", argv[0]);
    return 0;
}
请注意,您的程序可以作为以下方式启动:
/home/user/./app

在这种情况下,您可以使用strrchr来获取名称:

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

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

    appname = strrchr(argv[0], '/'); /* '\\' on Windows */
    printf("%s\n", appname ? ++appname : argv[0]);
    return 0;
}

你听说过 basename 吗? - Jonatan Goebel
@galinette,是POSIX的一部分。 - David Ranieri
无法在有反斜杠的 Windows 下工作。实际上,由于路径是一个平台特定的问题,如果没有像Qt(Boost?)这样的平台抽象库,就不能简单地回答。 - galinette
@AlterMann:你为什么说它会找到字符串的最后一个出现位置?因为反斜杠是C语言的转义字符,所以需要进行转义。 - galinette
@AlterMann,请检查我的答案。 - Grijesh Chauhan
显示剩余3条评论

7
当你正在构建可执行文件时,你知道它的名称;最简单的解决方案是将其嵌入到程序中,使用-D/D选项在命令行上定义一个宏即可。
除此之外,普遍的回答是不可能实现:
引用如下:

根据标准

  • argv[0]应包含用于调用程序的名称(无论这意味着什么)。 这很好,但1)在Unix下甚至无法实现,2)在大多数系统下,有各种别名使得用于调用程序的名称与可执行文件的名称毫无关系。

在Windows下

  • 有一个系统函数GetModuleFileName,可以用来获取可执行文件的路径。 一旦您拥有了路径,路径的最后一个元素就是您的可执行文件的名称。

在Unix下

  • 这是根本不可能的。 在启动新进程时,Unix需要为可执行文件的路径和最终出现在argv [0]中的参数分别提供独立的参数,因此它们潜在地互不相关。 这完全取决于谁启动了您的进程。 bash 将可执行文件的完整路径放在环境变量"_"中,因此您可以使用getenv来获取它。但这仅适用于您的程序是由bash启动的情况。 在大多数Unix系统上,还可以在/proc文件系统中找到它,如果您知道路径的话; 但这种组织方式各不相同。 请注意,由于硬链接,可执行文件可能没有一个名称。
真正的问题是为什么你想这样做。 你试图解决什么问题?

你有水晶球吗?我永远不会猜到这就是 OP 需要的。对我来说,这显然是一个运行时问题。 - Jonatan Goebel
@JonatanGoebel OP 问如何获取可执行文件的名称。他可能在想运行时,但是名称通常已知于构建过程,因此可以在构建时确定。(OP 没有指定他试图实现什么,所以很难说这个解决方案是否能解决问题——我实际上对此表示怀疑,因为我想不出需要这种知识的问题。但这就是他要求的。) - James Kanze

4

在Linux上特别注意:

您可能会使用proc(5)/proc/self/exe符号链接,因此请使用readlink(2)。完成后,您可以对所获得的符号链接使用basename(3)realpath(3)

然而,请注意程序可能并不总是有一个文件路径。它可能有 多个 文件路径(例如,/bin/rbash 是指向 /bin/bash 的符号链接,当作为 rbashbash 调用时,Shell 进程的行为不同)。有时一个给定的文件(实际上是 inode,请参见inode(7))有 多个 硬链接。在奇怪的情况下可能没有硬链接。同时,在程序被 execve(2) 启动后,可能会使用 unlink(2) 移除该程序 (也许是由于其他进程计划在你的进程之前运行等原因)。

(BTW,你的问题是特定于操作系统的。readdir(3)是POSIX标准,而有些操作系统甚至没有目录,并且C11标准中没有提到readdir;通过阅读n1570来进行检查)
我尝试了以下(病态的)./selfremove程序(在我的/home/basile/tmp/目录中),它会删除自己的二进制文件:
 // file selfremove.c
 #include <stdio.h>
 #include <stdlib.h>
 #include <unistd.h>
 #include <string.h>

 int
 main (int argc, char **argv)
 {
   char selfpath[128];
   memset (selfpath, 0, sizeof (selfpath));
   if (readlink ("/proc/self/exe", selfpath, sizeof (selfpath) - 1) < 0)
     {
       perror ("first readlink");
       exit (EXIT_FAILURE);
     };
   printf ("initial /proc/self/exe -> %s\n", selfpath);
   if (unlink (argv[0]))
     {
       fprintf (stderr, "unlink %s: %m\n", argv[0]);
       exit (EXIT_FAILURE);
     };
   printf ("%s unlinked\n", argv[0]);
   if (readlink ("/proc/self/exe", selfpath, sizeof (selfpath) - 1) < 0)
     {
       perror ("second readlink");
       exit (EXIT_FAILURE);
     };
   printf ("final /proc/self/exe -> %s\n", selfpath);
   return 0;
 }    

它可以工作,内核正在构建一个 * (已删除) 符号链接(因为一些恶意程序员可能会将可执行文件重命名为 selfremove (已删除),所以内核添加的后缀只是一个指示……):

 % ./selfremove 
 initial /proc/self/exe -> /home/basile/tmp/selfremove
 ./selfremove unlinked
 final /proc/self/exe -> /home/basile/tmp/selfremove (deleted)

所以即使使用 /proc/self/exe,您也不能始终信任结果。
如果你假设你的程序已经通过某个 shell(或类似执行 execvp(3) 的程序)被执行了 - 这并不总是正确的情况 - 那么 PATH 变量 有可能被使用(如果它没有 /,那么会从主函数的 argv[0] 中搜索)。你可以使用 getenv(3) 函数来获取它,例如 getenv("PATH"),从你的环境中获取(请参见 environ(7) 获取更多信息)。通常会设置和使用该变量,但也有一些特殊情况。

通常情况下,没有可靠的方法打印自己的可执行文件(正如我的病态selfremove.c所示)。在大多数情况下,您可以找到它(例如通过/proc/self/exereadlink或使用argv [0]$PATH中搜索)。


3
在Linux系统中,我认为您也可以使用basename(char *path);函数来自libgen.h库。尝试运行$ man basename命令:

打印已删除任何前导目录组件的NAME。如果指定,还将删除尾部的SUFFIX。

我尝试了如下代码:
// filename.c
#include<stdio.h>
#include<stdlib.h>
#include<libgen.h>
int main(int argc, char* argv[]){
    char* exe_name = basename(argv[0]);
    printf(" Executable Name: %s", exe_name);
    printf("\n");
    return EXIT_SUCCESS;
}

观察,编译并测试如下:

taxspanner@:~$ ls filename.c
filename.c
taxspanner@:~$ gcc -std=gnu99 -Wall -pedantic filename.c -o filename
taxspanner@:~$ ls filename*
filename  filename.c
taxspanner@:~$ 

现在在当前目录下运行它:
taxspanner@:~$ ./filename 
 Executable Name: filename

使用绝对路径:
taxspanner@:~$ pwd
/home/taxspanner
taxspanner@:~$ /home/taxspanner/filename 
 Executable Name: filename

相对路径:

taxspanner@:~$ cd study/divide-5/
taxspanner@:~/study/divide-5$ pwd
/home/taxspanner/study/divide-5
taxspanner@:~/study/divide-5$ ls ../../filename*
../../filename  ../../filename.c
taxspanner@:~/study/divide-5$ ../../filename
 Executable Name: filename
taxspanner@:~/study/divide-5$ 

尝试一次带有后缀的可执行文件名:

taxspanner@:~$ gcc -std=gnu99 -Wall -pedantic filename.c -o filename.out
taxspanner@:~$ ./filename.out 
 Executable Name: filename.out
taxspanner@:~$ cd study/divide-5/
taxspanner@:~/study/divide-5$ ../../filename.out 
 Executable Name: filename.out

使用时要小心:无论是dirname()还是basename()都可能修改路径的内容,因此在调用这些函数时最好传递一个副本

试一试吧!!


很好的解释!我之前不知道可以使用program*来启动相对路径下的程序。 - David Ranieri
1
@AlterMann 抱歉,我的回答中有个打字错误。对于相对路径,我们不需要使用 *,我只是使用了 ../../filename - Grijesh Chauhan

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