IT rekvalifikace s garancí práce. Seniorní programátoři vydělávají až 160 000 Kč/měsíc a rekvalifikace je prvním krokem. Zjisti, jak na to!
Hledáme nové posily do ITnetwork týmu. Podívej se na volné pozice a přidej se do nejagilnější firmy na trhu - Více informací.

Lekce 8 - Céčko a Linux - getopt_long a shell

V minulé lekci, Céčko a Linux - Přepínače a getopt, jsme si ukázali, jak je možné si zjednodušit práci s funkcí getopt. Ta ale nedokáže vše, co jsme udělali v příkladu ručně - například dlouhé argumenty (--argument). Na druhou stranu zase může být pořadí argumentů libovolné, což je velmi pozitivní vlastnost.

Jelikož jsme ale na Linuxu, tak bychom chtěli pokud možno dodržovat GNU styl argumentů. Řešení se nabízí v podobě funkce getopt_long, která nabízí stejné výhody a ještě něco navíc a zároveň umožňuje zpracování dlouhých argumentů.

getopt_long

Pokud chceme tuto funkci, její deklaraci nalezneme v hlavičkovém souboru getopt.h. Jedná se GNU rozšíření, takže výsledný program není přenositelný mimo Linuxové operační systémy.

Výhoda této funkce je ta, že krátké argumenty zpracovává úplně stejným způsobem, jako getopt - nemusíme se tedy učit úplně nové rozhraní. Kromě toho ale přibývají dva nové argumenty. První je pole struktur a druhý je ukazatel na int, kam funkce uloží index struktury právě nalezeného dlouhého argumentu.

Struktura pro dlouhé argumenty vypadá následovně:

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

Proměnná name je string (typicky použijeme řetězcový literál, ale také si můžeme jména vygenerovat za běhu programu a přiřadit obyčejný string) obsahující jméno dlouhého argumentu. Pokud tedy chceme mít argument --help, přiřadíme do name "help".

has_arg definuje, zda má argument hodnotu nebo ne. Možnosti jsou: no_argument, optional_argument a required_argument. Co která hodnota znamená, je myslím jasné. Jen bych možná poukázal na možná trochu zmatené pojmenování. V angličtině se argumentům říká option a hodnotám argument. V češtině bude asi více označení, ale já jsem se setkal hlavně s označením argument, takže to používám. Snad to bude jasné z kontextu.

Další dvě proměnné určují, co se má stát, když je argument nalezen. Pokud je ve flag hodnota NULL, tak pak val je hodnota, která je vrácena funkcí getopt_long. Většinou pak bude stejná, jako krátká verze argumentu a obě verze tak budou obslouženy stejným kódem ve switchi. Pokud je ve flag adresa proměnné, je do té uložena hodnota ve val (což bude typicky 0 nebo 1).

Ukázka

Pro ukázku si uděláme jednoduchý program. Jeho jedinou funkcí bude generovat data (v praxi by se to ale určitě dalo snadno rozšířit a nějak použít). Jako první si opět musíme definovat možné argumenty. Takhle bude vypadat jejich definice v kódu:

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::";

První argument je --debug. Jedná se o přepínač (nastavuje vlajku - ano/ne) a musí být uveden první, aby program správně fungoval (tj. vypisoval všechny debugovací informace - uvidíte později v kódu).

Další argument je klasický --help, který (jak jinak) vypíše nápovědu k programu. Existuje zkrácená verze -h. Argument nemá žádnou hodnotu.

S --file (-f) je to už zajímavější. Tento argument vyžaduje hodnotu, jinak funkce vypíše chybu. V short options se hodnota vyžaduje pomocí dvojtečky.

Dále máme argument s volitelnou hodnotou --lines. Zkrácený zápis je -n. Volitelná hodnota se zapíše dvěmi dvojtečkami. Toto nastavení změní formát výpisu (bude vypisovat v řádcích a ne na jeden řádek) a pokud uživatel zadá i hodnotu, tak i počet vypsaných řádků.

