Pourquoi argv inclut-il le nom du programme?

106

Les programmes Unix / Linux typiques acceptent les entrées de ligne de commande sous la forme d'un nombre d'arguments ( int argc) et d'un vecteur d'arguments ( char *argv[]). Le premier élément argvest le nom du programme, suivi des arguments réels.

Pourquoi le nom du programme est-il passé à l'exécutable en tant qu'argument? Existe-t-il des exemples de programmes utilisant leur propre nom (peut-être une sorte de execsituation)?

Shrikant Giridhar
la source
6
comme mv et cp?
Archemar
9
On Debian shest un lien symbolique vers dash. Ils se comportent différemment, lorsqu'ils sont appelés en tant shquedash
Motte001
21
@AlexejMagura Si vous utilisez quelque chose comme busybox(commun sur les disques de secours et autres), alors à peu près tout (cp, mv, rm, ls, ...) est un lien symbolique vers busybox.
Baard Kopperud
11
Je trouve cela vraiment difficile d'ignorer, donc je vais le dire: vous voulez dire probablement des programmes « GNU » ( gcc, bash, gunzip, la plupart du reste du système d' exploitation ...), comme Linux est que le noyau.
wizzwizz4
10
@ wizzwizz4 Qu'est-ce qui ne va pas avec les "programmes Unix / Linux typiques"? Je l'ai lu comme "Programmes typiques fonctionnant sous Unix / Linux". C'est beaucoup mieux que votre restriction à certains programmes GNU. Dennis Ritchie n’utilisait certainement aucun programme GNU. BTW le noyau Hurd est un exemple de programme GNU qui n’a pas de fonction principale ...
rudimeier

Réponses:

122

Pour commencer, notez que ce argv[0]n'est pas nécessairement le nom du programme. C'est ce que l'appelant met dans argv[0]l' execveappel système (par exemple, voir cette question sur le dépassement de capacité de la pile ). (Toutes les autres variantes de execne sont pas des appels système, mais des interfaces avec execve.)

Supposons, par exemple, ce qui suit (utiliser execl):

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

/var/tmp/mybackdoorest ce qui est exécuté mais argv[0]réglé sur top, et c’est ce que psou (le réel) topafficherait. Voir cette réponse sur U & L SE pour plus d'informations.

Mettre tout cela de côté: Avant l’avènement de systèmes de fichiers sophistiqués /proc, argv[0]c’était le seul moyen pour un processus d’en savoir plus sur son propre nom. Qu'est-ce que ce serait bon?

  • Plusieurs programmes personnalisent leur comportement en fonction du nom sous lequel ils ont été appelés (généralement par des liens symboliques ou physiques, par exemple les utilitaires de BusyBox ; plusieurs autres exemples sont fournis dans d'autres réponses à cette question).
  • De plus, les services, démons et autres programmes qui se connectent via syslog ajoutent souvent leur nom aux entrées du journal. sans cela, le suivi des événements deviendrait presque infaisable.
contre mode
la source
18
Des exemples de tels programmes sont bunzip2, bzcatet bzip2, pour lesquels les deux premiers sont des liens symboliques vers le troisième.
Ruslan
5
@Ruslan Fait intéressant, ce zcatn'est pas un lien symbolique. Ils semblent éviter les inconvénients de cette technique en utilisant plutôt un script shell. Mais ils ne parviennent pas à imprimer une --helpsortie complète car quelqu'un qui a ajouté des options à gzip a également oublié de maintenir zcat.
Rudimeier
1
Pour autant que je m'en souvienne, les normes de codage GNU ont découragé l'utilisation de argv [0] pour modifier le comportement du programme ( section "Normes pour les interfaces en général" dans la version actuelle ). gunzipest une exception historique.
19
busybox est un autre excellent exemple. Il peut être appelé par 308 noms différents pour appeler différentes commandes: busybox.net/downloads/BusyBox.html#commands
Pepijn Schmitz
2
Beaucoup, beaucoup plus de programmes injectent également argv[0]leur utilisation / aide au lieu de coder en dur leur nom. Certains dans leur intégralité, d'autres simplement le nom de base.
spectras
62

Beaucoup:

  • Bash s'exécute en mode POSIX lorsqu'il argv[0]est sh. Il fonctionne comme un shell de connexion quand argv[0]commence par -.
  • Vim se comporte différemment lorsqu'il est exécuté comme vi, view, evim, eview, ex, vimdiff, etc.
  • Busybox, comme déjà mentionné.
  • Dans les systèmes avec systemd comme init, shutdown, reboot, etc. sont des liens symboliques àsystemctl .
  • etc.
muru
la source
7
Un autre est sendmailet mail. Chaque MTA unix est livré avec un lien symbolique pour ces deux commandes et est conçu pour émuler le comportement de l'original lorsqu'il est appelé en tant que tel, ce qui signifie que tout programme unix devant envoyer un courrier sait exactement comment le faire.
Shadur
4
un autre cas courant: testet [: lorsque vous appelez le premier, il gère une erreur si le dernier argument est ]. (Sur Debian stable, ces commandes sont deux programmes différents, mais les versions précédentes et MacO utilisent toujours le même programme). Et tex, latexet ainsi de suite: le binaire est le même, mais en regardant la façon dont il a été appelé, il choisir la bonne configuration fichier. initest similaire.
Giacomo Catenazzi
4
En relation, [considère que c'est une erreur si le dernier argument n'est pas ] .
Chepner
Je suppose que cela répond à la deuxième question, mais pas à la première. Je doute fort que certains concepteurs de systèmes d’exploitation se soient assis et aient dit: «Hé, ce serait bien si le même programme fonctionnait différemment en fonction de son nom d’exécutable. J'imagine que je vais inclure le nom dans son tableau d'arguments, alors. "
Joey
@ Joey Oui, le libellé est destiné à indiquer que (Q: "Y a-t-il des ...?" A: "Beaucoup: ...")
muru
34

Historiquement, il argvn’ya qu’un tableau de pointeurs sur les "mots" de la ligne de commande, il est donc logique de commencer par le premier "mot", qui se trouve être le nom du programme.

Et il y a beaucoup de programmes qui se comportent différemment selon le nom utilisé pour les appeler, vous pouvez donc simplement créer différents liens avec eux et obtenir différentes "commandes". L'exemple le plus extrême auquel je puisse penser est busybox , qui agit comme plusieurs douzaines de "commandes" différentes en fonction de la manière dont elle est appelée .

Edit : Références pour Unix 1ère édition, comme demandé

On peut voir par exemple de la fonction principale de cccela argcet argvont déjà été utilisés. Le shell copie les arguments à l' parbufintérieur de la newargpartie de la boucle, tout en traitant la commande elle-même de la même manière que les arguments. (Bien entendu, il n'exécutera plus tard que le premier argument, qui est le nom de la commande). On dirait que les execvparents n'existaient pas alors.

