V dnešním díle se naučíme zpracovávat argumenty příkazového řádku.
19.9.2006 06:00 | Jiří Václavík | přečteno 24780×
Perl by nebyl plnohodnotným jazykem kdyby neuměl obsloužit argumenty programů. Toto téma jsme již naťukli v souvislosti se soubory, kdy jsme zmiňovali ovladač <> (resp. <ARGV> se stejným významem). Pomocí něj jsme byly schopni číst soubory, které byly předány jako argument na příkazovém řádku.
Ovladač <> funguje tak, že si Perl nejprve ověří, zda existují soubory se jmény uvedenými jako argumenty. Pokud ano, pak tyto soubory otevře, použije jako zdroj dat a zpřístupní ho ovladačem <>.
Tato metoda je ale vhodná pouze pro úzkou množinu programů. Přesněji řečeno pro ty, které vyžadují jako argument soubor, ze kterého se bude číst. Jistým zjednodušením je i to, že všechny soubory se slijí do jednoho.
Pro určité úlohy je tedy ovladač <> vhodný, ale pro jiné nikoliv. Co kdybychom pouze chtěli zjistit velikost předaného souboru? A co kdyby vůbec nebyl předáván název souboru, ale libovolný řetězec? Na to potřebujeme obecnější metodu pro práci s argumenty.
Perl nabízí pro práci s argumenty příkazového řádku na nejnižsí úrovni speciální pole @ARGV. Veškeré parametry předané programu jsou v tomto poli automaticky uloženy.
Další speciální proměnnou s podobným významem, tentokrát skalární, je $0. Ta obsahuje jméno programu. S regulárnímy výrazy nemá nic společného, přestože se tak na první pohled může zdát. Proměnné pro zapamatování však začínají od 1.
Ukažme si, jak to vypadá v praxi. Mějme program ll, kterému předáme jako argumenty jména souborů. U těchto souborů vytiskne příkaz ll informace o i-uzlu, podobně jako unixové ls -l.
ll ghost.png db.sql data install.py
Pokud zavoláme program tímto způsobem, bude obsah proměnných $0 a @ARGV následující:
Proměnná | Obsah |
$0 | ll |
$ARGV[0] | ghost.png |
$ARGV[1] | db.sql |
$ARGV[2] | data |
$ARGV[3] | install.py |
Napíšeme si výše zmíněný program ll. Ten bude přijímat argumenty příkazového řádku. ll bude vypisovat, jakého typu je soubor (adresář, roura, atd.), práva, počet odkazů, vlastníka (uživatel i skupina), velikost, datum, čas a jméno souboru. ll tak bude napodobovat ls -l, jen nebude vyladěn do takových detailů.
Nejprve si udělejme nějakou koncepci. Postup bude následující.
Zde máme hlavní cyklus programu, který v každé iteraci vyšetří 1 předaný existující soubor.
#!/usr/bin/perl
use strict;
for (@ARGV){
next unless -e $arg;
...
}
V každém cyklu tak zpracujeme 1 argument, který bude uložen vždy v proměnné $_.
Teď budeme postupně zjišťovat informace o i-uzlech. Za prvé zde je typ souboru. To vyřešíme podprogramem zjisti_typ_souboru. Obdobně získáme i řetězec práv. V dalším sloupci máme počet odkazů na soubor. Ten není problém zjistit, protože tuto informaci máme uloženou na 4. pozici v seznamu, který vrací funkce stat. Jméno vlastníka a skupiny dostaneme voláním getpwuid resp. getgrgid. Předposledním sloupcem je velikost, kterou získáme taktéž pomocí funkce stat. A nakonec potřebujeme datum a čas. My máme pouze počet sekund od 1.1.1970. Proto musíme opět použít podprogram.
Nyní máme alespoň teoreticky veškeré potřebné údaje. Vytiskneme je funkcí printf. Oproti příkazu ls -l použijeme pro každou hodnotu pevnou šířku sloupce.
Hlavní cyklus bude na základě těchto údajů vypadat takto.
for (@ARGV){
next unless -e $arg;
my @data = stat;
my $typ_souboru = ziskej_typ_souboru($_);
my $prava = ziskej_prava($data[2]);
my $odkazu = $data[3];
my $user = getpwuid $data[4];
my $group = getgrgid $data[5];
my $velikost = $data[7];
my $cas = zjisti_cas($data[9]);
printf("%1s%9s %3s %7s %7s %10d %16s %s\n", $typ_souboru, $prava,
$odkazu, $user, $group,
$velikost, $cas, $arg);
}
Zbývá nám dopsat jednotlivé podprogramy. Začneme určením typu souboru. K tomu si připomeňme speciální operátory pro soubory. Budeme rozeznávat obyčejné soubory, adresáře, symbolické odkazy, roury, sockety, blokové a znakové soubory. V tomto podprogramu nám postačí jednoduchý switch.
sub ziskej_typ_souboru {
my($soubor) = @_;
if (-d $soubor){return "d";} #adresář
elsif (-l $soubor){return "l";} #symbolický odkaz
elsif (-f $soubor){return "-";} #obyčejný soubor
elsif (-p $soubor){return "p";} #roura
elsif (-b $soubor){return "b";} #blokový
elsif (-c $soubor){return "c";} #znakový
elsif (-S $soubor){return "s";} #socket
else {return "?";} #neznamý
}
Další na řadě máme řetězec práv. To bude vůbec nejsložitější část programu. Ohled musíme brát i na sticky bit, set UID a set GID. A co k tomu vlastně máme k dispozici? Prakticky jen příkaz stat, pomocí kterého lze zjistit desítkový formát přístupových práv.
Vše se tedy bude odehrávat v podprogramu ziskej_prava, který obdrží jako argument desítkový zápis práv.
sub ziskej_prava {
my $dec_prava = shift;
...
}
Nejdříve musíme získat osmičkový zápis přístupových práv, se kterým se bude lépe pracovat.
my $oct_prava = sprintf "%o", $dec_prava & 07777;
Dále si celý problém rozdělíme do 3 kroků. Bude to spíše manuální práce než vymýšlení algoritmů. Zde je postup.
Pokud je sticky bit, set UID nebo set GID nastaven, je zároveň hodnota $dec_prava větší než 777 a je čtyřmístná. Takže pokud lze odečíst příslušné hodnoty, uděláme to a zároveň nastavíme proměnné $sbit, $suid a $sgid. Připomeňme si, že pro set UID odečítáme 4000, pro set GID 2000 a pro sticky bit 1000.
if (length $oct_prava == 4){
if (($oct_prava - 4000) >= 0){$oct_prava -= 4000; $suid = 1};
if (($oct_prava - 2000) >= 0){$oct_prava -= 2000; $sgid = 1};
if (($oct_prava - 1000) >= 0){$oct_prava -= 1000; $sbit = 1};
}
První a nejjednodušší krok máme úspěšně za sebou, zbývají ještě 2. Protože nyní víme, že je hodnota $dec_prava vždy trojmístná, můžeme jednotlivé cifry rozdělit.
my($vlastnik, $skupina, $ostatni) = split "", $oct_prava;
Od každé z proměnných $vlastnik, $skupina a $ostatni opět budeme postupně odečítat hodnoty (právo pro čtení 4, pro zápis 2, pro spouštění 1) a zároveň přidávat práva (byla-li příslušná hodnota odečtena). V případě absence práva zapíšeme znak -.
for my $us ($vlastnik, $skupina, $ostatni){
if (($us - 4) >= 0){$us -= 4; $retezec .= "r";} else {$retezec .= "-";}
if (($us - 2) >= 0){$us -= 2; $retezec .= "w";} else {$retezec .= "-";}
if (($us - 1) >= 0){$us -= 1; $retezec .= "x";} else {$retezec .= "-";}
}
V proměnné $retezec nyní máme řetězec práv a musíme do něj zahrnout $sbit, $suid a $sgid.
Existuje-li $suid, nahradíme 3. znak řetězce za s (pokud tam už je x) nebo za S (pokud tam není). Použijeme funkci substr. Její 4. parametr - řetězec, kterým se bude (v našem případě 3. znak) nahrazovat tedy bude právě s nebo S.
if ($suid == 1){
substr $retezec, 2, 1, ... s nebo S ...;
}
Jestliže je právě na 3. pozici znak x (což zjistíme opět příkazem substr), 4. parametrem bude s. Jinými slovy - prostor pro podmínkový operátor.
if ($suid == 1){
substr $retezec, 2, 1, ((substr $retezec, 2, 1) eq "x") ? "s" : "S";
}
To samé uděláme i s set GID a sticky bit.
if ($sgid == 1){
substr $retezec, 5, 1, ((substr $retezec, 5, 1) eq "x") ? "s" : "S";
}
if ($sbit == 1){
substr $retezec, 8, 1, ((substr $retezec, 8, 1) eq "x") ? "t" : "T";
}
A na závěr vrátíme výsledný řetězec.
return $retezec;
Posledním podprogramem zjistíme datum a čas. V něm získáme všechny potřebné údaje od funkce localtime. Stačí je pouze vhodně poskládat a vrátit.
sub zjisti_cas {
my($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($_[0]);
return sprintf("%4d-%02d-%02d %02d:%02d", $year + 1900, $mon, $mday,
$hour, $min)
}
To je celé. Oproti ls -l se sice ll v mnoha rysech významně liší (stačí porovnat výstupy po zadání adresáře), ale přesto nám posloužil jako názorná ukázka.
$ ./ll TEST -rws--Sr-T 1 jv users 0 2005-07-07 13:56 TEST $ ls -l TEST -rws--Sr-T 1 jv users 0 2005-08-07 13:56 TEST $