Почему argv включает название программы?

106

Типичные программы Unix / Linux принимают входные данные командной строки в качестве аргумента count ( int argc) и аргумента vector ( char *argv[]). Первым элементом argvявляется имя программы, за которым следуют фактические аргументы.

Почему имя программы передается исполняемому файлу в качестве аргумента? Есть ли примеры программ, использующих свое имя (может быть, какая-то execситуация)?

Shrikant Giridhar
источник
6
как мв и ср?
Архемар
9
На Debian shесть символическая ссылка на dash. Они ведут себя по-разному, когда их называют как shили какdash
Motte001
21
@AlexejMagura Если вы используете что-то вроде busybox(обычно на спасательных дисках и т. Д.), То почти все (cp, mv, rm, ls, ...) является символической ссылкой на busybox.
Баард Копперуд
11
Я нахожу это очень трудно игнорировать, так что я скажу: вы , вероятно , имели в виду программу «GNU» ( gcc, bash, gunzip, большую часть остальной части ОС ...), так как Linux это только ядро.
wizzwizz4
10
@ wizzwizz4 Что не так с «Типичными программами для Unix / Linux»? Я читаю это как «Типичные программы, работающие на Unix / Linux». Это намного лучше, чем ваше ограничение для определенных программ GNU. Деннис Ритчи, конечно же, не использовал никаких программ GNU. Кстати, ядро ​​Hurd является примером программы GNU, у которой нет основной функции ...
rudimeier

Ответы:

122

Для начала обратите внимание, что argv[0]это не обязательно название программы. Это то , что вызывающий абонент вводит в argv[0]часть execveсистемного вызова (например , см этот вопрос на переполнение стека ). (Все остальные варианты execявляются не системными вызовами, а интерфейсами execve.)

Предположим, например, следующее (используя execl):

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

/var/tmp/mybackdoorэто то, что выполняется, но argv[0]установлено top, и это то, что psили (реальный) topбудет отображаться. Смотрите этот ответ на U & L SE для получения дополнительной информации об этом.

Установка все это в стороне: До появления фантазии файловых систем , таких как /proc, argv[0]был единственным способом для процесса , чтобы узнать о его собственном имени. Для чего это будет хорошо?

  • Некоторые программы настраивают свое поведение в зависимости от имени, по которому они были вызваны (обычно с помощью символических или жестких ссылок, например , утилит BusyBox ; еще несколько примеров приведены в других ответах на этот вопрос).
  • Более того, сервисы, демоны и другие программы, которые регистрируются через системный журнал, часто добавляют свое имя к записям журнала; без этого отслеживание событий стало бы практически невозможным.
countermode
источник
18
Примерами таких программ являются bunzip2, bzcatи bzip2, для которых первые две являются символическими ссылками на третью.
Руслан
5
@Ruslan Интересно, что zcatэто не символическая ссылка. Кажется, они избегают недостатков этой техники, используя вместо этого сценарий оболочки. Но они не могут распечатать полный --helpвывод, потому что кто-то, кто добавил опции в gzip, тоже забыл поддерживать zcat.
rudimeier
1
Насколько я помню, стандарты кодирования GNU не поощряют использование argv [0] для изменения поведения программы ( раздел «Стандарты для интерфейсов в целом» в текущей версии ). gunzipэто историческое исключение.
19
busybox - еще один отличный пример. Он может вызываться 308 разными именами для вызова разных команд: busybox.net/downloads/BusyBox.html#commands
Pepijn Schmitz
2
Многие, многие другие программы также вводят их argv[0]в свои выходные данные использования / помощи вместо жесткого кодирования их имени. Некоторые полностью, некоторые просто базовое имя.
спектры
62

Много:

  • Bash работает в режиме POSIX, когда argv[0]есть sh. Он запускается как оболочка входа в систему, когда argv[0]начинается с -.
  • Vim ведет себя по- другому , когда работает как vi, view, evim, eview, ex, vimdiffи т.д.
  • Busybox, как уже упоминалось.
  • В системах с Systemd как первонач shutdown, rebootи т.д. являются символическими ссылками наsystemctl .
  • и так далее.