dirkt
la source
1
s'il vous plaît ajouter des références qui sauvegardent cette place.
Lesmana
A partir d’un écrémage rapide, execprend le nom de la commande à exécuter et un tableau de pointeurs de caractères à zéro (mieux visible à minnie.tuhs.org/cgi-bin/utree.pl?file=V1/u0.s , où execprend références à l'étiquette 2 et étiquette 1, et à l'étiquette 2:apparaît etc/init\0, et à l'étiquette 1:apparaît une référence à l'étiquette 2, et un zéro final), qui est fondamentalement ce qui execvefait aujourd'hui moins envp.
Ninjalj
1
execvet execlexistent "depuis toujours" (c'est-à-dire depuis le début jusqu'au milieu des années 1970) - execvétait un appel système et execlune fonction de bibliothèque qui l'appelait.   execven'existait pas alors parce que l'environnement n'existait pas alors. Les autres membres de la famille ont été ajoutés plus tard.
G-Man
@ G-Man Pouvez-vous m'indiquer execvdans la source v1 que j'ai liée? Juste curieux.
Dirkt
22

Cas d'utilisation:

Vous pouvez utiliser le nom du programme pour changer le comportement du programme .

Par exemple, vous pouvez créer des liens symboliques vers le binaire actuel.

Un exemple célèbre où cette technique est utilisée est le projet busybox qui n’installe qu’un seul binaire et de nombreux liens symboliques. (ls, cp, mv, etc.). Ils le font pour économiser de l'espace de stockage car leurs cibles sont de petits périphériques intégrés.

Ceci est aussi utilisé setarchdepuis util-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

Ici, ils utilisent essentiellement cette technique pour éviter de nombreux fichiers sources en double ou pour que les sources soient toujours plus lisibles.

Un autre cas d'utilisation serait un programme devant charger certains modules ou certaines données au moment de l'exécution. Avoir le chemin du programme vous permet de charger des modules à partir d'un chemin relatif à l'emplacement du programme .

En outre, de nombreux programmes impriment des messages d'erreur, notamment le nom du programme .

Pourquoi :

  1. Parce que c'est la convention POSIX ( man 3p execve):

argv est un tableau de chaînes d'arguments passées au nouveau programme. Par convention, la première de ces chaînes devrait contenir le nom de fichier associé au fichier en cours d'exécution.

  1. C'est la norme C (au moins C99 et C11):

Si la valeur de argc est supérieure à zéro, la chaîne pointée par argv [0] représente le nom du programme. argv [0] [0] doit être un caractère nul si le nom du programme n'est pas disponible dans l'environnement hôte.

Notez que la norme C dit "nom du programme" et non "nom de fichier".

