argv为什么要包含程序名称?


106

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

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


6
像MV和CP?
Archemar '16

9
在Debian上sh是的符号链接dash。当被称为sh或时,它们的行为有所不同dash
Motte001 '16

21
@AlexejMagura如果使用类似busybox(在救援盘等上很常见)的东西,则几乎所有内容(cp,mv,rm,ls,...)都是到busybox的符号链接。
Baard Kopperud '16

11
我发现这真的很难忽视,所以我会说:你大概的意思“GNU”计划(gccbashgunzip,大多数操作系统...的其余部分),由于Linux只是内核。
wizzwizz4 2016年

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

Answers:


122

首先,请注意argv[0]不一定是程序名称。这就是调用者argv[0]execve系统调用的处理(例如,在Stack Overflow上查看此问题)。(的所有其他变体exec不是系统调用,而是与的接口execve。)

例如,假设以下内容(使用execl):

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

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

设置所有的这一边:花哨的文件系统,如出现之前/procargv[0]是一个过程,了解了自己的名字的唯一途径。那有什么好处呢?

  • 多个程序根据调用它们的名称自定义其行为(通常通过符号链接或硬链接,例如BusyBox的实用程序;该问题的其他答案中提供了更多示例)。
  • 此外,通过syslog登录的服务,守护程序和其他程序通常在日志条目前加上名称。没有这个,事件跟踪将变得不可行。

18
这种方案的实例是bunzip2bzcat并且bzip2,为此,前两个是符号链接到第三之一。
Ruslan

5
有趣的zcat是,@ Ruslan 不是符号链接。他们似乎使用Shell脚本来避免使用该技术的弊端。但是它们无法打印出完整的--help输出,因为向gzip添加选项的人也忘记了维护zcat。
rudimeier '16

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

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

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

62

大量:

  • 击运行在POSIX模式argv[0]sh。当argv[0]以开头时,它将作为登录shell运行-
  • 当作为运行Vim的行为不同viviewevimeviewexvimdiff,等。
  • 如前所述,Busybox。
  • 在与systemd作为初始化系统shutdownreboot等等都是符号连接systemctl
  • 等等。

7
另一个是sendmailmail。每个unix MTA都为这两个命令提供了一个符号链接,并且被设计为模仿原始命令时的行为,这意味着需要发送邮件的任何unix程序都确切地知道它们如何做到这一点。
Shadur

4
另一种常见情况:testand [:调用前者时,如果最后一个参数是,它将处理错误]。(在实际的Debian稳定版上,这些命令是两个不同的程序,但是以前的版本和MacO仍使用相同的程序)。并且texlatex依此类推:二进制文件是相同的,但是查看它的调用方式,它选择了正确的配置文件。init很相似。
Giacomo Catenazzi

4
相关,[如果最后一个参数不是 ,则将其视为错误]
chepner '16

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

@Joey是的,其用语旨在传达这一点(问:“是否有任何……?” A:“大量:...”)
大师

34

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

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

编辑:Unix 1st版的参考,按要求

例如,可以从其主要功能中cc看到argcargv已被使用。该外壳拷贝参数传递给parbuf里面newarg的循环的一部分,而处理命令本身以同样的方式作为参数。(当然,稍后它仅执行第一个参数,即命令的名称)。看起来好像execv没有亲戚。


1
请添加支持此操作的参考。
lesmana

从快速略读,exec需要执行该命令的名称和字符指针的零封端的阵列(在最佳可见minnie.tuhs.org/cgi-bin/utree.pl?file=V1/u0.s,其中exec取对标签2和标签1的引用,在标签处2:出现etc/init\0,在标签处1:出现的对标签2的引用,以及一个终止的零),这基本上就是execve今天的减号envp
ninjalj

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

@ G-Man您能指出我链接到execv的v1源吗?只是好奇。
dirkt

22

用例:

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

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

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

setarchutil-linux中也使用了它:

$ 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

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

另一个用例是需要在运行时加载一些模块或数据的程序。有了程序路径,就可以从相对于程序位置的路径加载模块

而且,许多程序会打印错误消息,包括程序名称

为什么

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

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

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

如果argc的值大于零,则argv [0]指向的字符串表示程序名;如果程序名称在主机环境中不可用,则argv [0] [0]为空字符。

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


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

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

@rudimeier:您的“为什么”项目并不是真正的原因,它们只是“单子”,即只是在问为什么标准要求情况如此的问题。
einpoklum '16

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

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

21

除了程序根据调用方式改变其行为外,我发现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.

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

5

一个执行程序键入: program_name0 arg1 arg2 arg3 ...

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

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


4

基本上,argv包含程序名称,以便您可以编写诸如之类的错误消息prgm: file: No such file or directory,该消息将通过以下方式实现:

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

2

此程序的另一个应用示例是该程序,它将替换为...本身,直到您键入的不是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-人

0

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


2
这不是一个特别好的解释-我们没有理由不能对诸如此类的东西(char *path_to_program, char **argv, int argc)进行标准化
moopet 16/10/17

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

0

ccache的行为方式是为了模仿对编译器二进制文件的不同调用。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返回缓存的文件或将命令传递给实际的编译器。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.