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
)?
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?
Beaucoup:
argv[0]
est sh
. Il fonctionne comme un shell de connexion lorsque argv[0]
commence par -
.vi
, view
, evim
, eview
, ex
, vimdiff
, etc.shutdown
, reboot
, etc. sont liens symboliques vers systemctl
.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.
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:
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.
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".
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.
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.
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] );
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 | $
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]
.
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.