rudimeier
la source
3
Cela ne rompt-il pas si vous atteignez le lien symbolique depuis un autre lien symbolique?
Mehrdad
3
@ Mehrdad, Oui, c'est l'inconvénient et cela peut être déroutant pour l'utilisateur.
rudimeier
@rudimeier: Vos rubriques "Pourquoi" ne sont pas vraiment des raisons, elles sont simplement un "homoncule", c’est-à-dire qu’elles soulèvent la question de savoir pourquoi la norme exige que cela soit le cas.
einpoklum
La question de @einpoklum OP était: Pourquoi le nom du programme est-il passé à l'exécutable? J'ai répondu: Parce que POSIX et C standard nous disent de le faire. Comment pensez-vous que ce n'est pas vraiment une raison ? Si les documents que j'ai cités n'existeraient pas, de nombreux programmes ne passeraient probablement pas le nom du programme.
Rudimeier
Le PO demande effectivement "POURQUOI les normes POSIX et C disent-elles de le faire?" Certes, le libellé était à un niveau abstrait, mais cela semble clair. En réalité, le seul moyen de savoir est de demander aux auteurs.
user2338816
21

En plus des programmes modifiant leur comportement en fonction de la manière dont ils ont été appelés, je trouve argv[0]utile d'imprimer l'utilisation d'un programme, comme suit:

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

Cela fait que le message d'utilisation utilise toujours le nom par lequel il a été appelé. Si le programme est renommé, son message d'utilisation change avec lui. Il comprend même le nom du chemin avec lequel il a été appelé:

# 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]

C'est une bonne idée, en particulier pour les petits outils / scripts spéciaux qui pourraient vivre partout.

Cela semble également être une pratique courante dans les outils GNU, voir lspar exemple:

% 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.
marcelm
la source
3
+1 J'allais suggérer la même chose. Étrange que tant de gens se concentrent sur le changement de comportement et omettent de mentionner probablement l'utilisation la plus évidente et la plus répandue.
Le Vee
5

On exécute le typage du programme: program_name0 arg1 arg2 arg3 ....

Donc, le shell devrait déjà diviser le jeton, et le premier jeton est déjà le nom du programme. Et BTW donc il y a les mêmes indices côté programme et côté shell.

Je pense que c'était juste une astuce de commodité (au tout début), et, comme vous le voyez dans d'autres réponses, c'était aussi très pratique, cette tradition a donc été maintenue et définie en tant qu'API.

Giacomo Catenazzi
la source
4

Fondamentalement, argv inclut le nom du programme afin que vous puissiez écrire des messages d'erreur tels que prgm: file: No such file or directory, qui seraient implémentés avec quelque chose comme ceci:

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

Ce programme est un autre exemple d'application de ce programme, qui se remplace par ... lui-même, jusqu'à ce que vous tapiez quelque chose qui ne l'est pas 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;
}

De toute évidence, il s’agit d’un exemple artificiel, mais intéressant, mais je pense que cela peut avoir de véritables utilisations - par exemple, un binaire à mise à jour automatique, qui réécrit son propre espace mémoire avec une nouvelle version de lui-même qu’il a téléchargée ou modifiée.

Exemple:

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

7 | $

Source, et quelques informations supplémentaires .

chat
la source
Félicitations pour avoir atteint les 1000.
G-Man
0

Le chemin d'accès au programme est argv[0]afin que le programme puisse extraire les fichiers de configuration, etc. de son répertoire d'installation.
Ce serait impossible sans argv[0].

bob cuisinier
la source
2
Ce n'est pas une très bonne explication - il n'y a aucune raison pour que nous n'ayons pas normalisé quelque chose comme (char *path_to_program, char **argv, int argc)par exemple
moopet
Autant que je sache, la plupart des programmes tirent configuration à partir d' un emplacement standard ( ~/.<program>, /etc/<program, $XDG_CONFIG_HOME) et prendre un paramètre pour le modifier ou une option de compilation qui fait cuire dans une constante binaire.
Xiong Chiamiov
0

ccache se comporte de cette manière pour imiter différents appels aux fichiers binaires du compilateur. ccache est un cache de compilation - l’important n’est jamais de compiler deux fois le même code source, mais plutôt de renvoyer le code objet à partir du cache si possible.

Dans la page de manuel de ccache , "il existe deux manières d’utiliser ccache. Vous pouvez préfixer ccache de vos commandes de compilation ou laisser ccache se masquerade en tant que compilateur en créant un lien symbolique (nommé en tant que compilateur) vers ccache. La première méthode est plus pratique si vous voulez simplement essayer ccache ou si vous souhaitez l’utiliser pour certains projets spécifiques. La deuxième méthode est plus utile lorsque vous souhaitez utiliser ccache pour toutes vos compilations. "

La méthode des liens symboliques implique l'exécution de ces commandes:

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 ...

... qui a pour effet de permettre à ccache de prendre toutes les commandes qui auraient sinon été transmises aux compilateurs, permettant ainsi à ccache de renvoyer un fichier en cache ou de transmettre la commande au compilateur réel.

Adam J Richardson
la source