Dlaczego argv zawiera nazwę programu?

106

Typowe programy uniksowe / Linux akceptują dane z wiersza poleceń jako argument count ( int argc) i wektor argumentu ( char *argv[]). Pierwszym elementem argvjest nazwa programu - po nim rzeczywiste argumenty.

Dlaczego nazwa programu jest przekazywana do pliku wykonywalnego jako argument? Czy są jakieś przykłady programów używających ich własnych nazw (może jakaś execsytuacja)?

Shrikant Giridhar
źródło
6
jak mv i cp?
Archemar
9
Na Debianie shjest dowiązanie symboliczne dash. Zachowują się inaczej, gdy są nazywani jako shlub jakodash
Motte001
21
@AlexejMagura Jeśli używasz czegoś podobnego busybox( typowego na dyskach ratunkowych itp. ), To prawie wszystko (cp, mv, rm, ls, ...) jest dowiązaniem symbolicznym do busybox.
Baard Kopperud
11
Jestem znalezienie tego naprawdę trudno zignorować, więc powiem to: pewnie na myśli programy „GNU” ( gcc, bash, gunzip, większość z resztą OS ...), jak Linux to tylko jądro.
wizzwizz4,
10
@ wizzwizz4 Co jest złego w „Typowych programach Unix / Linux”? Czytam to jak „Typowe programy działające na systemach Unix / Linux”. To o wiele lepsze niż twoje ograniczenie do niektórych programów GNU. Dennis Ritchie z pewnością nie korzystał z żadnych programów GNU. BTW jądro Hurda jest przykładem programu GNU, który nie ma głównej funkcji ...
rudimeier,

Odpowiedzi:

122

Na początek zauważ, że argv[0]niekoniecznie jest to nazwa programu. To jest to, co rozmówca kładzie się argv[0]na execvewywołanie systemowe (np patrz na to pytanie na przepełnienie stosu ). (Wszystkie inne warianty execnie są wywołaniami systemowymi, ale interfejsami do execve.)

Załóżmy na przykład, że (używając execl):

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

/var/tmp/mybackdoorto, co jest wykonywane, ale argv[0]jest ustawione na top, i to właśnie wyświetli pslub (rzeczywiste) top. Zobacz tę odpowiedź na U&L SE, aby uzyskać więcej na ten temat.

Odkładając to wszystko na bok: Przed pojawieniem się takich fantazyjnych systemów plików /proc, argv[0]był to jedyny sposób, aby proces poznał własną nazwę. Do czego to by było dobre?

  • Kilka programów dostosowuje swoje zachowanie w zależności od nazwy, według której zostały wywołane (zwykle symboliczne lub twarde linki, na przykład narzędzia BusyBox ; kilka innych przykładów podano w innych odpowiedziach na to pytanie).
  • Ponadto usługi, demony i inne programy logujące się przez syslog często poprzedzają swoją nazwę wpisami dziennika; bez tego śledzenie zdarzeń stałoby się niemożliwe.
przeciwdziałanie
źródło
18
Przykładami takich programów są bunzip2, bzcati bzip2, dla których dwa pierwsze są dowiązania do trzeciego.
Ruslan
5
@ Ruslan Co ciekawe zcatnie jest dowiązaniem symbolicznym. Wydaje się, że unikają wad tej techniki, używając skryptu powłoki. Nie udaje im się jednak wydrukować pełnego --helpwyniku, ponieważ ktoś, kto dodał opcje do gzip, również zapomniał zachować Zcat.
rudimeier
1
Odkąd pamiętam, standardy kodowania GNU odradzały stosowanie argv [0] do zmiany zachowania programu ( sekcja „Standardy dla interfejsów ogólnie” w aktualnej wersji ). gunzipjest historycznym wyjątkiem.
19
busybox jest kolejnym doskonałym przykładem. Może być wywoływany przez 308 różnych nazw, aby wywoływać różne polecenia: busybox.net/downloads/BusyBox.html#commands
Pepijn Schmitz
2
Wiele, wiele innych programów również wstrzykuje swoje argv[0]dane wyjściowe dotyczące użycia / pomocy zamiast na stałe kodować ich nazwy. Niektóre w całości, niektóre tylko basename.
spectras
62

