7. díl - Céčko a Linux - Přepínače a getopt

C++ Linux Céčko a Linux - Přepínače a getopt

Zkoušky skončily a já si konečně našel čas k sepsání dalšího dílu.. :)

V minulém díle jsme si řekli něco o tom, jak funguje předávání argumentů ze shellu a jak je může náš program zpracovat. Dnes si vyzkoušíme měnit chování programu pomocí přepínačů a napíšeme si jednoduchou demonstrační aplikaci.

Přepínače

Přepínač je argument programu, který typicky ovlivňuje (přepíná) jeho funkcionalitu. Například mějme program, který dostane tři argumenty. První bude určovat operaci, která bude provedena s následujícími dvěmy argumenty.

Přepínače obykle začínají znakem - pro zkrácenou podobu a dvěma znaky - pro nezkrácenou podobu. Můžeme si samozřejmě vymyslet jakýkoliv formát, ale tohle je takový standard a myslím, že není důvod ho nepoužívat. :)

Pro začátek si nadefinujeme dva přepínače:

  • sčítání: -a resp. --addition
  • násobení: -m resp. --multiplication

Program pak spustíme například takto:

$ ./program -a 10 5

Ukázka

Jako první věc musíme určitě ověřit, zda uživatel zadal správný počet argumentů. Kdybychom to neudělali, program by při nesprávném použití pracoval neočekávaně a uživatel by o tom nebyl informován.

if (argc != 4)
{
  fprintf(stderr, "Chybny pocet argumentu!\nNapovedu zobrazite pomoci prepinace -h\n");
  return 1;
}

Nezapomeňme, že první argument je název programu, takže musíme ověřovat na počet zadávaných argumentů + 1. Vidíme také, že by se asi hodil další přepínač pro zobrazení nápovědy. Použijeme standardní -h, resp. --help. Jak ale vlastně zjistíme, který přepínač uživatel použil? Nejčastěji se používá funkce strcmp(). Najdeme ji v knihovně string a jak už název napovídá, porovnává dva řetězce znaků. Pokud ji někdo neznáte, můžete se pro bližší informace podívat do manuálových stránek. Základní funkčnost je ale taková, že jí dáte dva stringy (nebo přesněji "pole znaků ukončené nulovým znakem") a pokud se tyto rovnají, vrací nulu.

if (!strcmp(argv[1], "-a") || !strcmp(argv[1], "--addition"))
  vysledek = a + b;
else if (strcmp(argv[1], "-m") == 0 || strcmp(argv[1], "--multiplication") == 0)
  vysledek = a * b;
else
  {
    fprintf(stderr, "Je nutne zvolit operaci!\n");
  }

Tímto poměrně jednoduchým způsobem jsme zjistili, který přepínač uživatel použil. Jak vidíte, použil jsem oba možné zápisy podmínky - jsou naprosto ekvivalentní, takže je jedno, který použijete - já osobně preferuji první způsob, protože mi připadá přehlednější.. V proměnných a a b se již samozřejmě nachází převedené argumenty.

Kód pro výpis nápovědy bude obdobný. Musí ale být před ověřením správného počtu argumentů, protože by jím neprošel (a tudíž by se nápověda nikdy nevypsala). Co není už tak zřejmé (a často se zapomíná) je nutnost znovu ověřit počet argumentů.

