Vianoce v ITnetwork sú tu! Dobí si teraz kredity a získaj až 80 % extra kreditov na e-learningové kurzy ZADARMO. Zisti viac.
Hľadáme nové posily do ITnetwork tímu. Pozri sa na voľné pozície a pridaj sa k najagilnejšej firme na trhu - Viac informácií.

8. diel - Céčko a Linux - getopt_long a shell

V minulej lekcii, Céčko a Linux - Prepínače a getopt , sme si ukázali, ako je možné si zjednodušiť prácu s funkciou getopt. Tá ale nedokáže všetko, čo sme urobili v príklade ručne - napríklad dlhé argumenty (--argument). Na druhú stranu zase môže byť poradie argumentov ľubovoľné, čo je veľmi pozitívnu vlastnosť.

Keďže sme ale na Linuxe, tak by sme chceli pokiaľ možno dodržiavať GNU štýl argumentov. Riešenie sa ponúka v podobe funkcie getopt_long, ktorá ponúka rovnaké výhody a ešte niečo navyše a zároveň umožňuje spracovanie dlhých argumentov.

Getopt_long

Ak chceme túto funkciu, jej deklaráciu nájdeme v hlavičkovom súbore getopt.h. Jedná sa GNU rozšírenia, takže výsledný program nie je prenositeľný mimo Linuxové operačné systémy.

Výhoda tejto funkcie je tá, že krátke argumenty spracováva úplne rovnakým spôsobom, ako getopt - nemusíme sa teda učiť úplne nové rozhranie. Okrem toho ale pribúdajú dva nové argumenty. Prvá je pole štruktúr a druhý je ukazovateľ na int, kam funkcia uloží index štruktúry práve nájdeného dlhého argumentu.

Štruktúra pre dlhé argumenty vyzerá nasledovne:

struct option
{
  const char *name;
  int         has_arg;
  int        *flag;
  int         val;
};

Premenná name je string (typicky použijeme reťazcový literál, ale tiež si môžeme mená vygenerovať za behu programu a priradiť obyčajný string) obsahujúca meno dlhého argumentu. Ak teda chceme mať argument --help, priradíme do name "help".

has_arg definuje, či má argument hodnotu alebo nie. Možnosti sú: no_argument, optional_argument a required_argument. Čo ktorá hodnota znamená, je myslím jasné. Len by som možno poukázal na možno trochu zmätené pomenovanie. V angličtine sa argumentom hovorí option a hodnotám argument. V slovenčine bude asi viac označenie, ale ja som sa stretol hlavne s označením argument, takže to používam. Snáď to bude jasné z kontextu.

Ďalšie dve premenné určujú, čo sa má stať, keď je argument nájdený. Ak je vo flag hodnota NULL, tak potom val je hodnota, ktorá je vrátená funkciou getopt_long. Väčšinou potom bude rovnaká, ako krátka verzia argumentu a obe verzie tak budú spracovávané rovnakým kódom vo switchu. Ak je vo flag adresa ukazovatele, je do tej uložená hodnota vo val (čo bude typicky 0 alebo 1).

Ukážka

Pre ukážku si urobíme jednoduchý program. Jeho jedinou funkciou bude generovať dáta (v praxi by sa to ale určite dalo ľahko rozšíriť a nejako použiť). Ako prvý si opäť musíme definovať možné argumenty. Takto bude vyzerať ich definície v kóde:

struct option long_options[] =
{
  {"debug", no_argument,        &debug_flag, 1},
  {"help",  no_argument,        NULL, 'h'},
  {"file",  required_argument,  NULL, 'f'},
  {"lines", optional_argument,  NULL, 'l'},
  {"test",  no_argument,        NULL, 't'},
  {0, 0, 0, 0}  // ukoncovaci prvek
};

char *short_options = "hf:l::";

Prvý argument je --debug. Ide o prepínač (nastavuje vlajku - áno / nie) a musí byť uvedený prvý, aby program správne fungoval (tj. Vypisoval všetky debugovacie informácie - uvidíte neskôr v kóde).

Ďalšia argument je klasický --help, ktorý (ako inak) vypíše pomocníka k programu. Existuje skrátená verzia -h. Argument nemá žiadnu hodnotu.

S --file (-f) je to už zaujímavejšie. Tento argument vyžaduje hodnotu, inak funkcia vypíše chybu. V short options sa hodnota vyžaduje pomocou dvojbodky.

Ďalej máme argument s voliteľnou hodnotou --lines. Skrátený zápis je -n. Voliteľná hodnota sa zapíše dvoma dvojbodkami. Toto nastavenie zmení formát výpisu (bude vypisovať v riadkoch a nie na jeden riadok) a pokiaľ užívateľ zadá aj hodnotu, tak aj počet vypísaných riadkov.

Tu nastáva trochu problém, ako sa odovzdáva hodnota (ktorú potom nájdeme v premennej optarg). Ako povinné, tak nepovinné hodnoty argumentov môžeme odovzdávať takto: "--lines = 15" resp. "-N15". U povinných argumentov máme navyše možnosť odovzdať hodnotu ako nasledujúce argument - "--lines 15" resp. "-N 15". Pre používateľov to môže byť trochu mätúce, ale chápem autormi funkcie, že neumožňujú oddeľovať nepovinný argument medzerou. Našťastie to nie je taký problém, pretože voliteľné argumenty málokedy použijeme.

Dôležité je pridať na koniec poľa štruktúru s nulami, aby funkcia vedela, kde pole končí.

Tu je kód, ktorý zaisťuje celé spracovanie argumentov:

while ((c = getopt_long(argc, argv, short_options, long_options, &option_index)) != -1)
{
  switch (c)
  {
    case 'h':
      fprintf(stdout,
         "***Help for this useless program***\n"
         "Possible arguments: --debug (must be first), --help, --file, --lines, --test\n"
      );
      exit (0);
      break;
    case 'f':
      if (debug_flag)
        fprintf(stderr, "Used file: %s\n", optarg);
      out = fopen(optarg, "w");
      break;
    case 'l':
      lines_flag = 1;
      if (optarg)
        lines = atoi(optarg);
      if (debug_flag)
        fprintf(stderr, "Number of lines: %d\n", lines);
      break;
    case 't':   // neni definovano v short_options -> -t nebude fungovat
      fprintf(stdout, "Test\n");
      break;
  }
}

Myslím, že väčšina je asi jasná. Uvediem ale aj tak pár poznámok.

1. getopt_long vracia -1, keď dorazí na koniec argv.

2. option_index je vyžadovaný, aj keď ho nepotrebujeme. Použiť by sme ho mohli takto:

long_options[option_index]

3. Pre skrátenie kódu som nič neošetřoval. Inak by samozrejme malo byť každé fopen, prevody a fclose ošetrené.

Tu je cyklus, ktorý vypisuje informácie - za pozornosť stojí použitia vlajok.

for (int i = 0; i < lines; i++)
{
  if (lines_flag)
    fprintf(out, "Line %d: something\n", i);
  else
    fprintf(out, "%d: something; ", i);
  if (debug_flag)
    fprintf(stderr, "%d printed\n", i);
}

Celý program je priložený k stiahnutiu. Odporúčam si s tým trochu pohrať - tak to človek pochopí najrýchlejšie. :)

Tipy pre shell

Ako viete, argumenty odovzdávané programe sú oddeľované pomocou bielych znakov (medzera, tabulátor, \ n). Čo keď ale chcem odovzdať argument, ktorý obsahuje práve tieto znaky?

Riešenie je využitie úvodzoviek. Čokoľvek uzavrite do úvodzoviek bude považované za jeden argument.

Tu je kód jednoduchého programu, ktorý budeme využívať pri našich pokusoch s odovzdávaním argumentov. :)

#include <stdio.h>

int main(int argc, char **argv)
{
  for (int i = 1; i < argc; i++)
    printf("Arg%d: \"%s\"\n", i, argv[i]);
  return 0;
}

Ukážka spustenie:

$ ./program "arg1 s mezerou" arg2
arg1: "arg1 s mezerou"
arg2: "arg2"

Niekedy (typicky pri písaní skriptov) sa určite môžete stretnúť s použitím premenných shellu - a už vlastnými, alebo tzv. Premennými prostredia.

$ PROM = "moje data"

$ ./program $PROM
arg1: "moje data"

$ ./program $USER
arg1: "david"

Ak by ste chceli odovzdať programu z nejakého dôvodu (napríklad píšete program, ktorý generuje bashové skripty) názov premennej shellu vrátane dolára (čo by normálne vypísalo obsah premennej), môžete ho uzavrieť do apostrofov - to povie shellu, aby prestal extenzii (nahrádzanie špeciálnych znakov a premenných).

$ ls
soubor1 soubor2 program

$ ./program *
Arg1: "soubor1"
Arg2: "soubor2"
Arg3: "program"

$ ./program '*' '$USER'
Arg1: "*"
Arg2: "$USER"

Ešte pre zopakovanie z minule - použitie spätných apostrofov zaistí realizácii v nich uvedeného a odovzdanie toho, čo vypíše.

file.txt

line1
line2
last_linie

Skúsime ho použiť ako zdroj argumentov ..

$ ./program `cat soubor.txt`
Arg1: "line1
line2
last_line"

To asi nie je úplne čo by sme čakali, že? Niekedy sa to určite môže hodiť .. Ale väčšinou by sme chceli použiť každý riadok ako samostatný argument (zvyčajne samozrejme súbor otvoríme a pracujeme s ním v programe - toto sa hodí ale u mnohých linuxových programov, ktoré sú písané ako filtre a ich jedinný vstup sú argumenty ). K tomu slúži program xargs.

$ cat soubor.txt | xargs ./program
Arg1: "line1"
Arg2: "line2"
Arg3: "last_line"

S využitím xargs sa dá vykonávať množstvo zaujímavých vecí, ale o tom možno až niekedy inokedy - kto chce, môže sa pozrieť do manuálových stránok.

Týmto článkom končí "minisérie" o filtroch a nabudúce sa pozrieme na niečo iné. Bude to lekcia o statických a dynamických knižniciach - Céčko a Linux - Statické a dynamické knižnice .


 

Mal si s čímkoľvek problém? Stiahni si vzorovú aplikáciu nižšie a porovnaj ju so svojím projektom, chybu tak ľahko nájdeš.

Stiahnuť

Stiahnutím nasledujúceho súboru súhlasíš s licenčnými podmienkami

Stiahnuté 52x (909 B)
Aplikácia je vrátane zdrojových kódov v jazyku C

 

Predchádzajúci článok
Céčko a Linux - Prepínače a getopt
Všetky články v sekcii
Linux a programovanie v jazyku C
Preskočiť článok
(neodporúčame)
Céčko a Linux - Statické a dynamické knižnice
Článok pre vás napísal David Novák
Avatar
Užívateľské hodnotenie:
Ešte nikto nehodnotil, buď prvý!
Autor se zajímá především o nízkoúrovňové programování (C/C++, ASM) a návrh hardwaru (VHDL).
Aktivity