Dużo:

  • Bash działa w trybie POSIX, gdy argv[0]jest sh. Działa jako powłoki logowania, gdy argv[0]rozpoczyna się -.
  • Vim zachowuje się inaczej, gdy uruchamiane jako vi, view, evim, eview, ex, vimdiff, itd.
  • Busybox, jak już wspomniano.
  • W systemach z Systemd jako startowych shutdown, rebootitp są dowiązania dosystemctl .
  • i tak dalej.
muru
źródło
7
Kolejnym jest sendmaili mail. Każdy MTA uniksowy zawiera łącze symboliczne dla tych dwóch poleceń i ma na celu emulację zachowania oryginału, gdy zostanie wywołany, co oznacza, że ​​każdy program uniksowy, który musi wysłać pocztę, wie dokładnie, jak to zrobić.
Shadur,
4
inny typowy przypadek: testi [: kiedy wywołujesz ten pierwszy, obsługuje błąd, jeśli ostatnim argumentem jest ]. (w aktualnej stabilnej wersji Debiana polecenia te są dwoma różnymi programami, ale poprzednie wersje i MacO nadal używają tego samego programu). A tex, latexi tak dalej: binarna jest taka sama, ale patrząc, jak to nazwano, to wybrać odpowiednią konfigurację pliku. initjest podobny.
Giacomo Catenazzi
4
Powiązane, [uważa to za błąd, jeśli ostatni argument nie jest ].
chepner
Myślę, że to odpowiada na drugie pytanie, ale nie na pierwsze. Bardzo wątpię, żeby jakiś projektant systemu operacyjnego usiadł i powiedział: „Hej, byłoby fajnie, gdybym miał ten sam program robiąc różne rzeczy na podstawie jego nazwy pliku wykonywalnego. Wydaje mi się, że w ten sposób wstawię nazwę do tablicy argumentów. «
Joey,
@Joey Tak, sformułowanie ma na celu przekazanie, że (P: „Czy są jakieś ...?” O: „Obfitość: ...”)
muru
34

Historycznie argvjest to tylko tablica wskaźników do „słów” wiersza poleceń, więc warto zacząć od pierwszego „słowa”, którym jest nazwa programu.

Jest też sporo programów, które zachowują się inaczej, w zależności od tego, do jakiej nazwy się je wywołuje, więc możesz po prostu tworzyć do nich różne łącza i uzyskiwać różne „polecenia”. Najbardziej ekstremalnym przykładem, jaki mogę wymyślić, jest busybox , który działa jak kilkadziesiąt różnych „poleceń” w zależności od tego, jak się nazywa .

Edycja : Referencje dla 1. edycji Uniksa, zgodnie z żądaniem

Widać np. Z głównej funkcji cctego argci argvbyły już używane. Te powłoki kopiuje argumentów do parbufwewnątrz newargczęści pętli, traktując ten sam rozkaz w taki sam sposób, jak argumentów. (Oczywiście później wykonuje tylko pierwszy argument, którym jest nazwa polecenia). Wygląda na to, że execvkrewni wtedy nie istnieli.

reż
źródło
1
dodaj referencje, które to potwierdzają.
lesmana,
Po szybkim przejrzeniu execpobiera nazwę polecenia do wykonania i tablicę wskaźników charczących zero (najlepiej widać na minnie.tuhs.org/cgi-bin/utree.pl?file=V1/u0.s , gdzie execbierze 2:pojawiają się odniesienia do etykiety 2 i etykiety 1, a na etykiecie etc/init\0, a na etykiecie 1:pojawia się odniesienie do etykiety 2 i zera kończącego), co jest w zasadzie tym, co execvedziś minus envp.
ninjalj
1
execvi execlistniały „na zawsze” (tj. od początku do połowy lat 70.) - execvbyło wywołaniem systemowym i execlbyło funkcją biblioteki, która je wywołała.   execvewtedy nie istniało, ponieważ wtedy środowisko nie istniało. Pozostali członkowie rodziny zostali dodani później.
G-Man
@ G-Man Czy możesz wskazać mi execvźródło v1, które podłączyłem? Po prostu ciekawy.
dirkt
22