Мур
источник
7
Еще один sendmailи mail. Каждый Unix MTA поставляется с символической ссылкой для этих двух команд и предназначен для имитации поведения оригинала при вызове как такового, что означает, что любая Unix-программа, которая должна отправлять почту, точно знает, как они могут это сделать.
Шадур
4
другой распространенный случай: testи [: когда вы вызываете первый, он обрабатывает ошибку, если последний аргумент равен ]. (в реальной стабильной версии Debian эти команды представляют собой две разные программы, но предыдущие версии и MacO все еще используют одну и ту же программу). И tex, latexи так далее: бинарная то же самое, но , глядя , как его называли, это выбрать правильный конфигурационный файл. initпохож.
Джакомо Катенацци
4
Связанный, [считает ошибкой, если последний аргумент не является ].
chepner
Я думаю, что это отвечает на второй вопрос, но не первый. Я очень сомневаюсь, что какой-то дизайнер ОС сел и сказал: «Эй, было бы здорово, если бы у меня была одна и та же программа, выполняющая разные вещи только на основе ее имени исполняемого файла. Полагаю, я включу имя в массив аргументов. «
Джои
@Joey Да, формулировка предназначена для того, чтобы передать это (Q: «Есть ли ...?» A: «Много: ...»)
Muru
34

Исторически, argvэто просто массив указателей на «слова» командной строки, поэтому имеет смысл начать с первого «слова», которое, как оказалось, является названием программы.

И есть довольно много программ, которые ведут себя по-разному, в зависимости от того, какое имя используется для их вызова, так что вы можете просто создавать разные ссылки на них и получать разные «команды». Самый экстремальный пример, который я могу вспомнить, это busybox , который действует как несколько десятков различных «команд» в зависимости от того, как он вызывается .

Изменить : ссылки на Unix 1-е издание, по запросу

Можно увидеть, например, из основной функции ccэтого argcи argvуже были использованы. В оболочках копируют аргументы parbufвнутри newargчасти петли, в то время как лечащие сама команда таким же образом , как аргументы. (Конечно, позже он выполняет только первый аргумент, который является именем команды). Похоже, execvродственников тогда не было.

dirkt
источник
1
пожалуйста, добавьте ссылки, которые подтверждают это.
Lesmana
При быстром скимминге execберет имя команды для выполнения и массив нулевых символов с указателями на символы (лучше всего смотреть на minnie.tuhs.org/cgi-bin/utree.pl?file=V1/u0.s , где execпринимает ссылки на метку 2 и метку 1, и на метке 2:появляется etc/init\0, а на метке 1:появляется ссылка на метку 2 и завершающий ноль), что в основном то, execveчто сегодня делает минус envp.
ниндзя
1
execvи execlсуществовали «вечно» (то есть с начала до середины 1970-х годов) - это execvбыл системный вызов и execlбыла библиотечная функция, которая его вызывала.   execveтогда не существовало, потому что тогда среды не было. Другие члены семьи были добавлены позже.
G-Man
@ G-Man Можете ли вы указать мне execvисточник v1, на который я ссылался? Просто любопытно.
Диркт
22

Сценарии использования:

Вы можете использовать имя программы, чтобы изменить ее поведение .

Например, вы можете создать несколько символических ссылок на настоящий двоичный файл.

Одним из известных примеров использования этого метода является проект busybox, который устанавливает только один двоичный файл и множество символических ссылок на него. (ls, cp, mv и т. д.). Они делают это, чтобы сэкономить место для хранения, потому что их целью являются небольшие встроенные устройства.

Это также используется в 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

Здесь они используют эту технику в основном, чтобы избежать дублирования исходных файлов или просто сделать источники более читабельными.

Другой вариант использования - это программа, которая должна загружать некоторые модули или данные во время выполнения. Наличие пути к программе позволяет вам загружать модули с пути относительно места расположения программы .

Более того, многие программы печатают сообщения об ошибках, включая название программы .

Почему :

  1. Потому что это соглашение POSIX ( man 3p execve):

argv - это массив строк аргументов, передаваемых новой программе. По соглашению первая из этих строк должна содержать имя файла, связанное с исполняемым файлом.

  1. Это стандарт C (по крайней мере, C99 и C11):

Если значение argc больше нуля, строка, на которую указывает argv [0], представляет имя программы; argv [0] [0] должен быть нулевым символом, если имя программы недоступно из среды хоста.

Обратите внимание, что в стандарте C написано «имя программы», а не «имя файла».

