web-dev-qa-db-fra.com

Pourquoi argv inclut-il le nom du programme?

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

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

109
Shrikant Giridhar

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

Supposons, par exemple, ce qui suit (en utilisant execl):

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

/var/tmp/mybackdoor est ce qui est exécuté mais argv[0] est défini sur top, et c'est ce que ps ou (le vrai) top afficherait. Voir cette réponse sur U&L SE pour en savoir plus.

Mettre tout cela de côté: avant l'avènement de fantastiques systèmes de fichiers comme /proc, argv[0] était le seul moyen pour un processus de connaître son propre nom. À quoi cela servirait-il?

  • Plusieurs programmes personnalisent leur comportement en fonction du nom sous lequel ils ont été appelés (généralement par des liens symboliques ou matériels, par exemple tilitaires 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 pratiquement impossible.
123
countermode

Beaucoup:

  • Bash s'exécute en mode POSIX lorsque argv[0] est sh. Il fonctionne comme un shell de connexion lorsque argv[0] commence par -.
  • Vim se comporte différemment lorsqu'il est exécuté en tant que vi, view, evim, eview, ex, vimdiff, etc.
  • Busybox, comme déjà mentionné.
  • Dans les systèmes avec systemd comme init, shutdown, reboot, etc. sont liens symboliques vers systemctl .
  • etc.
62
muru

Historiquement, argv n'est qu'un tableau de pointeurs vers les "mots" de la ligne de commande, il est donc logique de commencer par le premier "Word", qui se trouve être le nom du programme.

Et il y a pas mal de programmes qui se comportent différemment selon le nom utilisé pour les appeler, vous pouvez donc simplement créer différents liens vers eux et obtenir différentes "commandes". L'exemple le plus extrême auquel je peux penser est busybox , qui agit comme plusieurs dizaines de "commandes" différentes selon la façon dont il est appelé .

Edit : Références pour Unix 1st edition, comme demandé

On peut voir par exemple de la fonction main de cc que argc et argv étaient déjà utilisés. Shell copie les arguments dans la parbuf à l'intérieur de la partie newarg de la boucle, tout en traitant la commande elle-même de la même manière que les arguments. (Bien sûr, plus tard, il n'exécute que le premier argument, qui est le nom de la commande). Il semble que execv et ses proches n'existaient pas à l'époque.

35
dirkt

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 réel.

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

Ceci est également utilisé dans setarch d'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 cette technique essentiellement pour éviter de nombreux fichiers source en double ou simplement pour garder les sources plus lisibles.

Un autre cas d'utilisation serait un programme qui doit charger certains modules ou données au moment de l'exécution. Le fait d'avoir le chemin du programme vous permet de charger des modules à partir d'un chemin par rapport à l'emplacement du programme.

De plus, de nombreux programmes imprimer les messages d'erreur, y compris 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 doit 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 le caractère nul si le nom du programme n'est pas disponible dans l'environnement hôte.

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

22
rudimeier

En plus des programmes modifiant leur comportement en fonction de la façon dont ils ont été appelés, je trouve argv[0] utile pour imprimer l'utilisation d'un programme, comme ceci:

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

Ainsi, le message d'utilisation utilise toujours le nom sous lequel il a été appelé. Si le programme est renommé, son message d'utilisation change avec lui. Il inclut 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 belle touche, en particulier pour les petits outils/scripts à usage spécial qui pourraient vivre partout.

Cela semble une pratique courante dans les outils GNU également, voir ls par 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.
21
marcelm

On exécute le programme en tapant: program_name0 arg1 arg2 arg3 ....

Le shell doit donc 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 sur Shell.

Je pense que ce n'était qu'un truc de commodité (au tout début) et, comme vous le voyez dans d'autres réponses, c'était aussi très pratique, donc cette tradition a été poursuivie et définie comme API.

5
Giacomo Catenazzi

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

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

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

Évidemment, une sorte d'exemple artificiel mais intéressant, mais je pense que cela peut avoir de réelles 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 .

2
cat

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

0
bob cook

ccache se comporte de cette façon afin d'imiter différents appels aux binaires du compilateur. ccache est un cache de compilation - il ne s'agit jamais de compiler deux fois le même code source, mais de renvoyer le code objet du cache si possible.

À partir de la page de manuel de ccache , "il existe deux façons d'utiliser ccache. Vous pouvez soit préfixer vos commandes de compilation avec ccache, soit laisser ccache se faire passer pour le compilateur en créant un lien symbolique (nommé comme compilateur ) à 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 particulièrement utile lorsque vous souhaitez utiliser ccache pour toutes vos compilations. "

La méthode des liens symboliques implique d'exécuter 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 ...

... dont l'effet est d'autoriser ccache à accrocher toutes les commandes qui auraient autrement été transmises aux compilateurs, permettant ainsi à ccache de renvoyer un fichier en cache ou de passer la commande au compilateur réel.

0
Adam J Richardson