Przypadków użycia:

Możesz użyć nazwy programu aby zmienić zachowanie programu .

Na przykład możesz utworzyć dowiązania symboliczne do rzeczywistego pliku binarnego.

Jednym znanym przykładem zastosowania tej techniki jest projekt busybox, który instaluje tylko jeden plik binarny i wiele dowiązań symbolicznych do niego. (ls, cp, mv itp.). Oni to robią aby zaoszczędzić miejsce, ponieważ ich celem są małe urządzenia wbudowane.

Jest to również używane w programie 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

Tutaj używają tej techniki w zasadzie aby uniknąć wielu zduplikowanych plików źródłowych lub po prostu, aby źródła były bardziej czytelne.

Innym przykładem użycia może być program, który musi załadować niektóre moduły lub dane w czasie wykonywania. Posiadanie ścieżki programu pozwala ładowanie modułów ze ścieżki względem lokalizacji programu .

Ponadto wiele programów drukuje komunikaty o błędach, w tym nazwę programu .

Dlaczego :

  1. Ponieważ jest to konwencja POSIX ( man 3p execve):

argv to tablica ciągów argumentów przekazywanych do nowego programu. Zgodnie z konwencją pierwszy z tych ciągów powinien zawierać nazwę pliku powiązaną z wykonywanym plikiem.

  1. Jest to standard C (co najmniej C99 i C11):

Jeśli wartość argc jest większa od zera, ciąg wskazany przez argv [0] reprezentuje nazwę programu; argv [0] [0] będzie znakiem pustym, jeśli nazwa programu nie jest dostępna w środowisku hosta.

Zauważ, że C Standard mówi „nazwa programu”, a nie „nazwa pliku”.

rudimeier
źródło
3
Czy to się nie psuje, jeśli osiągniesz dowiązanie symboliczne z innego dowiązania symbolicznego?
Mehrdad,
3
@ Mehrdad, tak, to jest wada i może być myląca dla użytkownika.
rudimeier
@ rudimeier: Twoje przedmioty „Dlaczego” nie są tak naprawdę przyczynami, są tylko „homunkulusem”, tzn. po prostu nasuwa się pytanie, dlaczego standard tak właśnie musi wyglądać.
einpoklum
Pytanie @einpoklum OP brzmiało: Dlaczego nazwa programu jest przekazywana do pliku wykonywalnego? Odpowiedziałem: Ponieważ standard POSIX i C nakazuje nam to zrobić. Jak myślisz, że to nie jest tak naprawdę powód ? Jeśli cytowane przeze mnie dokumenty nie istniałyby, prawdopodobnie wiele programów nie podałoby nazwy programu.
rudimeier
OP skutecznie pyta: „DLACZEGO standardy POSIX i C mówią, aby to zrobić?” Owszem, sformułowanie było na abstrakcyjnym poziomie, ale wydaje się jasne. Realistycznie jedynym sposobem na poznanie jest zapytanie do pomysłodawców.
user2338816
21

Oprócz programów zmieniających ich zachowanie w zależności od tego, jak zostały wywołane, uważam, że argv[0]przydatne w drukowaniu użycia programu, takie jak:

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

To powoduje, że komunikat użycia zawsze używa nazwy, przez którą został wywołany. Jeśli nazwa programu zostanie zmieniona, jego komunikat o użyciu zmienia się wraz z nim. Zawiera nawet nazwę ścieżki, z którą został wywołany:

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