if (argc > 1 && (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")))
{
  fprintf(stdout, "Program se pouziva nasledovne: bla bla\n");
  return 0;
}

Celý program je v příloze. Ukázka je ale čistě pro akademické účely - například pro převod argumentů by bylo vhodné použít strtod a ověřovat, zda jsou to čísla.

Tip

Pro ty, co neznají příliš Linuxové systémy, mám jeden pěkný trik. Ukážeme si to na programu, který jsme právě napsali..

Fungovat bude s každým programem, který vrací pouze výsledek. Náš program se bude chovat přesně tak. Výpis výsledku vypadá následovně:

fprintf(stdout, "%d\n", vysledek);

Takhle bude vypadat běžné použití našeho programu:

$ ./program -a 10 5
15
$

Můžeme ale použít jednu pěknou vlastnost shellu..

$ ./program -a 10 `./program -m 10 15`
160
$

Pokud něco uzavřeme do zpětných apostrofů (AltGr + klávesa pod Esc), shell to spustí prvně a místo těchto apostrofů vloží to, co program vypsal na stdout. V našem příkladu tedy shell nejdříve spustil ./program -m 10 15. Výsledek tohoto volání je 150. Druhé spuštění našeho programu tedy vypadalo následovně: ./program -a 10 150. Pěkné, že?

getopt

Co jsme si zatím ukázali, je plně multiplatformní a mělo by fungovat prakticky všude. Na UNIXových operačních systémech (splňující normu POSIX) najdeme funkci getopt(), která nám může usnadnit práci (to platí především pro programy s velkým množstvím přepínačů).

Nachází se v knihovně getopt (nebo unistd - může se lišit dle systému), takže pro její použití musíme připojit příslušný hlavičkový soubor:

#include <getopt.h>

Funkce se typicky používá v cyklu a v kombinaci s příkazem switch. Funkci předáme argc, argv a řetězec s možnými argumenty. Umí pracovat jen s krátkými argumenty.

while ((opt = getopt (argc, argv, "am")) != -1)
{
  switch (opt)
  {
    case 'a':
      if (set)
      {
        fprintf(stderr, "Nelze pouzit -a a -m zaroven!\n");
        return 1;
      }
      operace = ADD;
      break;
    case 'm':
      if (set)
      {
        fprintf(stderr, "Nelze pouzit -a a -m zaroven!\n");
        return 1;
      }
      operace = MUL;
      break;
  }
}

Funkce getopt se hodí spíše pro zpracování argumentů jako takových, než přepínačů - v našem případě jsem musel zajistit výlučnost pomocí vlajky. Pokud ale máme například program, který má množství argumentů, které například nastavují formát výstupu a lze je kombinovat, getopt je ideální funkce.

Po proběhnutí tohoto kódu víme, jestli uživatel zadal -a nebo -m. Zajímavá vlastnost je, že nezáleží na pořadí argumentů - getopt prochází argv tak dlouho, než je najde všechny. Nalezené argumenty z argv odstraní (což se nám bude velice hodit).

Náš program trochu pozměníme. Nyní bude akceptovat libovolný počet čísel ke zpracování a pokud uživatel nepoužije přepínač, výchozí operace bude sčítání.

if (!set)
  operace = ADD;

if (operace == ADD)
  vysledek = 0;
else if (operace == MUL)
  vysledek = 1;

for (int i = optind; i < argc; i++)
{
  if (operace == ADD)
    vysledek += atoi(argv[i]);
  else if (operace == MUL)
    vysledek *= atoi(argv[i]);
}

For cyklus projde zbylé argumenty programu a provede příslušnou operaci. Z proměnné optind zjistíme, kde začínají nezpracované argumenty.

Celý program je opět k dispozici ke stažení.

Funkce také podporuje zpracování argumentů s parametrem. Pokud například použijeme "a:", getopt bude hledat argument -a následovaný parametrem. Toto je ovšem GNU rozšíření a bude tedy fungovat jen na Linuxu.

$ ./program -a 15

Kód na zpracování by vypadal následovně:

case 'a':
  a_flag = true;
  moje_promenna = optarg;
  break;

Více informací najdete v manuálových stránkách plus zde najdete pěknou ukázku i s ošetřením chybějících argumentů.

getopt_long

Jak jsem řekl, getopt neumožňuje zpracování dlouhých argumentů (--argument). Proto tu máme pokročilejší funkci - getopt_long, která zvládá oboje. Informace o něm si můžete jednoduše najít v manuálových stránkách.

Zde bych vytvořil "minianketu" - chcete, abych o getopt_long napsal samostatný článek, ve kterém bych ho vysvětlil a ukázal, jak se používá nebo už bylo dost zpracovávání argumentů? :)


 

Stáhnout

Staženo 8x (1.1 kB)
Aplikace je včetně zdrojových kódů v jazyce C

 

  Aktivity (1)

Článek pro vás napsal David Novák
Avatar
Autor v současné době studuje FIT VUT Brno a zajímá se především o nízkoúrovňové programování (C/C++, ASM) a návrh hardwaru (VHDL). Je zde také členem výzkumného týmu ANT@FIT (Accelerated Network Technologies).

Jak se ti líbí článek?
Celkem (3 hlasů) :
4.333334.333334.333334.33333 4.33333


 


Miniatura
Předchozí článek
Céčko a Linux - Filtry
Miniatura
Všechny články v sekci
Programování v jazyce C v Linuxu
Miniatura
Následující článek
Céčko a Linux - getopt_long a shell

 

 

Komentáře

Avatar
Josef Kuchař (Pepa489):

Super, na tento díl jsem dlouho čekal, ale dočkal jsem se :)

Odpovědět 19.6.2015 22:00
2x piš, jednou debuguj
Avatar
David Novák
Tým ITnetwork
Avatar
Odpovídá na Josef Kuchař (Pepa489)
David Novák:

Sorry za zpoždění - závěr semestru bývá poněkud hektický :D

A důležitá otázka pro všechny čtenáře - má být další článek o getopt_long (tzn. v zásadě bych přeložil, zjednodušil a prakticky ukázal, co je v manuálových stránkách) nebo se mám posunout k dalšímu tématu? :)

Ještě mám v plánu podívat se blíže na Codeblocks, debugging a do budoucna na nějaké další zajímavosti jako třeba práce s procesy, syscally apod. A kdyby jste měli někdo nápady, o čem bych mohl napsat, tak mi klidně napište..

Odpovědět 19.6.2015 22:07
Chyba je mezi klávesnicí a židlí.
Avatar
Odpovídá na David Novák
Josef Kuchař (Pepa489):

Ok, o getopt_long bych uvítal další článek
Máš tam malou chybku: Nechází se v knihovně getopt (nebo unistd - může se lišit dle systému), takže pro její použití musíme připojit příslušný hlavičkový soubor

Odpovědět 20.6.2015 10:45
2x piš, jednou debuguj
Avatar
David Novák
Tým ITnetwork
Avatar
Odpovědět 20.6.2015 10:54
Chyba je mezi klávesnicí a židlí.
Děláme co je v našich silách, aby byly zdejší diskuze co nejkvalitnější. Proto do nich také mohou přispívat pouze registrovaní členové. Pro zapojení do diskuze se přihlas. Pokud ještě nemáš účet, zaregistruj se, je to zdarma.

Zobrazeno 4 zpráv z 4.