Perché argv include il nome del programma?


106

I programmi Unix / Linux tipici accettano gli input della riga di comando come argomento count ( int argc) e argomento vector ( char *argv[]). Il primo elemento di argvè il nome del programma, seguito dagli argomenti reali.

Perché il nome del programma viene passato all'eseguibile come argomento? Ci sono esempi di programmi che usano il proprio nome (forse una specie di execsituazione)?


6
come mv e cp?
Archemar,

9
Su Debian shè un link simbolico a dash. Si comportano diversamente, quando chiamati come sho comedash
Motte001,

21
@AlexejMagura Se usi qualcosa del genere busybox(comune su dischi di ripristino e simili), praticamente tutto (cp, mv, rm, ls, ...) è un collegamento simbolico a busybox.
Baard Kopperud,

11
Mi sto trovando questo molto difficile da ignorare, quindi mi dire che: probabilmente dire i programmi "GNU" ( gcc, bash, gunzip, la maggior parte del resto del sistema operativo ...), come Linux è solo il kernel.
wizzwizz4,

10
@ wizzwizz4 Cosa c'è che non va nei "tipici programmi Unix / Linux"? L'ho letto come "Programmi tipici in esecuzione su Unix / Linux". È molto meglio della tua restrizione ad alcuni programmi GNU. Dennis Ritchie non stava certamente usando alcun programma GNU. A proposito il kernel Hurd è un esempio di un programma GNU che non ha una funzione principale ...
rudimeier,

Risposte:


122

Per cominciare, nota che argv[0]non è necessariamente il nome del programma. E 'ciò che il chiamante mette in argv[0]della execvechiamata di sistema (ad esempio, vedere questa domanda su Stack Overflow ). (Tutte le altre varianti di execnon sono chiamate di sistema ma interfacce per execve.)

Supponiamo, ad esempio, quanto segue (usando execl):

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

/var/tmp/mybackdoorè ciò che viene eseguito ma argv[0]è impostato su top, e questo è ciò psche (o reale) topverrebbe visualizzato. Vedi questa risposta su U&L SE per ulteriori informazioni al riguardo.

Mettendo da parte tutto ciò: prima dell'avvento di fantasiosi filesystem /proc, argv[0]era l'unico modo per un processo di conoscere il proprio nome. Per cosa sarebbe utile?

  • Diversi programmi personalizzano il loro comportamento in base al nome con cui sono stati chiamati (di solito tramite collegamenti simbolici o fissi, ad esempio le utility di BusyBox ; numerosi altri esempi sono forniti in altre risposte a questa domanda).
  • Inoltre, servizi, demoni e altri programmi che accedono a syslog spesso antepongono il loro nome alle voci del registro; senza questo, il monitoraggio degli eventi diventerebbe quasi impossibile.

18
Esempi di tali programmi sono bunzip2, bzcate bzip2, per i quali primi due sono link simbolici alla terza.
Ruslan,

5
@Ruslan È interessante notare che zcatnon è un link simbolico. Sembrano invece evitare gli svantaggi di questa tecnica usando uno script di shell. Ma non riescono a stampare un --helpoutput completo perché qualcuno che ha aggiunto opzioni a gzip ha dimenticato di mantenere anche zcat.
rudimeier,

1
Per quanto posso ricordare, gli standard di codifica GNU hanno scoraggiato l'uso di argv [0] per cambiare il comportamento del programma ( sezione "Standard per le interfacce in generale" nella versione corrente ). gunzipè un'eccezione storica.

19
busybox è un altro esempio eccellente. Può essere chiamato da 308 nomi diversi per invocare comandi diversi: busybox.net/downloads/BusyBox.html#commands
Pepijn Schmitz

2
Molti, molti più programmi iniettano anche il argv[0]loro uso / output di aiuto invece di codificare il loro nome. Alcuni per intero, altri solo il nome di base.
extra

62

Plenty:

  • Bash viene eseguito in modalità POSIX quando lo argv[0]è sh. Funziona come una shell di login quando argv[0]inizia con -.
  • Vim in modo diverso quando eseguito come vi, view, evim, eview, ex, vimdiff, etc.
  • Busybox, come già accennato.
  • Nei sistemi con systemd come init, shutdown, reboot, ecc sono collegamenti simbolici asystemctl .
  • e così via.

7
Un altro è sendmaile mail. Ogni singolo MTA unix viene fornito con un link simbolico per questi due comandi ed è progettato per emulare il comportamento dell'originale quando viene chiamato come tale, il che significa che qualsiasi programma unix che deve inviare posta sa esattamente come può farlo.
Shadur,

