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.