Funkcji z rodziny exec
używa się dość nawet często, więc wypada
znać je conieco.
Wyexec
owanie jakiegoś polecenia powoduje zastąpienie przestrzeni
adresowej bieżącego procesu przez proces nowo utworzony. Mówiąc po ludzku, po
wywołaniu exec
nie wykona się już ani jedna instrukcja bieżącego
programu, o ile exec
wykona się poprawnie (co można ładnie
wykorzystać do kontroli błędów). Dokładniej wyjaśnię to za chwilę na
przykładzie.
Funkcji uruchamiających programy jest parę - w sumie sześć. Można
je podzielić na dwie grupy: execl*
i execv*
.
Pierwsze jako argumenty przyjmują po kolei char* (czyli stringi),
drugie przyjmują tablice wskaźników (czyli tablice stringów). W każdym
przypadku ostatnim wskaźnikiem musi być NULL. Może w końcu
obiecany przykład:
int main(void) { execl("/bin/ls", "ls", "-l", "/", NULL /* parametry sie koncza*/); /* jesli execl() pojdzie OK, to nic ponizej sie juz nie wykona */ perror("Nie znalazlem /bin/ls"); return 1; } |
int main(void) { char *args[4]; /* mozna to wladowac do deklaracji, ale tu lepiej widac, ze to tablica */ args[0] = "ls"; args[1] = "-l"; args[2] = "/"; args[3] = NULL; /* musi byc, zeby sie dalo ustalic, ile jest parametrow */ execv("/bin/ls", args); /* jesli execv() pojdzie OK, to nic ponizej sie juz nie wykona */ perror("Nie znalazlem /bin/ls"); return 1; } |
Jak widać po uruchomieniu, komentarz Nie znalazlem /bin/ls się nie
pojawia, właśnie dlatego, że cała pamięć naszego programu została zastąpiona
przez ls. To teraz wypadałoby powiedzieć, co tam się dzieje.
Znaczenie parametrów jest dokładnie takie samo, jak w tablicy char
**argv
, czyli zerowy parametr mówi, jak się program "nazywa" (ja
wstawiałem ls, ale nic nie stoi na przeszkodzie, żeby wstawić na
przykład bardzo glupia nazwa dla programu ;]), pierwszy parametr to
pierwszy parametr użyteczny dla programu itd. Czyli w naszym przypadku
uruchamiany jest ls -l /. Na końcu zawsze musi się znaleźć wskaźnik
NULL, żeby exec*()
mógł ustalić, gdzie się parametry
kończą.
A po co w ogóle są funkcje z rodziny execv*()
, skoro w taki
dziwny sposób ich się używa? Bo się przydają, gdy nie wiesz z góry, ile
parametrów będziesz chciał podać dalej do programu. I w sumie chyba tylko do
tego :)
Notka: Jeśli chcesz wykonać jakiś program, musisz mieć prawo
wykonywania do pliku uruchamiającego. Jeśli jednak to jest skrypt (na przykład
shella), to nie musisz mieć tego prawa, bo możesz go odpalić przez
execl("/bin/sh", "sh", "skrypt.sh", NULL);
. Ale tu już nie
odpalasz samego skryptu, ale shell z argumentem skrypt.sh.
Tym się z grubsza różnią rodziny execl*
i execv*
.
W rodzinie execl*
są funkcje execl()
,
execlp()
i execle()
(w rodzinie execv*
odpowiednio execv()
, execvp()
i execve()
). Pierwsza wykonuje program z podanymi parametrami,
ale wymaga podania ścieżki do pliku wykonywalnego (chyba, że plik jest
w bieżącym katalogu, wtedy sama nazwa pliku bez katalogów wystarczy). Lepsza
jest funkcja execlp()
, bo przeszukuje katalogi wymienione
w zmiennej $PATH w poszukiwaniu polecenia. Została
jeszcze funkcja execle()
. Ta skubana potrafi zmienić zmienne
środowiskowe, które muszą być podane jako tablica stringów zakończona
NULLem (identyczny komentarz do execve()
) za
NULLowym wskaźnikiem oznaczającym koniec argumentów. Zmienne są
podawane w formie NAZWA_ZMIENNEJ=wartosc, moze byc ze spacjami.
Niestety, funkcja nie przeszukuje katalogów ze zmiennej
$PATH.
int main(void) { char *env[] = {"PATH=/bin:/usr/bin:/usr/X11R6/bin:/usr/local/bin:/opt/bin", "XXX=smieci, ktore nic nas nie obchodza", NULL}; execle("/usr/bin/env", "/usr/bin/env", NULL, env); /* jesli execv() pojdzie OK, to nic ponizej sie juz nie wykona */ perror("Nie znalazlem /usr/bin/env"); return 1; } |
Nie wykluczam, że env masz gdzie indziej, wtedy /usr/bin/env musisz zamienić na prawidłowe położenie env. Ale raczej jest to /usr/bin.
Poza rodziną exec*
jest jeszcze jedna drobna funkcyjka, zwąca się
int system(char * komenda)
. Uruchamia
ona jako osobny proces podaną komendę (przekazując cały parametr do sh
-c, co daje możliwość wykonywania skryptów od ręki) i czeka, aż powłoka
się zakończy. Przydatne do czyszczenia ekranu.
/* ... */ #ifdef unix /* pod un*xami kompilator ma definicje "unix" */ # include <stdlib.h> /* system() */ #else # include <conio.h> /* clrscr() */ #endif /* ... */ int main(void) { /* przenosne czyszczenie ekranu :) */ #ifdef unix system("clear"); #else clrscr(); #endif puts("Lista otwartych edytorów vim:"); system("ps -A | grep vim"); return 0; } |
Tak przy okazji, w kompilatorach pod systemami uniksowymi zdefiniowana jest #definicja unix, której nie ma z oczywistych względów na platformie DOS/Windows. Daje to możliwość skonstruowania zgrabnego czyszczenia ekranu, które nie wymaga zmieniania kodu przy przenoszeniu programu między Windows i Linuksem, co zostało pokazane w przykładzie.