eval
i exec
oba są wbudowane w polecenia bash (1), które wykonują polecenia.
Widzę też, że exec
ma kilka opcji, ale czy to jedyna różnica? Co dzieje się z ich kontekstem?
eval
i exec
oba są wbudowane w polecenia bash (1), które wykonują polecenia.
Widzę też, że exec
ma kilka opcji, ale czy to jedyna różnica? Co dzieje się z ich kontekstem?
Odpowiedzi:
eval
i exec
są zupełnie innymi zwierzętami. (Oprócz faktu, że oba będą uruchamiać polecenia, ale robi to wszystko, co robisz w powłoce).
$ help exec
exec: exec [-cl] [-a name] [command [arguments ...]] [redirection ...]
Replace the shell with the given command.
To exec cmd
, co robi, jest dokładnie takie samo, jak tylko uruchamianie cmd
, z tą różnicą, że bieżąca powłoka jest zastępowana poleceniem, zamiast uruchamiania osobnego procesu. Uruchamianie say /bin/ls
wywoła wewnętrznie fork()
proces potomny, a następnie exec()
proces potomny /bin/ls
. exec /bin/ls
z drugiej strony nie rozwidli się, a jedynie zastępuje powłokę.
Porównać:
$ bash -c 'echo $$ ; ls -l /proc/self ; echo foo'
7218
lrwxrwxrwx 1 root root 0 Jun 30 16:49 /proc/self -> 7219
foo
z
$ bash -c 'echo $$ ; exec ls -l /proc/self ; echo foo'
7217
lrwxrwxrwx 1 root root 0 Jun 30 16:49 /proc/self -> 7217
echo $$
wypisuje PID powłoki, którą uruchomiłem, a listing /proc/self
daje nam PID tego, ls
który został uruchomiony z powłoki. Zwykle identyfikatory procesów są różne, ale exec
w powłoce ls
mają ten sam identyfikator procesu. Ponadto następujące polecenie exec
nie zostało uruchomione, ponieważ powłoka została zastąpiona.
Z drugiej strony:
$ help eval
eval: eval [arg ...]
Execute arguments as a shell command.
eval
uruchomi argumenty jako polecenie w bieżącej powłoce. Innymi słowy eval foo bar
to to samo co sprawiedliwe foo bar
. Ale zmienne zostaną rozszerzone przed wykonaniem, abyśmy mogli wykonywać polecenia zapisane w zmiennych powłoki:
$ unset bar
$ cmd="bar=foo"
$ eval "$cmd"
$ echo "$bar"
foo
To będzie nie utworzy proces potomny, więc zmienna jest ustawiona w bieżącej powłoki. (Oczywiście eval /bin/ls
stworzy proces potomny, tak samo jak zwykły stary /bin/ls
).
Lub możemy mieć polecenie, które wyprowadza polecenia powłoki. Uruchamianie ssh-agent
uruchamia agenta w tle i generuje wiązkę przypisań zmiennych, które mogą być ustawione w bieżącej powłoce i używane przez procesy potomne ( ssh
polecenia, które uruchomisz). Dlatego ssh-agent
można rozpocząć od:
eval $(ssh-agent)
Bieżąca powłoka pobierze zmienne dla innych poleceń do dziedziczenia.
Oczywiście, jeśli zmienna cmd
zawiera coś podobnego rm -rf $HOME
, to uruchamianie eval "$cmd"
nie byłoby czymś, co chciałbyś zrobić. Nawet takie rzeczy jak zmiana dowodzenia wewnątrz łańcucha będą przetwarzane, więc trzeba naprawdę mieć pewność, że dane wejściowe eval
są bezpieczne przed użyciem.
Często można uniknąć, eval
a nawet przypadkowo pomieszać kod i dane w niewłaściwy sposób.
eval
w pierwszej kolejności również do tej odpowiedzi. Rzeczy takie jak pośrednio modyfikujące zmienne można wykonać w wielu powłokach poprzez declare
/ typeset
/ nameref
i rozszerzenia takie jak ${!var}
, więc użyłbym ich zamiast, eval
chyba że naprawdę musiałem tego unikać.
exec
nie tworzy nowego procesu. To zastępuje bieżący proces z nową komendę. Jeśli zrobiłeś to w wierszu poleceń, to skutecznie zakończy sesję powłoki (a może wylogujesz się lub zamkniesz okno terminala!)
na przykład
ksh% bash
bash-4.2$ exec /bin/echo hello
hello
ksh%
Oto jestem ksh
(moja normalna powłoka). Zaczynam, bash
a potem w środku exec /bin/echo
. Widzimy, że wróciłem ksh
później, ponieważ bash
proces został zastąpiony przez /bin/echo
.
exec
służy do zastąpienia bieżącego procesu powłoki nowymi i obsługi przekierowań / deskryptorów plików, jeśli nie podano żadnej komendy. eval
służy do oceny ciągów jako poleceń. Oba mogą być użyte do zbudowania i wykonania polecenia z argumentami znanymi w czasie wykonywania, ale exec
oprócz wykonywania poleceń zastępuje proces bieżącej powłoki.
Składnia:
exec [-cl] [-a name] [command [arguments]]
Zgodnie z instrukcją, jeśli istnieje polecenie określone to wbudowane
... zastępuje powłokę. Nie jest tworzony nowy proces. Argumenty stają się argumentami do dowodzenia.
Innymi słowy, jeśli działałeś bash
z PID 1234 i jeśli miałbyś działać exec top -u root
w tej powłoce, top
polecenie będzie miało PID 1234 i zastąpi proces powłoki.
Gdzie to jest przydatne? W czymś znanym jako skrypty opakowujące. Takie skrypty budują zestawy argumentów lub podejmują określone decyzje dotyczące zmiennych, które mają zostać przekazane do środowiska, a następnie wykorzystują je exec
do zastąpienia dowolną określoną komendą i oczywiście podając te same argumenty, które zbudował skrypt opakowania.
Instrukcja podaje również, że:
Jeśli polecenie nie zostanie określone, wszelkie przekierowania zostaną zastosowane w bieżącej powłoce
To pozwala nam przekierowywać wszystko z bieżących strumieni wyjściowych powłok do pliku. Może to być przydatne do rejestrowania lub filtrowania, gdzie nie chcesz widzieć stdout
poleceń, a jedynie stderr
. Na przykład tak:
bash-4.3$ exec 3>&1
bash-4.3$ exec > test_redirect.txt
bash-4.3$ date
bash-4.3$ echo "HELLO WORLD"
bash-4.3$ exec >&3
bash-4.3$ cat test_redirect.txt
2017年 05月 20日 星期六 05:01:51 MDT
HELLO WORLD
To zachowanie sprawia, że jest to przydatne do logowania się w skryptach powłoki , przekierowywania strumieni do oddzielnych plików lub procesów oraz innych zabawnych rzeczy z deskryptorami plików.
Na poziomie kodu źródłowego przynajmniej dla bash
wersji 4.3 exec
wbudowany jest zdefiniowany w builtins/exec.def
. Analizuje otrzymane polecenia, a jeśli takie istnieją, przekazuje rzeczy do shell_execve()
funkcji zdefiniowanej w execute_cmd.c
pliku.
Krótko mówiąc, istnieje rodzina exec
poleceń w języku programowania C i shell_execve()
jest to po prostu funkcja otoki execve
:
/* Call execve (), handling interpreting shell scripts, and handling
exec failures. */
int
shell_execve (command, args, env)
char *command;
char **args, **env;
{
Ręczne ustawienia bash 4.3 (podkreślenie dodane przeze mnie):
Argumenty są odczytywane i łączone w jedno polecenie. To polecenie jest następnie odczytywane i wykonywane przez powłokę , a jego status wyjścia jest zwracany jako wartość eval.
Zauważ, że nie ma zamiany procesu. W przeciwieństwie do tego, exec
gdzie celem jest symulacja execve()
funkcjonalności, eval
wbudowany służy tylko do „oceny” argumentów, tak jakby użytkownik wpisał je w wierszu poleceń. W związku z tym tworzone są nowe procesy.
Gdzie to może być przydatne? Jak zauważył Gilles w tej odpowiedzi : „... eval nie jest używane bardzo często. W niektórych powłokach najczęstszym zastosowaniem jest uzyskanie wartości zmiennej, której nazwa jest znana dopiero po uruchomieniu”. Osobiście użyłem go w kilku skryptach na Ubuntu, gdzie konieczne było wykonanie / ocena polecenia w oparciu o konkretny obszar roboczy, z którego użytkownik aktualnie korzysta.
Na poziomie kodu źródłowego jest on zdefiniowany builtins/eval.def
i przekazuje przeanalizowany ciąg wejściowy do evalstring()
funkcji.
Między innymi eval
może przypisywać zmienne, które pozostają w bieżącym środowisku wykonywania powłoki, podczas gdy exec
nie może:
$ eval x=42
$ echo $x
42
$ exec x=42
bash: exec: x=42: not found
tworząc nowy proces potomny, uruchom argumenty i zwróć status wyjścia.
Oh, co? Chodzi o eval
to, że w żaden sposób nie tworzy ono procesu potomnego. Jeśli zrobię
eval "cd /tmp"
w powłoce, a następnie bieżąca powłoka zmieni katalog. Ani też nie exec
tworzy nowego procesu potomnego, zamiast tego zmienia bieżący plik wykonywalny (czyli powłokę) dla danego; identyfikator procesu (i otwarte pliki i inne rzeczy) pozostają takie same. W przeciwieństwie do eval
, an exec
nie powróci do powłoki wywołującej, chyba że exec
sama zawiedzie z powodu niemożności znalezienia lub załadowania pliku wykonywalnego lub śmierci w wyniku problemów z rozszerzaniem argumentów.
eval
w zasadzie interpretuje swoje argumenty jako ciąg po konkatenacji, mianowicie wykona dodatkową warstwę interpretacji symboli wieloznacznych i podziału argumentów. exec
nic takiego nie robi.
Ocena
Te prace:
$ echo hi
hi
$ eval "echo hi"
hi
$ exec echo hi
hi
Nie powodują one jednak:
$ exec "echo hi"
bash: exec: echo hi: not found
$ "echo hi"
bash: echo hi: command not found
Zastąp proces obrazu
Ten przykład pokazuje, jak exec
zastępuje obraz procesu wywoływania:
# Get PID of current shell
sh$ echo $$
1234
# Enter a subshell with PID 5678
sh$ sh
# Check PID of subshell
sh-subshell$ echo $$
5678
# Run exec
sh-subshell$ exec echo $$
5678
# We are back in our original shell!
sh$ echo $$
1234
Zauważ, że exec echo $$
działał z PID podpowłoki! Co więcej, po jego zakończeniu wróciliśmy do naszej oryginalnej sh$
powłoki.
Z drugiej strony, eval
czy nie zastąpić obraz procesu. Raczej uruchamia podane polecenie, tak jak normalnie w samej powłoce. (Oczywiście, jeśli uruchomisz polecenie wymagające odrodzenia procesu ... robi to po prostu!)
sh$ echo $$
1234
sh$ sh
sh-subshell$ echo $$
5678
sh-subshell$ eval echo $$
5678
# We are still in the subshell!
sh-subshell$ echo $$
5678
exec
)