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:

$ echo $HOME
/home/petr
$ echo $USER
petr
$ echo $$
2684
$ USER=root
$ echo $USER
root
Nastavením proměnné USER na root samozřejmě nezískáte práva uživatele root :-). Maximálně tím zmatete některé špatně napsané aplikace.

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:

$ ./env
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.

  1. /*------------------------------------------------*/
  2. /* 20zakladyI/options.h                           */
  3.  
  4. #ifndef _OPTIONS_H
  5. #define _OPTIONS_H
  6. #include <stdbool.h>
  7. struct options {
  8.         int speed;
  9.         bool help;
  10.         char * file;
  11.         bool messagesEnabled;
  12.         int messageLevel;
  13. };
  14.  
  15. void printOption(struct options Option);
  16. #endif
  17. /*------------------------------------------------*/

A ještě pomocná funkce, která strukturu vypíše.

  1. /*------------------------------------------------*/
  2. /* 20zakladyI/options.c                           */
  3.  
  4. #include <stdio.h>
  5. #include "options.h"
  6.  
  7. void printOption(struct options Option) {
  8.         printf(
  9.                 "Speed\t\t= %i\n"
  10.                 "Help\t\t%s\n"
  11.                 "File\t\t= %s\n"
  12.                 "Messsages\t%s\n"
  13.                 "Messages level\t= %i\n",
  14.                 Option.speed,
  15.                 Option.help ? "Enabled" : "Disabled",
  16.                 Option.file ? Option.file : "(null)",
  17.                 Option.messagesEnabled ? "Enabled" : "Disabled",
  18.                 Option.messageLevel
  19.         );
  20. }
  21. /*------------------------------------------------*/

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).

int getopt(int argc, char * const argv[], const char *optstring);

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.

  1. /*------------------------------------------------*/
  2. /* 20zakladyI/getopt.c                            */
  3.  
  4. #include <unistd.h>
  5. #include <stdio.h>
  6. #include <stdlib.h> /* atoi() */
  7. #include "options.h"
  8. #include <locale.h>
  9.  
  10.  
  11. int main (int argc, char * argv[]) {
  12.         int op, i;
  13.         struct options Option = { 0, false, NULL, false, 0};
  14.         char  *optstring = "+shf:m::";
  15.         //setlocale(LC_MESSAGES, "");
  16.         do {
  17.                 op = getopt(argc, argv, optstring);
  18.  
  19.                 switch(op) {
  20.                         case -1:  break;
  21.                         case '?': fprintf(stderr,"Unknown or invalid argument for %c\n",optopt); return 1;
  22.                         case ':': fprintf(stderr,"Missing argument for :\n"); return 2;
  23.                         case 'h': Option.help = true; break;
  24.                         case 's': Option.speed++; break;
  25.                         case 'f': Option.file = optarg; break;
  26.                         case 'm':
  27.                                  Option.messagesEnabled = true;
  28.                                  if(optarg != NULL) {
  29.                                         printf("optarg je %s\n", optarg);
  30.                                         Option.messageLevel = atoi(optarg);
  31.                                  }
  32.                                  break;
  33.                 }
  34.        
  35.         } while(op != -1);
  36.  
  37.         printOption(Option);
  38.  
  39.         for(i = optind; i < argc; i++) {
  40.                 printf("\t\t-- %s\n", argv[i]);
  41.         }
  42.  
  43.         return 0;
  44. }
  45. /*------------------------------------------------*/

Možný výstup z programu:

$ ./getopt
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.

  1. /*------------------------------------------------*/
  2. /* 20zakladyI/getoptlong.c                        */
  3.  
  4. #include <unistd.h>
  5. #include <getopt.h>
  6. #include <stdio.h>
  7. #include <stdlib.h> /* atoi() */
  8. #include "options.h"
  9. #include <locale.h>
  10.  
  11.  
  12. int main (int argc, char * argv[]) {
  13.         int op,i;
  14.         struct options Option = { 0, false, NULL, false, 0};
  15.         char  *optstring = "+shf:m::";
  16.         const struct option longopts[] = {
  17.                 { "speed",      no_argument,       NULL, 'S' },
  18.                 { "help",       no_argument,       NULL, 'H' },
  19.                 { "file",       required_argument, NULL, 'F' },
  20.                 { "messages",   optional_argument, NULL, 'M' },
  21.                 { NULL,         no_argument,       NULL,  0  }
  22.         };
  23.  
  24.         //setlocale(LC_MESSAGES, "");
  25.  
  26.         do {
  27.                 op = getopt_long(argc, argv, optstring, longopts, NULL);
  28.  
  29.                 switch(op) {
  30.                         case -1:  break;
  31.                         case '?': fprintf(stderr,"Unknown or invalid argument for %c\n",optopt); return 1;
  32.                         case ':': fprintf(stderr,"Missing argument for :\n"); return 2;
  33.                         case 'H':
  34.                         case 'h': Option.help = true; break;
  35.                         case 'S':
  36.                         case 's': Option.speed++; break;
  37.                         case 'F':
  38.                         case 'f': Option.file = optarg; break;
  39.                         case 'M':
  40.                         case 'm':
  41.                                  Option.messagesEnabled = true;
  42.                                  if(optarg != NULL) {
  43.                                         printf("optarg je %s\n", optarg);
  44.                                         Option.messageLevel = atoi(optarg);
  45.                                  }
  46.                                  break;
  47.                 }
  48.        
  49.         } while(op != -1);
  50.  
  51.         printOption(Option);
  52.  
  53.         for(i = optind; i < argc; i++) {
  54.                 printf("\t\t-- %s\n", argv[i]);
  55.         }
  56.  
  57.         return 0;
  58. }
  59. /*------------------------------------------------*/

