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 54x (909 B)
Aplikace je včetně zdrojových kódů v jazyce C