rudimeier
источник
3
Разве это не нарушается, если вы получаете символическую ссылку из другой символической ссылки?
Мердад
3
@ Mehrdad, да, это недостаток и может сбить пользователя с толку.
rudimeier
@rudimeier: Ваши пункты «Почему» на самом деле не являются причинами, они просто «гомункулы», то есть просто возникает вопрос, почему стандарт требует, чтобы это имело место.
einpoklum
Вопрос @einpoklum OP заключался в следующем: почему имя программы передается в исполняемый файл? Я ответил: потому что стандарт POSIX и C говорит нам об этом. Как вы думаете, это не совсем причина ? Если документы, которые я цитировал, не существовали бы, то, вероятно, многие программы не передавали бы имя программы.
rudimeier
ОП фактически спрашивает: «ПОЧЕМУ стандарты POSIX и C говорят об этом?» Разумеется, формулировка была на абстрактном уровне, но, похоже, понятна. Реально, единственный способ узнать, это спросить авторов.
user2338816
21

В дополнение к программам, изменяющим их поведение в зависимости от того, как они были вызваны, я считаю argv[0]полезным печатать информацию об использовании программы, например:

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

Это заставляет сообщение об использовании всегда использовать имя, через которое оно было вызвано. Если программа переименована, сообщение об использовании меняется вместе с ней. Он даже включает имя пути, с которым он был вызван:

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

Это приятное прикосновение, особенно для небольших специальных инструментов / скриптов, которые могут жить повсюду.

Это кажется обычной практикой в ​​инструментах GNU, см. lsНапример:

% 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
источник
3
+1. Я собирался предложить то же самое. Странно, что так много людей сосредотачиваются на изменении поведения и не упоминают, вероятно, наиболее очевидное и гораздо более широко распространенное использование.
Ви
5

Один выполняет программу набрав: program_name0 arg1 arg2 arg3 ....

Таким образом, оболочка должна разделить токен, а первый токен - это уже имя программы. И кстати, так что есть одинаковые показатели на стороне программы и на оболочке.

Я думаю, что это был просто удобный прием (в самом начале), и, как вы видите в других ответах, он также был очень удобен, поэтому эта традиция была продолжена и установлена ​​как API.

Джакомо Катенацци
источник
4

По сути, argv включает в себя имя программы, так что вы можете писать сообщения об ошибках, например prgm: file: No such file or directory, которые будут реализованы примерно так:

    fprintf( stderr, "%s: %s: No such file or directory\n", argv[0], argv[1] );
user628544
источник
2

Другим примером применения этого является эта программа, которая заменяет себя на ... себя, пока вы не наберете что-то, что не является 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;
}

Очевидно, что это своего рода надуманный, но интересный пример, но я думаю, что это может иметь реальное применение - например, самообновляемый двоичный файл, который переписывает свое собственное пространство памяти с новой версией самого себя, которую он загрузил или изменил.

Пример:

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

7 | $

Источник и еще немного информации .

кошка
источник
Поздравляем с достижением 1000.
G-Man
0

Путь к программе таков argv[0], чтобы программа могла получить файлы конфигурации и т. Д. Из каталога установки.
Это было бы невозможно без argv[0].

Боб Кук
источник
2
Это не очень хорошее объяснение - нет никаких причин, по которым мы не могли бы стандартизировать что-то вроде, (char *path_to_program, char **argv, int argc)например
moopet
AFAIK, большинство программ тянуть конфигурации из стандартного расположения ( ~/.<program>, /etc/<program, $XDG_CONFIG_HOME) и либо принять параметр , чтобы изменить его или иметь опцию во время компиляции , что печет в постоянной к двоичной системе .
Сюн Чиамов
0

ccache ведет себя так, чтобы имитировать различные вызовы двоичных файлов компилятора. ccache - это кеш компиляции - суть в том, чтобы никогда не компилировать один и тот же исходный код дважды, а вместо этого возвращать объектный код из кеша, если это возможно.

На странице справочника ccache «есть два способа использования ccache. Вы можете либо добавить префиксные команды компиляции к ccache, либо разрешить маскировать ccache в качестве компилятора, создав символическую ссылку (называемую как компилятор) для ccache. Первый метод Это наиболее удобно, если вы просто хотите попробовать ccache или использовать его для некоторых конкретных проектов. Второй способ наиболее полезен, когда вы хотите использовать ccache для всех ваших компиляций. "

Метод символических ссылок включает в себя выполнение этих команд:

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

... эффект которого состоит в том, что ccache позволяет захватывать любые команды, которые в противном случае пошли бы в компиляторы, что позволяет ccache возвращать кэшированный файл или передавать команду фактическому компилятору.

Адам Ричардсон
источник