¿Por qué argv incluye el nombre del programa?

106

Los programas típicos de Unix / Linux aceptan las entradas de la línea de comandos como un conteo de argumentos ( int argc) y un vector de argumentos ( char *argv[]). El primer elemento de argves el nombre del programa, seguido de los argumentos reales.

¿Por qué se pasa el nombre del programa al ejecutable como argumento? ¿Hay ejemplos de programas que usen su propio nombre (tal vez algún tipo de execsituación)?

Shrikant Giridhar
fuente
66
como mv y cp?
Archemar
99
En Debian shes enlace simbólico a dash. Se comportan de manera diferente, cuando se les llama como sho comodash
Motte001
21
@AlexejMagura Si usa algo como busybox(común en los discos de rescate y demás ), casi todo (cp, mv, rm, ls, ...) es un enlace simbólico a busybox.
Baard Kopperud
11
Estoy encontrando esta muy difícil de ignorar, así que voy a decirlo: es probable que los programas decir "GNU" ( gcc, bash, gunzip, la mayor parte del resto del OS ...), ya que Linux es sólo el núcleo.
wizzwizz4
10
@ wizzwizz4 ¿Qué hay de malo con los "programas típicos de Unix / Linux"? Lo leí como "Programas típicos que se ejecutan en Unix / Linux". Eso es mucho mejor que su restricción a ciertos programas GNU. Dennis Ritchie ciertamente no estaba usando ningún programa GNU. Por cierto, el kernel Hurd es un ejemplo de un programa de GNU, que no tiene una función principal ...
rudimeier

Respuestas:

122

Para empezar, tenga en cuenta que argv[0]no es necesariamente el nombre del programa. Es lo que la persona que llama pone en argv[0]la execvellamada del sistema (por ejemplo, vea esta pregunta en Desbordamiento de pila ). (Todas las demás variantes de execno son llamadas al sistema sino interfaces a execve).

Supongamos, por ejemplo, lo siguiente (usando execl):

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

/var/tmp/mybackdoores lo que se ejecuta pero argv[0]se establece en top, y esto es lo que pso (lo real) topmostraría. Vea esta respuesta en U&L SE para más información sobre esto.

Dejando a un lado todo esto: antes de la aparición de sofisticados sistemas de archivos como /proc, argv[0]era la única forma de que un proceso aprendiera sobre su propio nombre. ¿Para qué sería bueno?

  • Varios programas personalizan su comportamiento según el nombre con el que fueron llamados (generalmente por enlaces simbólicos o duros, por ejemplo, las utilidades de BusyBox ; se proporcionan varios ejemplos más en otras respuestas a esta pregunta).
  • Además, los servicios, demonios y otros programas que inician sesión a través de syslog a menudo anteponen su nombre a las entradas de registro; sin esto, el seguimiento de eventos sería casi inviable.
contramodo
fuente
18
Ejemplos de tales programas son bunzip2, bzcaty bzip2, para los cuales los dos primeros son enlaces simbólicos al tercero.
Ruslan
55
@Ruslan Curiosamente zcatno es un enlace simbólico. Parecen evitar las desventajas de esta técnica utilizando un script de shell en su lugar. Pero no pueden imprimir una --helpsalida completa porque alguien que agregó opciones a gzip también olvidó mantener zcat.
rudimeier
1
Desde que tengo memoria, los estándares de codificación GNU han desalentado el uso de argv [0] para cambiar el comportamiento del programa ( sección "Estándares para interfaces en general" en la versión actual ). gunzipEs una excepción histórica.
19
busybox es otro excelente ejemplo. Puede ser invocado por 308 nombres diferentes para invocar diferentes comandos: busybox.net/downloads/BusyBox.html#commands
Pepijn Schmitz
2
Muchos, muchos más programas también inyectan su argv[0]salida de uso / ayuda en lugar de codificar su nombre. Algunos en su totalidad, algunos solo el nombre base.
espectras
62

Mucho:

  • Bash se ejecuta en modo POSIX cuando argv[0]está sh. Se ejecuta como un shell de inicio de sesión cuando argv[0]comienza con -.
  • Vim se comporta de forma diferente cuando se ejecuta como vi, view, evim, eview, ex, vimdiff, etc.
  • Busybox, como ya se mencionó.
  • En sistemas con systemd como init, shutdown, reboot, etc., son enlaces simbólicos asystemctl .
  • y así.
