Základní linuxové funkce I
V této (a další) kapitole vám představím některé užitečné funkce, které by měl mít v základní výbavě každý linuxový programátor. Je to můj osobní výběr, někdo jiný by určitě vybral jiné funkce, ale sem si jistý, že vás některé z těcho funkcí zaujmou :-).
Proměnné prostředí (env)
Určitě znáte proměnné prostředí a víte, jak se dají v Linuxu používat. Například:
/home/petr
$ echo $USER
petr
$ echo $$
2684
$ USER=root
$ echo $USER
root
Na příkladu vidíte, že se na systémové proměnné nedá moc spolehnout. Ale není špatné pužít například $USER jako defaulní login pro přihlašovaní (s heslem). Proměnná $$ obsahuje ID procesu.
K získání proměnných prostředí můžete použít nestandardní třetí parametr funkce main()
,
nebo funkci getenv()
. Ke změně proměnné slouží funkce setenv()
, nebo starší
putenv()
a ke smazání funkce unsetenv()
.
Nastavení proměnné na prázdný řetězec a její smazání není totéž. Funkce putenv()
použijte
na systémech, které nemají setenv()
.
/* 20zakladyI/env.c */
#include <stdio.h>
#include <stdlib.h>
static void printVariable(char *name, char *value) {
printf("Variable %s is %s\n", name, value == NULL ? "not set." : value);
}
int main(int arg, char *argv[], char * env[]) {
int i = 0;
char *user;
while(env[i++]) {
printf("%s\n", env[i-1]);
if(i == 10) { printf("...\n"); break; }
}
user = getenv("USER");
printVariable("USER", user);
i = setenv("USER", "root", 1);
if(i) { perror("setenv");exit(1); }
user = getenv("USER");
printVariable("USER", user);
i = unsetenv("USER");
if(i) { printf("Nelze odstranit promennou.\n"); }
user = getenv("USER");
printVariable("USER", user);
return 0;
}
/*------------------------------------------------*/
Výstup z programu:
LESSKEY=/etc/lesskey.bin
XDG_VTNR=7
MANPATH=/usr/local/man:/usr/share/man
NNTPSERVER=news
SSH_AGENT_PID=1617
KDE_MULTIHEAD=false
XDG_SESSION_ID=1
DM_CONTROL=/var/run/xdmctl
HOSTNAME=linux-nrse.site
XKEYSYMDB=/usr/X11R6/lib/X11/XKeysymDB
...
Variable USER is root
Variable USER is root
Variable USER is not set.
Pamatujte si, že každý proces pracuje s vlastní kopií uživatelského prostoru, tedy i s kopií systémových proměnných. Po skončení programu je tato kopie smazána a vy se dostanete zase do shellu s původními hodnotami uživatelských proměnných (program je nezmění).
Zpracovávání argumentů příkazové řádky
Už víte, jak získat argumenty příkazové řádky. Teď vám ukáži dvě
funkce, které vám pomohou parsovat argumenty ve formátu -a, -f filename, --file=filename
atp.
Pro příklady jsem si vytvořil strukturu struct options
,
kterou budete moci nastavovat přes parametry příkazové řádky.
A ještě pomocná funkce, která strukturu vypíše.
- /*------------------------------------------------*/
- /* 20zakladyI/options.c */
- #include <stdio.h>
- #include "options.h"
- "Speed\t\t= %i\n"
- "Help\t\t%s\n"
- "File\t\t= %s\n"
- "Messsages\t%s\n"
- "Messages level\t= %i\n",
- Option.speed,
- Option.help ? "Enabled" : "Disabled",
- Option.file ? Option.file : "(null)",
- Option.messagesEnabled ? "Enabled" : "Disabled",
- Option.messageLevel
- );
- }
- /*------------------------------------------------*/
Funkce getopt
První funkce pro práci s přepínači, kterou popíši, je funkce getopt()
.
Tato funkce je přenositelná na více operačních systémech,
ale umí zpracovávat jen krátké volby/přepínače (-a
).
První dva argumenty jsou ty, co dostává funkce main()
.
Poslední argument specifikuje volby, kterým má váš program pracovat.
Pokud optstring začíná znakem +
, bude se funkce getopt()
chovat podle POSIX standardu (bude tedy více přenositelná). Konkrétně to znamená, že když
narazí na první argument z příkazové řádky, který není volbou, zastaví hledání dalších
voleb. Bez +
by je přeskočil a pokračoval v hledání dál.
Další znaky jsou názvy voleb. Pokud je za nějakým znakem dvojtečka, očekává volba mezeru a nějaký argument. Pokud jsou tam dvě dvojtečky, argument je nepovinný. Nepovinný argument se musí psát za volbu bez mezery (jinak by nebylo jasné, jestli je to nepovinný argument, nebo jen nějaký další nesouvisející argument). Uvidíte v příkladu.
Argumenty ukládá funkce getopt()
do globální proměnné optarg.
Pokud getopt()
narazí na neznámou volbu, vrátí '?' a uloží volbu do proměnné
optopt. Jinak vrací nalezenou volbu. Pokud uvedete dvojtečku
v optstring kde nemá být (třeba jako první znak), vrátí getopt()
dvojtečku.
A nakonec, pokud getopt()
projde všechny volby, vrátí -1.
- /*------------------------------------------------*/
- /* 20zakladyI/getopt.c */
- #include <unistd.h>
- #include <stdio.h>
- #include <stdlib.h> /* atoi() */
- #include "options.h"
- #include <locale.h>
- //setlocale(LC_MESSAGES, "");
- do {
- op = getopt(argc, argv, optstring);
- }
- }
- printOption(Option);
- }
- }
- /*------------------------------------------------*/
Možný výstup z programu:
Speed = 0
Help Disabled
File = (null)
Messsages Disabled
Messages level = 0
$ ./getopt -s -h -f test.txt -m
Speed = 1
Help Enabled
File = test.txt
Messsages Enabled
Messages level = 0
$ ./getopt -ssss -m33
optarg je 33
Speed = 4
Help Disabled
File = (null)
Messsages Enabled
Messages level = 33
$ ./getopt -f
./getopt: option requires an argument -- 'f'
Unknown or invalid argument for f
$ ./getopt -shm3 -f test.txt
optarg je 3
Speed = 1
Help Enabled
File = test.txt
Messsages Enabled
Messages level = 3
$ ./getopt -shm3 neco co uz neni volba -f test.txt
optarg je 3
Speed = 1
Help Enabled
File = (null)
Messsages Enabled
Messages level = 3
-- neco
-- co
-- uz
-- neni
-- volba
-- -f
-- test.txt
Všiměte si, že getopt()
v případě problému sám vypíše chybové hlášení.
Tomu se dá zabránit, když nastavíte globální proměnnou opterr na 0.
Funkce getopt_long
Funkce getopt_long()
funguje podobně jako getopt()
,
jenom umí navíc zpracovat dlouhé volby ve formátu
--file soubor.txt, --file=soubor.txt
atp.
Dlouhé volby se předávají v poli typu struct option longopts
.
Prvním členem této funkce je název volby, druhý argument určuje, zda má volba
argument (a zda je povinný), třetí argument není zajímavý, tak bude NULL a čtvrtý
argument určuje, co funkce getopt_long()
vrací.
Normálně se to dělá tak, že čtvrtý argument odpovídá odpovídajícím krátkým volbám z optstring, ale abych odlišil optstring od čtvrého argumentu z longopts, uvedl jsem velká písmena.
- /*------------------------------------------------*/
- /* 20zakladyI/getoptlong.c */
- #include <unistd.h>
- #include <getopt.h>
- #include <stdio.h>
- #include <stdlib.h> /* atoi() */
- #include "options.h"
- #include <locale.h>
- { "speed", no_argument, NULL, 'S' },
- { "help", no_argument, NULL, 'H' },
- { "file", required_argument, NULL, 'F' },
- { "messages", optional_argument, NULL, 'M' },
- { NULL, no_argument, NULL, 0 }
- };
- //setlocale(LC_MESSAGES, "");
- do {
- op = getopt_long(argc, argv, optstring, longopts, NULL);
- }
- }
- printOption(Option);
- }
- }
- /*------------------------------------------------*/
Možný výstup:
optarg je 3
Speed = 1
Help Enabled
File = test.txt
Messsages Enabled
Messages level = 3
$ ./getoptlong -shm3 -f test.txt --file=test2.txt
optarg je 3
Speed = 1
Help Enabled
File = test2.txt
Messsages Enabled
Messages level = 3
$ ./getoptlong -shm3 -f test.txt --file=test2.txt --messages=4
optarg je 3
optarg je 4
Speed = 1
Help Enabled
File = test2.txt
Messsages Enabled
Messages level = 4
$ ./getoptlong -shm3 -f test.txt --file=test2.txt --messages 4
optarg je 3
Speed = 1
Help Enabled
File = test2.txt
Messsages Enabled
Messages level = 3
-- 4
Jak vidíte, dlouhé volbě s volitelným argumentem lze předat argument jen pomocí rovnítka.
Zamykání souborů (flock)
Pokud chcete něco zapisovat nebo číst ze souboru, mohlo by dojít ke katastrofě, pokud by ve stejný okamžik soubor měnil nějaký jiný program. Tomu se dá zabránit zamykáním.
Soubor lze zamknout funkcí flock()
.
Soubor můžete zamknout jako LOCK_SH
(shared). Tento zámek může získat více procesů.
Nebo jako LOCK_EX
(exclusive). Tento zámek může vlastnit jen jeden proces.
Pokud je tedy soubor zamknutý pomocí LOCK_EX
, nemůžete získat ani zámek LOCK_SH
.
A LOCK_EX
nemůžete získat, když má někdo zámek LOCK_SH
. EX je prostě opravdu exkluzivní :-).
LOCK_SH
se obvykle používá pro čtení souboru, LOCK_EX
pro zápis.
Zámek se odstraňuje pomocí operace LOCK_UN
, nebo tak, že souborový proud uzavřete.
Funkce flock()
pracuje s deskriptorem souboru. Pokud máte k dispozici odkaz na strukturu FILE,
deskriptor souboru získáte pomocí funkce fileno()
, viz příklad.
Abyste si vyzkoušeli zamykání, spoušťejte program na dvou terminálech …
Otevírám soubor test.txt pro zápis
Získávám zámek pro zápis
Čekám na stisk ENTERu
terminal2$ ./flock -w -f test.txt
Otevírám soubor test.txt pro zápis
Získávám zámek pro zápis
# stisk Entru na terminal1
Uvolňuji zámek a zavírám soubor
terminal1$
# terminal2:
Získávám zámek pro zápis
Čekám na stisk ENTERu
Jak vidíte (nebo spíš tušíte), funkce flock()
se zablokuje,
dokud nemůže získat zámek. Pokud byste měli zájem o neblokující volání,
podívejte se do manuálové stránky na LOCK_NB
. Neblokující volání
buď získá zámek, nebo ihned vrátí chybu (na nic nečeká).
Kdybyste náhodou psali nějakou vysokozátěžovou aplikaci a chtěli byste zamykat jen určité části souboru, podívejte se na File Locks.
Zamknutí souboru odradí od jeho čtení/zápisu jenom programy, které také používají flock()
!
Zamykání také nefunguje na NFS (Network File System).
Dočasné soubory (tmpfile)
Pokud potřebujete vytvořit dočasný soubor, použijte funkci tmpfile()
.
Funkce vám zajistí, že otevře nový, neexistující soubor (nehrozí, že byste
omylem otevřeli existující soubor).
Můžete taky použít funkci tmpnam()
pro získání jména souboru, který neexistuje
a otevřít si ho sami, ale bacha! Mezi voláním tmpnam()
a otevřením souboru je dostatek
prostoru na to, aby někdo soubor takového jména vytvořil. A to by mohl být velký (bezpečnostní) problém.
Používejte proto pro otevření takového souboru open()
s volbou O_EXCL.
- /*------------------------------------------------*/
- /* 20zakladyI/tmpfile.c */
- #include <stdio.h>
- #include <string.h>
- int nacteno;
- retezec[nacteno] = '\0';
- }
- /*------------------------------------------------*/
Výstup z programu:
Nacteno: Hello World Tmpname = /tmp/filerMcAZN
Dočasný soubor se po skončení programu (nebo uzavření deskriptoru souboru) smaže, takže ho nikde nehledejte ;-).
Existuje ještě funkce tmpfile64(void)
, která otevře soubor,
který může být větší než 2^31 bajtů i na 32-bitových počítačích.