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