典型的Unix / Linux程序接受命令行输入作为参数计数(int argc)和参数向量(char *argv[])。 argv的第一个元素是程序名称-后面是实际参数。

为什么将程序名称作为参数传递给可执行文件?是否有使用自己的名称的程序示例(可能是exec的情况)?

评论

像mv和cp一样?

在Debian上,sh是破折号的符号链接。当被称为sh或破折号时,它们的行为不同

@AlexejMagura如果您使用像busybox之类的东西(在救援盘等上很常见),那么几乎所有内容(cp,mv,rm,ls,...)都是到busybox的符号链接。

我发现这真的很难忽略,所以我要说:您可能是指“ GNU”程序(gcc,bash,gunzip,大多数其他OS ...),因为Linux只是内核。

@ wizzwizz4“典型的Unix / Linux程序”怎么了?我读起来像“在Unix / Linux上运行的典型程序”。这比您对某些GNU程序的限制要好得多。 Dennis Ritchie当然没有使用任何GNU程序。顺便说一句,Hurd内核是一个没有主要功能的GNU程序示例。

#1 楼

首先,请注意argv[0]不一定是程序名称。这就是调用方放入argv[0]系统调用的execve中的内容(例如,在堆栈溢出中查看此问题)。 (exec的所有其他变体不是系统调用,而是到execve的接口。)例如,假设以下内容(使用execl):

execl("/var/tmp/mybackdoor", "top", NULL);


/var/tmp/mybackdoor是执行的东西,但是argv[0]设置为top,这就是ps或(实数)top将显示的内容。有关此内容的更多信息,请参见U&L SE上的答案。

将所有这些放在一边:在出现诸如/proc之类的精美文件系统之前,argv[0]是进程了解其自身名称的唯一方法。这有什么用?


几个程序根据调用它们的名称自定义其行为(通常通过符号或硬链接,例如BusyBox的实用程序;还有几个示例)此外,通过syslog登录的服务,守护程序和其他程序通常将其名称放在日志条目的前面;没有这个,事件跟踪将变得不可行。


评论


这样的程序的示例是bunzip2,bzcat和bzip2,它们的前两个是到第三个的符号链接。

–俄罗斯
16-10-12在7:37

@Ruslan有趣的是zcat不是符号链接。他们似乎改用Shell脚本来避免这种技术的缺点。但是他们无法打印出完整的--help输出,因为向gzip添加选项的人也忘记了维护zcat。

– rudimeier
16-10-12在9:17

据我所知,GNU编码标准一直不鼓励使用argv [0]来更改程序行为(当前版本中的“通常用于接口的标准”部分)。 gunzip是一个历史例外。

–user41515
16-10-12在11:58



busybox是另一个很好的例子。可以使用308个不同的名称来调用它以调用不同的命令:busybox.net/downloads/BusyBox.html#commands

–佩平·施密兹(Pepijn Schmitz)
16-10-12在13:24

许多很多程序也将argv [0]注入到用法/帮助输出中,而不是对其名称进行硬编码。有些是完整的,有些只是基本名称。

–光谱
16-10-15在14:13

#2 楼

大量:


argv[0]sh时,Bash以POSIX模式运行。当argv[0]-开头时,它作为登录shell运行。
如前所述,当vi以viviewevimeviewexvimdiff等运行时,Vim的行为有所不同。
如前所述,Busybox。在将systemd设为init的系统中,shutdownreboot等是指向systemctl的符号链接。
等等。


评论


另一个是sendmail和mail。每个单独的unix MTA都带有针对这两个命令的符号链接,并且被设计为模拟原始命令时的行为,这意味着需要发送邮件的任何unix程序都确切地知道如何执行此操作。

– Shadur
16-10-12在9:51

另一个常见情况:test和[:调用前者时,如果最后一个参数为],它将处理错误。 (在实际的Debian稳定版上,这些命令是两个不同的程序,但以前的版本和MacO仍使用相同的程序)。与tex,latex等类似:二进制文件是相同的,但是在查看其调用方式后,它选择了正确的配置文件。初始化是相似的。

–贾科莫·卡泰纳兹(Giacomo Catenazzi)
16-10-12在19:25

相关,[如果最后一个参数不是],则认为它是错误。

–chepner
16-10-13在15:49

我想这回答了第二个问题,但没有回答第一个问题。我非常怀疑某些OS设计师坐下来说:“嘿,如果我有相同的程序仅基于可执行文件的名称执行不同的操作,那就太好了。我想我会在其参数数组中包含该名称。«