Možný výstup:

$ ./getoptlong -shm3 -f test.txt
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().

int flock(int fd, int operation);

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.

  1. /*------------------------------------------------*/
  2. /* 20zakladyI/flock.c                             */
  3.  
  4. #include <stdio.h>
  5. #include <stdlib.h>
  6. #include <stdbool.h>
  7. #include <errno.h>
  8. #include <unistd.h>
  9. #include <sys/file.h>
  10.  
  11. int flock(int fd, int operation);
  12.  
  13.  
  14. int main(int argc, char *argv[]) {
  15.         bool write = false;
  16.         int opt;
  17.         char *filename = NULL;
  18.         do {
  19.                 opt = getopt(argc, argv, "+wf:");
  20.                 switch(opt) {
  21.                         case -1: break;
  22.                         case '?':
  23.                         case ':':
  24.                                 exit(1);
  25.                         case 'w': write = true; break;
  26.                         case 'f': filename = optarg; break;
  27.                        
  28.                 }
  29.         } while (opt != -1);
  30.  
  31.         if(!filename) {
  32.                 fprintf(stderr,"Zadejte jméno souboru pomocí volby -f \"jmeno souboru\"\n");
  33.                 exit(1);
  34.         }      
  35.         printf("Otevírám soubor %s pro %s\n", filename, write ? "zápis" : "čtení");
  36.         FILE* f = fopen(filename,         write ? "w"   : "r");
  37.         if(f == NULL) {
  38.                 fprintf(stderr, "Soubor %s neexistuje.\n", filename);
  39.                 exit(1);
  40.         }
  41.         printf("Získávám zámek pro %s\n", write ? "zápis" : "čtení");
  42.         int result = flock(fileno(f),     write ? LOCK_EX : LOCK_SH);
  43.         if(result) {
  44.                 perror(NULL);
  45.                 exit(1);
  46.         }
  47.  
  48.         printf("Čekám na stisk ENTERu ");
  49.         (void) getchar();
  50.  
  51.         printf("Uvolňuji zámek a zavírám soubor\n");
  52.         flock(fileno(f), LOCK_UN);
  53.         fclose(f); /* taky by uvolnil zamek */
  54.        
  55.         return 0;
  56. }

Abyste si vyzkoušeli zamykání, spoušťejte program na dvou terminálech …

terminal1$ ./flock -w -f test.txt
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.

  1. /*------------------------------------------------*/
  2. /* 20zakladyI/tmpfile.c                           */
  3.  
  4. #include <stdio.h>
  5. #include <string.h>
  6.  
  7. int main(void) {
  8.  
  9.         FILE * f = tmpfile();
  10.         char * zprava = "Hello World\n";
  11.         char retezec[255];
  12.         int nacteno;
  13.  
  14.         fwrite(zprava, 1, strlen(zprava), f);
  15.         rewind(f);
  16.         nacteno = fread(retezec, 1, sizeof(retezec),f);
  17.         retezec[nacteno] = '\0';
  18.         printf("Nacteno: %s\n", retezec);
  19.         fclose(f);
  20.        
  21.         tmpnam(retezec);
  22.         printf("Tmpname = %s\n", retezec);
  23.  
  24.         return 0;
  25. }
  26. /*------------------------------------------------*/

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.

Komentář Hlášení chyby
Created: 23.8.2014
Last updated: 10.10.2014
Tato stánka používá ke svému běhu cookies, díky kterým je možné monitorovat, co tu provádíte (ne že bych to bez cookies nezvládl). Také vás tu bude špehovat google analytics. Jestli si myslíte, že je to problém, vypněte si cookies ve vašem prohlížeči, nebo odejděte a už se nevracejte :-). Prohlížením tohoto webu souhlasíte s používáním cookies. Dozvědět se více..