muru
fuente
77
Otro es sendmaily mail. Cada MTA de Unix viene con un enlace simbólico para esos dos comandos, y está diseñado para emular el comportamiento del original cuando se llama como tal, lo que significa que cualquier programa de Unix que necesite enviar correo sabe exactamente cómo puede hacerlo.
Shadur
44
otro caso común: testy [: cuando llamas al primero, maneja un error si el último argumento es ]. (en Debian estable, estos comandos son dos programas diferentes, pero las versiones anteriores y MacOs todavía usan el mismo programa). Y tex, latexy así sucesivamente: el binario es el mismo, pero mirando cómo se llama, es elegir el correcto configuración de archivo. inites similar.
Giacomo Catenazzi
44
Relacionado, lo [considera un error si el último argumento no lo es ].
chepner
Supongo que esto responde la segunda pregunta, pero no la primera. Dudo mucho que algún diseñador de sistemas operativos se haya sentado y haya dicho »Oye, sería genial si tuviera el mismo programa haciendo cosas diferentes solo en función de su nombre ejecutable. Supongo que incluiré el nombre en su matriz de argumentos, entonces. «
Joey
@Joey Sí, la redacción pretende transmitir eso (P: "¿Hay alguna ...?" A: "Mucho: ...")
muru
34

Históricamente, argves solo una serie de punteros a las "palabras" de la línea de comandos, por lo que tiene sentido comenzar con la primera "palabra", que resulta ser el nombre del programa.

Y hay bastantes programas que se comportan de manera diferente según el nombre que se use para llamarlos, por lo que puede crear diferentes enlaces a ellos y obtener diferentes "comandos". El ejemplo más extremo que se me ocurre es busybox , que actúa como varias docenas de "comandos" diferentes según cómo se llame .

Editar : Referencias para Unix 1st edition, según lo solicitado

Uno puede ver, por ejemplo, desde la función principal de cceso argcy argvya se utilizaron. El shell copia argumentos en el parbufinterior de la newargparte del bucle, mientras trata el comando en sí de la misma manera que los argumentos. (Por supuesto, más adelante solo ejecuta el primer argumento, que es el nombre del comando). Parece que los execvparientes no existían entonces.

dirkt
fuente
1
agregue referencias que respalden esto.
lesmana
Desde un descremado rápido, exectoma el nombre del comando a ejecutar y una matriz de punteros de caracteres con terminación cero (mejor visto en minnie.tuhs.org/cgi-bin/utree.pl?file=V1/u0.s , donde exectoma referencias a la etiqueta 2 y la etiqueta 1, y en la etiqueta 2:aparece etc/init\0, y en la etiqueta 1:aparece una referencia a la etiqueta 2, y un cero final), que es básicamente lo que execvehace hoy menos envp.
ninjalj
1
execvy execlhan existido "para siempre" (es decir, desde principios hasta mediados de la década de 1970) - execvfue una llamada al sistema y execlfue una función de biblioteca que la llamó.   execveno existía entonces porque el entorno no existía entonces. Los otros miembros de la familia fueron agregados más tarde.
G-Man
@ G-Man ¿Puedes señalarme execven la fuente v1 que vinculé? Sólo curioso.
dirkt
22

Casos de uso:

Puede usar el nombre del programa para cambiar el comportamiento del programa .

Por ejemplo, podría crear algunos enlaces simbólicos al binario real.

Un ejemplo famoso en el que se utiliza esta técnica es el proyecto busybox que instala solo un binario único y muchos enlaces simbólicos. (ls, cp, mv, etc.) Lo están haciendo para ahorrar espacio de almacenamiento porque sus objetivos son pequeños dispositivos integrados.

Esto también se usa en 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

Aquí están utilizando esta técnica básicamente para evitar muchos archivos fuente duplicados o simplemente para mantener las fuentes más legibles.

Otro caso de uso sería un programa que necesita cargar algunos módulos o datos en tiempo de ejecución. Tener la ruta del programa le permite cargar módulos desde una ruta relativa a la ubicación del programa .

Además, muchos programas imprimen mensajes de error, incluido el nombre del programa .

Por qué :

  1. Porque es la convención POSIX ( man 3p execve):

argv es un conjunto de cadenas de argumentos pasados ​​al nuevo programa. Por convención, la primera de estas cadenas debe contener el nombre de archivo asociado con el archivo que se está ejecutando.

  1. Es estándar C (al menos C99 y C11):

Si el valor de argc es mayor que cero, la cadena a la que apunta argv [0] representa el nombre del programa; argv [0] [0] será el carácter nulo si el nombre del programa no está disponible en el entorno del host.

Tenga en cuenta que el estándar C dice "nombre del programa" no "nombre de archivo".