–乔伊
16-10-18在5:52

@Joey是的,其用语旨在传达这一点(问:“有没有……?” A:“有很多……”)

–muru
16-10-18在6:34

#3 楼

从历史上看,argv只是指向命令行中“单词”的指针的数组,因此从第一个“单词”开始就有意义,后者恰好是程序的名称。

根据调用它们的名称,有很多程序的行为有所不同,因此您可以创建指向它们的不同链接并获得不同的“命令”。我能想到的最极端的示例是busybox,它的作用类似于数十个不同的“命令”,具体取决于调用方式。

编辑:按要求提供Unix 1st Edition的引用

例如从cc的主要功能来看,已经使用了argcargv。 Shell将参数复制到循环parbuf部分内的newarg,同时以与参数相同的方式处理命令本身。 (当然,稍后它仅执行第一个参数,即命令的名称)。看来execv和亲戚当时不存在。

评论


请添加支持此内容的参考。

– Lesmana
16-10-12在10:24

通过快速浏览,exec提取了要执行的命令的名称和一个以0结尾的char指针数组(最好在minnie.tuhs.org/cgi-bin/utree.pl?file=V1/u0.s,其​​中查看) exec引用了标签2和标签1,在标签2:出现了etc / init \ 0,在标签1:出现了对标签2的引用,并且终止了零),这基本上是execve现在减去envp的结果。

– ninjalj
16-10-13在19:49

execv和execl“永远存在”(即,自1970年代初到中期)-execv是系统调用,而execl是调用它的库函数。 execve当时不存在,因为当时环境不存在。后来又增加了家庭的其他成员。

– G-Man说“恢复莫妮卡”
16-10-14在5:41

@ G-Man您能否在链接到的v1源中将我指向execv?只是好奇。

–dirkt
16-10-14在6:56

#4 楼

用例:

您可以使用程序名称来更改程序行为。

例如,您可以创建一些指向实际二进制文件的符号链接。

使用此技术的一个著名示例是busybox项目,该项目仅安装一个单一的二进制文件和许多符号链接。 (ls,cp,mv等)。他们这样做是为了节省存储空间,因为它们的目标是小型嵌入式设备。

util-linux在setarch中也使用了此方法:

$ ls -l /usr/bin/ | grep setarch
lrwxrwxrwx 1 root root           7 2015-11-05 02:15 i386 -> setarch
lrwxrwxrwx 1 root root           7 2015-11-05 02:15 linux32 -> setarch
lrwxrwxrwx 1 root root           7 2015-11-05 02:15 linux64 -> setarch
-rwxr-xr-x 1 root root       14680 2015-10-22 16:54 setarch
lrwxrwxrwx 1 root root           7 2015-11-05 02:15 x86_64 -> setarch


在这里,他们基本上是在使用这种技术来避免重复的源文件,或者只是为了使源更加可读。

另一个用例是需要在运行时加载一些模块或数据的程序。具有程序路径使您能够从相对于程序位置的路径加载模块。

此外,许多程序会打印错误消息,包括程序名称。

为什么:


,因为它是POSIX约定(man 3p execve):


argv是传递的参数字符串数组到新程序。按照约定,这些字符串中的第一个字符串应包含与正在执行的文件关联的文件名。



它是C标准(至少C99和C11):


如果argc的值大于零,则argv [0]指向的字符串表示程序名称;如果无法从主机环境获得程序名称,则argv [0] [0]为空字符。


请注意,C标准说“程序名称”而不是“文件名”。

评论


如果您从另一个符号链接到达符号链接,这不会中断吗?

–user541686
16-10-12在6:55



@Mehrdad,是的,这是缺点,可能会使用户感到困惑。

– rudimeier
16-10-12在7:30

@rudimeier:您的“为什么”项目并不是真正的原因,它们只是“同质”,即,它只是提出了一个问题,即为什么该标准要求情况如此?

– einpoklum
16-10-15在11:10

@einpoklum OP的问题是:为什么将程序名称传递给可执行文件?我回答:因为POSIX和C标准要求我们这样做。您怎么认为这不是真正的原因?如果我引用的文档不存在,那么可能很多程序都不会通过程序名称。

– rudimeier
16-10-15在23:35

OP实际上在问:“为什么POSIX和C标准说要这样做?”当然,措词是抽象的,但似乎很明确。实际上,唯一的了解方法是询问发起者。

–user2338816
16-10-17在0:30

#5 楼

除了程序根据调用方式改变其行为外,我发现argv[0]对于打印程序的用法也很有用,例如:

printf("Usage: %s [arguments]\n", argv[0]);


这会导致用法消息,始终使用被调用的名称。如果重命名该程序,则其用法消息也会随之更改。它甚至包括它被称为的路径名:

# cat foo.c 
#include <stdio.h>
int main(int argc, char **argv) { printf("Usage: %s [arguments]\n", argv[0]); }
# gcc -Wall -o foo foo.c
# mv foo /usr/bin 
# cd /usr/bin 
# ln -s foo bar
# foo
Usage: foo [arguments]
# bar
Usage: bar [arguments]
# ./foo
Usage: ./foo [arguments]
# /usr/bin/foo
Usage: /usr/bin/foo [arguments]


这是一个很好的联系,特别是对于可能在各处使用的小型专用工具/脚本。

这在GNU工具中也很常见,例如参见ls

% ls --qq
ls: unrecognized option '--qq'
Try 'ls --help' for more information.
% /bin/ls --qq
/bin/ls: unrecognized option '--qq'
Try '/bin/ls --help' for more information.


评论


+1。我打算提出同样的建议。奇怪的是,如此多的人专注于改变行为,却没有提到最明显和更广泛的用法。

– Vee
16-10-13在11:51

#6 楼

一个执行程序键入以下内容:
program_name0 arg1 arg2 arg3 ...

因此,外壳程序应该已经分割了令牌,并且第一个令牌已经是程序名称。顺便说一句,所以在程序端和外壳程序上都有相同的索引。

我认为这只是一个方便的窍门(从一开始),正如您在其他答案中所看到的那样非常方便,因此这一传统得以延续并设置为API。

#7 楼

基本上,argv包含程序名称,以便您可以编写类似以下内容的错误消息prgm: file: No such file or directory

    fprintf( stderr, "%s: %s: No such file or directory\n", argv[0], argv[1] );


#8 楼

此程序的另一个应用示例是该程序,它将替换为...本身,直到您键入非y为止。

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

int main (int argc, char** argv) {

  (void) argc;

  printf("arg: %s\n", argv[1]);
  int count = atoi(argv[1]);

  if ( getchar() == 'y' ) {

    ++count;

    char buf[20];
    sprintf(buf, "%d", count);

    char* newargv[3];
    newargv[0] = argv[0];
    newargv[1] = buf;
    newargv[2] = NULL;

    execve(argv[0], newargv, NULL);
  }

  return count;
}


显然一个人为的,有趣的示例,但我认为它可能有实际用途-例如,一个自我更新的二进制文件,它使用下载或更改的自身新版本来重写其自身的内存空间。

示例:

$ ./res 1
arg: 1
y
arg: 2
y
arg: 3
y
arg: 4
y
arg: 5
y
arg: 6
y
arg: 7
n

7 | $


来源以及更多信息。

评论


恭喜您达到1000。

– G-Man说“恢复莫妮卡”
16-10-14在5:36

#9 楼

该程序的路径为argv[0],以便该程序可以从其安装目录中检索配置文件等。
如果没有argv[0],这是不可能的。

评论


这不是一个很好的解释-例如,没有理由我们无法对诸如(char * path_to_program,char ** argv,int argc)之类的东西进行标准化

– moopet
16-10-17在10:46

Afaik,大多数程序从标准位置(〜/。<程序>,/ etc / <程序,$ XDG_CONFIG_HOME)中提取配置,并采用参数对其进行更改,或者具有将常量常量烘焙为二进制文件的编译时选项。

–熊佳亚诺夫
16-10-17在18:32

#10 楼

ccache的行为方式是为了模仿对编译器二进制文件的不同调用。 ccache是​​一个编译缓存-整个过程永远不要两次编译相同的源代码,而是尽可能从缓存中返回目标代码。

在ccache手册页中,“有两种使用方法您可以在编译命令前添加ccache前缀,也可以通过创建指向ccache的符号链接(命名为编译器)让ccache冒充编译器。如果您只是想尝试ccache或希望使用,第一种方法最方便以便将其用于某些特定项目。当您希望对所有编译使用ccache时,第二种方法最有用。“

symlinks方法涉及运行以下命令:

cp ccache /usr/local/bin/
ln -s ccache /usr/local/bin/gcc
ln -s ccache /usr/local/bin/g++
ln -s ccache /usr/local/bin/cc
ln -s ccache /usr/local/bin/c++
... etc ...


...的作用是允许ccache截取原本应交给编译器的任何命令,从而使ccache返回缓存文件或将命令传递给实际编译器。