Odpalanie programów

Funkcji z rodziny exec* używa się dość nawet często, więc wypada znać je conieco.
Wyexecowanie 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.