4
un altro caso comune: teste [: quando chiami il primo, gestisce un errore se l'ultimo argomento è ]. (sull'attuale Debian stable questi comandi sono due programmi diversi, ma le versioni precedenti e MacO usano ancora lo stesso programma). E tex, latexe così via: il binario è lo stesso, ma guardando come è stato chiamato, è scegliere la corretta configurazione del file. initè simile.
Giacomo Catenazzi,

4
Correlato, lo [considera un errore se l'ultimo argomento non lo è ].
Chepner,

Immagino che questo risponda alla seconda domanda, ma non alla prima. Dubito molto che alcuni progettisti di sistemi operativi si siano seduti e abbiano detto »Ehi, sarebbe bello se avessi lo stesso programma che faceva cose diverse solo in base al suo nome eseguibile. Immagino che includerò il nome nella sua matrice di argomenti, allora. «
Joey,

@Joey Sì, la formulazione ha lo scopo di trasmettere che (D: "Ci sono ...?" A: "Abbondanza: ...")
muru,

34

Storicamente, argvè solo una serie di puntatori alle "parole" della riga di comando, quindi ha senso iniziare con la prima "parola", che sembra essere il nome del programma.

E ci sono alcuni programmi che si comportano in modo diverso a seconda del nome utilizzato per chiamarli, quindi puoi semplicemente creare collegamenti diversi a loro e ottenere diversi "comandi". L'esempio più estremo che mi viene in mente è busybox , che si comporta come una dozzina di "comandi" diversi a seconda di come viene chiamato .

Modifica : riferimenti per Unix 1a edizione, come richiesto

Si può vedere ad esempio dalla funzione principale di ccquello argce argvsono già stati utilizzati. La shell copia gli argomenti parbufall'interno della newargparte del ciclo, trattando il comando stesso allo stesso modo degli argomenti. (Naturalmente, in seguito esegue solo il primo argomento, che è il nome del comando). Sembra che i execvparenti non esistessero allora.


1
si prega di aggiungere riferimenti che lo supportano.
lesmana,

Da uno skimming rapido, execprende il nome del comando da eseguire e un array con terminazione zero di puntatori char (si può vedere meglio su minnie.tuhs.org/cgi-bin/utree.pl?file=V1/u0.s , dove execprende riferimenti all'etichetta 2 e all'etichetta 1, e all'etichetta 2:appare etc/init\0, e all'etichetta 1:appare un riferimento all'etichetta 2 e uno zero finale, che è fondamentalmente ciò che execvefa meno oggi envp.
ninjalj,

1
execved execlè esistito "per sempre" (cioè dall'inizio alla metà degli anni '70) - execvera una chiamata di sistema ed execlera una funzione di libreria che lo chiamava.   execvenon esisteva allora perché l'ambiente non esisteva allora. Gli altri membri della famiglia furono aggiunti in seguito.
G-Man,

@ G-Man Puoi indicarmi execvla fonte v1 che ho collegato? Solo curioso.
Dirkt

22

Casi d'uso:

È possibile utilizzare il nome del programma per modificare il comportamento del programma .

Ad esempio, è possibile creare alcuni collegamenti simbolici al binario effettivo.

Un esempio famoso in cui viene utilizzata questa tecnica è il progetto busybox che installa solo un singolo binario e molti collegamenti simbolici. (ls, cp, mv, ecc.). Lo stanno facendo per risparmiare spazio di archiviazione perché i loro target sono piccoli dispositivi integrati.

Questo è usato anche setarchda 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

Qui stanno usando questa tecnica sostanzialmente per evitare molti file sorgente duplicati o semplicemente per mantenere le fonti più leggibili.

Un altro caso d'uso sarebbe un programma che deve caricare alcuni moduli o dati in fase di esecuzione. Avere il percorso del programma consente di caricare i moduli da un percorso relativo alla posizione del programma .

Inoltre, molti programmi stampano messaggi di errore, incluso il nome del programma .

Perché :

  1. Perché è la convenzione POSIX ( man 3p execve):

argv è una matrice di stringhe di argomenti passate al nuovo programma. Per convenzione, la prima di queste stringhe dovrebbe contenere il nome file associato al file in esecuzione.

  1. È standard C (almeno C99 e C11):

Se il valore di argc è maggiore di zero, la stringa indicata da argv [0] rappresenta il nome del programma; argv [0] [0] deve essere il carattere null se il nome del programma non è disponibile dall'ambiente host.

Si noti che lo standard C dice "nome del programma" non "nome file".


3
Questo non si interrompe se si raggiunge il collegamento simbolico da un altro collegamento simbolico?
Mehrdad,

3
@Mehrdad, Sì, questo è il rovescio della medaglia e può essere fonte di confusione per l'utente.
rudimeier,

@rudimeier: I tuoi articoli 'Why' non sono in realtà ragioni, sono solo un "homunculus", cioè si pone solo la domanda sul perché lo standard richieda che ciò avvenga.
einpoklum,

La domanda di @einpoklum OP era: Perché il nome del programma è passato all'eseguibile? Ho risposto: Perché POSIX e C standard ci dicono di farlo. Come pensi che non sia davvero un motivo ? Se i documenti che ho citato non esistessero, probabilmente molti programmi non passerebbero il nome del programma.
rudimeier,

L'OP sta effettivamente chiedendo "PERCHÉ gli standard POSIX e C dicono di farlo?" È vero che la formulazione era a livello astratto, ma sembra chiaro. Realisticamente, l'unico modo per sapere è chiedere agli autori.
user2338816

21

Oltre ai programmi che modificano il loro comportamento in base al modo in cui sono stati chiamati, trovo argv[0]utile nella stampa l'utilizzo di un programma, in questo modo:

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

Ciò fa sì che il messaggio di utilizzo utilizzi sempre il nome con cui è stato chiamato. Se il programma viene rinominato, il suo messaggio di utilizzo cambia con esso. Include anche il nome del percorso con cui è stato chiamato:

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

È un bel tocco, specialmente per piccoli strumenti / script speciali che potrebbero vivere ovunque.

Questa sembra una pratica comune anche negli strumenti GNU, vedi lsad esempio:

% 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. Stavo per suggerire lo stesso. Strano che così tante persone si concentrino sul cambiamento del comportamento e non menzionino probabilmente l'uso più ovvio e molto più diffuso.
The Vee,

5

Uno esegue la tipizzazione del programma: program_name0 arg1 arg2 arg3 ....

Quindi la shell dovrebbe già dividere il token e il primo token è già il nome del programma. E BTW quindi ci sono gli stessi indici sul lato del programma e sulla shell.

Penso che questo sia stato solo un trucco pratico (all'inizio) e, come vedi in altre risposte, è stato anche molto utile, quindi questa tradizione è stata continuata e impostata come API.


4

Fondamentalmente, argv include il nome del programma in modo da poter scrivere messaggi di errore simili prgm: file: No such file or directory, che verrebbero implementati con qualcosa del genere:

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

2

Un altro esempio di un'applicazione di questo è questo programma, che si sostituisce con ... se stesso, finché non si digita qualcosa che non lo è 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;
}

Ovviamente, un certo esempio inventato, seppur interessante, ma penso che questo possa avere degli usi reali - per esempio, un binario auto-aggiornante, che riscrive il proprio spazio di memoria con una nuova versione di se stesso che ha scaricato o modificato.

Esempio:

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

7 | $

Fonte e alcune altre informazioni .


Congratulazioni per aver raggiunto 1000.
G-Man il

0

Il percorso del programma è argv[0], in modo che il programma possa recuperare i file di configurazione ecc. Dalla sua directory di installazione.
Ciò sarebbe impossibile senza argv[0].


2
Questa non è una spiegazione particolarmente buona - non c'è motivo per cui non avremmo potuto standardizzare qualcosa come (char *path_to_program, char **argv, int argc)ad esempio
Moopet,

AFAIK, la maggior parte dei programmi di tirare la configurazione da una posizione standard ( ~/.<program>, /etc/<program, $XDG_CONFIG_HOME) e prendere un parametro da modificare o avere un'opzione di compilazione che cuoce in un costante al binario.
Xiong Chiamiov,

0

ccache si comporta in questo modo per imitare diverse chiamate ai binari del compilatore. ccache è una cache di compilazione - l'intero punto non è mai quello di compilare lo stesso codice sorgente due volte, ma invece restituire il codice oggetto dalla cache, se possibile.

Dalla pagina man di ccache , "ci sono due modi per usare ccache. Puoi prefissare i tuoi comandi di compilazione con ccache oppure puoi lasciare che ccache si maschera come compilatore creando un link simbolico (chiamato come compilatore) a ccache. Il primo metodo è molto utile se si desidera semplicemente provare ccache o si desidera utilizzarlo per alcuni progetti specifici. Il secondo metodo è più utile quando si desidera utilizzare ccache per tutte le compilation. "

Il metodo symlink prevede l'esecuzione di questi comandi:

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

... il cui effetto è consentire a ccache di catturare qualsiasi comando che altrimenti sarebbe andato ai compilatori, permettendo così a ccache di restituire un file memorizzato nella cache o passare il comando al compilatore effettivo.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.