Zde nástává trochu problém, jak se předává hodnota (kterou pak najdeme v proměnné optarg). Jak povinné, tak nepovinné hodnoty argumentů můžeme předávat takto: "--lines=15" resp. "-n15". U povinných argumentů máme navíc možnost předat hodnotu jako následující argument - "--lines 15" resp. "-n 15". Pro uživatele to může být trochu matoucí, ale chápu autory funkce, že neumožňují oddělovat nepovinný argument mezerou. Naštěstí to není takový problém, protože volitelné argumenty málokdy použijeme.

Důležité je přidat na konec pole strukturu s nulami, aby funkce věděla, kde pole končí.

Zde je kód, který zajišťuje celé zpracování argumentů:

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ětšina je asi jasná. Uvedu ale i tak pár poznámek.

1. getopt_long vrací -1, když dorazí na konec argv.

2. option_index je vyžadován, i když ho nepotřebujeme. Použít bychom ho mohli takto:

long_options[option_index]

3. Pro zkrácení kódu jsem nic neošetřoval. Jinak by samozřejmě mělo být každé fopen, převody a fclose ošetřeny.

Zde je cyklus, který vypisuje informace - za pozornost stojí použití vlajek.

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 přiložen ke stažení. Doporučuji si s tím trochu pohrát - tak to člověk pochopí nejrychleji. :)

Tipy pro shell

Jak víte, argumenty předávané programu jsou oddělovány pomocí bílých znaků (mezera, tabulátor, \n). Co když ale chci předat argument, který obsahuje právě tyto znaky?

Řešení je využití uvozovek. Cokoliv uzavřete do uvozovek bude považováno za jeden argument.

Tady je kód jednoduchého programu, který budeme využívat při našich pokusech s předáváním argumentů. :)

#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ázka spuštění:

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

Někdy (typicky při psaní skriptů) se určitě můžete setkat s použitím proměnných shellu - a už vlastními, nebo tzv. proměnnými prostředí.

$ PROM = "moje data"

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

$ ./program $USER
arg1: "david"

Pokud by jste chtěli předat programu z nějakého důvodu (například píšete program, který generuje bashové skripty) název proměnné shellu včetně dolaru (což by normálně vypsalo obsah proměnné), můžete jej uzavřít do apostrofů - to řekne shellu, aby neprováděl extenzi (nahrazování speciálních znaků a proměnných).

$ ls
soubor1 soubor2 program

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

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

Ještě pro zopakování z minule - použití zpětných apostrofů zajistí provedení příkazu v nich uvedeného a předání toho, co vypíše.

soubor.txt

line1
line2
last_linie

Zkusíme ho použít jako zdroj argumentů..

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

To asi není úplně co bychom čekali, že? Někdy se to určitě může hodit.. Ale většinou bychom chtěli použít každou řádku jako samostatný argument (obvykle samozřejmě soubor otevřeme a pracujeme s ním v programu - tohle se hodí ale u mnoha linuxových programů, které jsou psány jako filtry a jejich jedinný vstup jsou argumenty). K tomu slouží program xargs.

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

S využitím xargs se dá provádět množství zajímavých věcí, ale o tom možná až někdy jindy - kdo chce, může se podívat do manuálových stránek.

Touto lekcí končí "minisérie" o filtrech a příště se podíváme na něco jiného. Bude to lekce o statických a dynamických knihovnách - Céčko a Linux - Statické a dynamické knihovny.


 

Měl jsi s čímkoli problém? Stáhni si vzorovou aplikaci níže a porovnej ji se svým projektem, chybu tak snadno najdeš.

Stáhnout

Stažením následujícího souboru souhlasíš s licenčními podmínkami

Staženo 50x (909 B)
Aplikace je včetně zdrojových kódů v jazyce C

 

Předchozí článek
Céčko a Linux - Přepínače a getopt
Všechny články v sekci
Linux a programování v jazyce C
Přeskočit článek
(nedoporučujeme)
Céčko a Linux - Statické a dynamické knihovny
Článek pro vás napsal David Novák
Avatar
Uživatelské hodnocení:
5 hlasů
Autor se zajímá především o nízkoúrovňové programování (C/C++, ASM) a návrh hardwaru (VHDL).
Aktivity