To miły akcent, szczególnie w przypadku małych narzędzi / skryptów specjalnego przeznaczenia, które mogą istnieć wszędzie.

Wydaje się to powszechną praktyką również w narzędziach GNU, patrz lsna przykład:

% 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
źródło
3
+1. Chciałem zasugerować to samo. Dziwne, że tak wiele osób koncentruje się na zmianie zachowań i nie wspomina o prawdopodobnie najbardziej oczywistym i znacznie bardziej rozpowszechnionym użyciu.
The Vee
5

Jeden wykonuje pisanie programu: program_name0 arg1 arg2 arg3 ....

Więc powłoka powinna już podzielić token, a pierwszy token jest już nazwą programu. I BTW, więc są te same wskaźniki po stronie programu i powłoki.

Myślę, że to była tylko sztuczka wygody (na samym początku) i, jak widać w innych odpowiedziach, była również bardzo przydatna, więc ta tradycja była kontynuowana i ustawiona jako API.

Giacomo Catenazzi
źródło
4

Zasadniczo argv zawiera nazwę programu, dzięki czemu można pisać komunikaty o błędach, takie jak prgm: file: No such file or directory, które można zaimplementować za pomocą czegoś takiego:

    fprintf( stderr, "%s: %s: No such file or directory\n", argv[0], argv[1] );
użytkownik628544
źródło
2

Innym przykładem zastosowania tego jest ten program, który zamienia się na ... sam, dopóki nie wpiszesz czegoś, co nie jest 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;
}

Oczywiście jest to wymyślony, choć interesujący przykład, ale myślę, że może mieć prawdziwe zastosowanie - na przykład samoregulujący plik binarny, który przepisuje własną przestrzeń pamięci przy użyciu nowej wersji siebie, którą pobrał lub zmienił.

Przykład:

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

7 | $

Źródło i więcej informacji .

kot
źródło
Gratulujemy osiągnięcia 1000.
G-Man
0

Ścieżka do programu jest argv[0]taka, aby program mógł pobierać pliki konfiguracyjne itp. Ze swojego katalogu instalacyjnego.
Bez tego byłoby to niemożliwe argv[0].

Bob Cook
źródło
2
To nie jest szczególnie dobre wytłumaczenie - nie ma powodu, dla którego nie moglibyśmy ustandaryzować czegoś takiego jak (char *path_to_program, char **argv, int argc)na przykład
moopet
AFAIK większość programów ciągnąć konfigurację ze standardowej lokalizacji ( ~/.<program>, /etc/<program, $XDG_CONFIG_HOME) i albo przyjąć parametr zmienić go lub posiada opcję kompilacji, które piecze w stałym do binarnego.
Xiong Chiamiov
0

ccache zachowuje się w ten sposób, aby naśladować różne wywołania do plików binarnych kompilatora. ccache to pamięć podręczna kompilacji - chodzi o to, aby nigdy nie skompilować dwukrotnie tego samego kodu źródłowego, ale zamiast tego zwrócić kod obiektu z pamięci podręcznej, jeśli to możliwe.

Ze strony podręcznika ccache „istnieją dwa sposoby korzystania z ccache. Możesz albo poprzedzić polecenia kompilacji ccache, albo możesz pozwolić ccache maskować się jako kompilator, tworząc dowiązanie symboliczne (zwane kompilatorem) do ccache. Pierwsza metoda jest najwygodniejszy, jeśli chcesz wypróbować pamięć podręczną lub użyć jej w niektórych konkretnych projektach. Druga metoda jest najbardziej przydatna, gdy chcesz używać pamięci podręcznej do wszystkich swoich kompilacji. ”

Metoda symlinks polega na uruchomieniu następujących poleceń:

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

... którego efektem jest umożliwienie ccache'owi przechwycenia wszelkich poleceń, które w innym przypadku trafiłyby do kompilatorów, umożliwiając w ten sposób zwrócenie buforowanego pliku lub przekazanie polecenia do kompilatora.

Adam J Richardson
źródło