rudimeier
fuente
3
¿No se rompe esto si alcanzas el enlace simbólico desde otro enlace simbólico?
Mehrdad
3
@Mehrdad, sí, ese es el inconveniente y puede ser confuso para el usuario.
rudimeier
@rudimeier: Sus artículos 'Por qué' no son realmente razones, son solo un "homúnculo", es decir, simplemente plantea la pregunta de por qué el estándar requiere que sea así.
einpoklum
La pregunta de @einpoklum OP fue: ¿Por qué se pasa el nombre del programa al ejecutable? Respondí: porque POSIX y el estándar C nos dicen que lo hagamos. ¿Cómo crees que no es realmente una razón ? Si los documentos que he citado no existieran, probablemente muchos programas no pasarían el nombre del programa.
rudimeier
El OP pregunta efectivamente "¿POR QUÉ dicen los estándares POSIX y C para hacer esto?" Por supuesto, la redacción estaba en un nivel abstracto, pero parece claro. Siendo realistas, la única forma de saber es preguntar a los creadores.
user2338816
21

Además de los programas que alteran su comportamiento en función de cómo fueron llamados, me parece argv[0]útil imprimir el uso de un programa, así:

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

Esto hace que el mensaje de uso use siempre el nombre a través del cual fue llamado. Si se cambia el nombre del programa, su mensaje de uso cambia con él. Incluso incluye el nombre de ruta con el que se llamó:

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

Es un buen toque, especialmente para pequeñas herramientas / scripts de propósito especial que podrían vivir en todo el lugar.

Esto también parece una práctica común en las herramientas GNU, ver lspor ejemplo:

% 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
fuente
3
+1. Iba a sugerir lo mismo. Es extraño que tantas personas se centren en cambiar el comportamiento y no mencionen probablemente el uso más obvio y mucho más extendido.
The Vee
5

Uno ejecuta el programa tecleando: program_name0 arg1 arg2 arg3 ....

Entonces el shell ya debería dividir el token, y el primer token ya es el nombre del programa. Y, por cierto, existen los mismos índices en el lado del programa y en el shell.

Creo que esto fue solo un truco de conveniencia (muy al principio) y, como puede ver en otras respuestas, también fue muy útil, por lo que esta tradición se continuó y se estableció como API.

Giacomo Catenazzi
fuente
4

Básicamente, argv incluye el nombre del programa para que pueda escribir mensajes de error como prgm: file: No such file or directory, que se implementaría con algo como esto:

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

Otro ejemplo de una aplicación de este es este programa, que se reemplaza por sí mismo ... hasta que escribe algo que no lo es 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;
}

Obviamente, es una especie de ejemplo ingenioso pero interesante, pero creo que esto puede tener usos reales, por ejemplo, un binario de actualización automática, que reescribe su propio espacio de memoria con una nueva versión de sí mismo que descargó o cambió.

Ejemplo:

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

7 | $

Fuente, y algo más de información .

gato
fuente
Felicidades por llegar a 1000.
G-Man
0

La ruta al programa es argv[0], para que el programa pueda recuperar archivos de configuración, etc. de su directorio de instalación.
Esto sería imposible sin él argv[0].

Bob Cook
fuente
2
Esa no es una explicación particularmente buena: no hay ninguna razón por la que no podríamos haber estandarizado algo como, (char *path_to_program, char **argv, int argc)por ejemplo
moopet el
Que yo sepa, la mayoría de los programas de arrastre de configuración desde una ubicación estándar ( ~/.<program>, /etc/<program, $XDG_CONFIG_HOME) y, o bien tomar un parámetro para cambiarlo o tiene una opción en tiempo de compilación que se hornea en una constante para el binario.
Xiong Chiamiov
0

ccache se comporta de esta manera para imitar diferentes llamadas a los binarios del compilador. ccache es una caché de compilación: el objetivo principal nunca es compilar el mismo código fuente dos veces, sino devolver el código objeto de la caché si es posible.

Desde la página de manual de ccache , "hay dos formas de usar ccache. Puede prefijar sus comandos de compilación con ccache o puede dejar que ccache se disfrace como compilador creando un enlace simbólico (llamado como compilador) a ccache. El primer método es más conveniente si solo desea probar ccache o desea usarlo para algunos proyectos específicos. El segundo método es más útil para cuando desea usar ccache para todas sus compilaciones ".

El método de enlaces simbólicos implica ejecutar estos comandos:

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

... cuyo efecto es permitir que ccache enganche cualquier comando que de otro modo hubiera ido a los compiladores, permitiendo así que ccache devuelva un archivo en caché o pase el comando al compilador real.

Adam J Richardson
fuente