Chtěl bych vám představit utilitu pz jako pythonize, určenou pro vás, pokud jste uživatelem příkazové řádky se znalostí Pythonu. Současné linuxové distribuce mají k dispozici mnoho efektivních nástrojů pro zpracování vstupu. Ale přáli jste si někdy, abyste místo nich mohli použít syntaxi Pythonu? Listujete často manuálem, jak že se chovají ty přepínače na formátování? Připadá vám zdrojový kód Bashe obtížně čitelný? Právě pro vás je tento článek. Dozvíte se, jak si napsat maličký program a jak je vyhodnocován, které proměnné v něm máte k dispozici, pár slov k auto-importu, přepínačích a ukážeme si některé příklady použití.
Příkazová řádka je vynikající rozhraní, jež se vyznačuje ohromujícím globálním… když ne přímo nadšením, pak dosahem. Do terminálu se lze připojit málem už na poslední pračce. Někteří uživatelé se příkazové řádky obávají, nicméně doposud neexistují dokonalé grafické aplikace, umožňující nám vyrovnat se s šíří úkolů, jimž člověk před klávesnicí čelí. Možná jste guru GNU coreutils a znáte všechny přepínače programů tr a cut, možná děláte jazykovou korekturu v sedu místo ve Wordu a nákupní seznamy píšete v awk, pak utilitu pz nepotřebujete. Pokud jste ale strávili tři hodiny hledáním zapomenuté mezery v podmínce Bash skriptu a umíte si představit lepší využití pro své odpoledne, možná váš život bude už večer jednodušší. Netřeba již více umět rozličné syntaxe, umíte-li syntaxi Pythonu.
Proč ale nepoužít přímo Python na příkazové řádce, copak nelze použít přepínač -c a vyhodnotit kód?
python -c "print(1)" # 1
Ano, samozřejmě lze. Ale zkuste si zpracovat složitější úkol a brzy utonete při zpracovávání vstupu a výstupu. Přestože je výpočet na jednu řádku, už jenom import knihoven na zacházení s rourami způsobí pořádné bolení hlavy. Zvlášť když přihlédneme k tomu, že Python je nevhodný na jednořádkové příkazy, které tu působí monstrózně; na ty je tu například PERL. Utilita pz umožňuje zanedbat všechny ty řádky jako shebang #!/usr/bin/env python3, loggování, konverzi mezi byty a stringem a tak dále; všechno to, co byste kvůli jednořádkové funkcionalitě psali stále dokola.
Při navrhování rozhraní jsme se pokusili použít pravidlo intuice – pokud by existoval ideální program, jak přesně by ho uživatel chtěl použít? Základem jsou proměnné, přepínače a klauzule. Některé proměnné se automaticky doplňují, některé jsou předpřipraveny pro vás, abyste je nemuseli inicializovat a další řídí výstup. Klauzule obsahují váš příkaz nebo jejich sekvenci a vyhodnocují se před, během a po zpracování vstupu a přepínače mění jejich chování.
Nechme tedy Python dělat ty věci, pro které nám přirostl k srdci, jako vykrajování podřetězců pomocí hranatých závorek [start:stop:step] nebo generátorová notace. Utilita pz vám přesně toto umožní. K dispozici máte proměnnou s, která vždy obsahuje právě zpracovávaný řádek. Změníte-li ji, změní se výstup. Pošleme nyní na vstup slovíčko ‚ahoj‘ a nechme vyříznout podřetězec mezi 1. a 3. znak, tedy chtějme dostat ‚ho‘. (Pozn.: za mřížkou značím návratovou hodnotu programu.)
# vykrojíme řetězec echo "ahoj" | pz s[1:3] # "ho"
Kdybychom použili přepínač --verbose, k zobrazení detailních informací, zjistili bychom, že utilita na pozadí vyhodnotila, že se proměnná s při vykonávání příkazu nijak nemění. A upravila tedy výraz o zpětné přiřazení do proměnné s tímto způsobem: s = s[1:3]. Proto není třeba do s zpátky explicitně přiřazovat. Mezi další automaticky doplňované proměnné patří n, která obsahuje aktuální řádku konvertovanou na číslo (lze-li). V následujícím příkladu přičteme k prvku na indexu 1 sedmičku. Zároveň je vidět, že volání utility lze libovolně řetězit.
# rozdělíme na čárce a přičteme sedmičku echo "ahoj,5" | pz 's.split(",")[1]' | pz n+7 # 12
K dalším automaticky doplňovaným proměnným patří count (číslo aktuální řádky), text (celý text v jediném řetězci), lines (celý text v listu řádků), numbers (celý text jako list, přičemž každý řádek je změněn na číslo). Utilita myslí na přetékání (automatické doplňování proměnných lze vypnout) i průtok – proměnná text je k dispozici implicitně až po zpracování celého textu, abychom bezpečně mohli zpracovávat nekonečný vstup libovolně dlouho. Podívejme se například na proměnnou numbers. Na vstupu dostáváme postupně čtyři čísla. Naše klauzule určuje, že přes proměnnou s se na výstup dostane n-tice ve formátu: číslo aktuální řádky, aktuální řádka a aritmetický průměr.
$ echo -e "20\n40\n25\n28" | pz 's = count, s, sum(numbers)/count' 1, 20, 20.0 2, 40, 30.0 3, 25, 28.333333333333332 4, 28, 28.25
Zatím jsme viděli pouze klauzuli main, která se spouští pro každou řádku vstupu. Existují i klauzule --setup, která se vyhodnocuje úplně na začátku (pro případ, že je třeba iniciovat některé proměnné) a zrcadlově k tomu --end, která se spustí jedinkrát na závěr. Pro jednoduchý příklad chtějme spočítat délku celého textu.
echo -e "hello\nworld" | pz --end 'len(text)' # 11
Filtrování lze snadno realizovat pomocí přepínače --filter – řádka se pošle dále podle booleanu, na který se přetypuje výsledek klauzule main. Obdobně pracuje proměnná skip, které stačí nastavit pravdivostní hodnotu. Pošleme dál například jen čísla, která jsou větší než tři.
$ echo -e "1\n2\n3\n4\n5" | pz "skip = not n > 3" 4 5
Pro užití regulárních výrazů máme zvláštní přepínače. Není tedy třeba importovat z modulu re dané funkce a ztratit se v lese uvozovek a apostrofů, stačí využít --search, --match, --findall či --sub. Pokud se vám do textu zatoulala URL, přičemž na jedné řádce jich může být i více, prožeňte každou řádku přepínačem findall. Klauzule main se pak automaticky doplní na: s = re.findall(výraz, vstup).
$ echo "Lorem http://example.com ipsum http://example.cz dolor" | pz --findall "(https?://[^\s]+)" http://example.com http://example.cz
Když se zaměříte na výstup, zjistíte, že pro jednu řádku vstupu (tu jedinou), se nám vrátilo více řádek. Ano, i to je možné. Výstup se upraví podle toho, co v proměnné s zbude po zprocesování řádku. Je-li to n-tice či generátor, vypíše se jako jeden řádek, oddělený čárkami. Zbude-li list, dostaneme řádků více. Najdeme-li volatelný výraz, zavoláme ho. To je důvod, proč můžeme napsat pouhé s.lower bez závorek a dostat malá písmena.
echo "HEllO" | pz s.lower # `s = s.lower()` → "hello" echo "HEllO" | pz len # `s = len(s)` → "5" echo "25" | pz sqrt | pz round # `s = math.sqrt(n)` → `s = round(n)` → "5"
A jak pracuje automatický import? Bylo by velmi nepraktické, kdyby uživatel musel používat klauzuli --setup i pro import těch nejobyčejnějších funkcí. Zároveň načítat všechny dostupné moduly naráz by utilitu zpomalilo na nesnesitelnou úroveň, neřku-li o problémech, které by vyvstaly při duplicitním jménu funkce u různých modulů. Utilita přistupuje na kompromis: Část funkcí je importována od začátku, například všechny symboly z knihovny math, aby uživatel mohl sečíst všechna čísla na vstupu pouhým pz --end sum (které se interně přeloží jako pz --end "s=sum(numbers)"). Další množství funkcí je připraveno v záloze, aby se importovaly, jakmile zpracovávání vstupu selže kvůli jejich nepřítomnosti. Například modul requests, aby uživatel mohl načíst webovou stránku pouhým echo "http://example.com" | pz 'requests.get(s).content', funkce sleep z modulu time, funkce datetime z modulu datetime, dříve viděný randint z modulu random nebo celý modul random, aby uživatel mohl snadno použít náhodný výběr přes funkce random.choice.
Na závěr si předložme ještě jednu komplikovanější výzvu. Stojíme před úkolem, kdy chceme ověřit, k čemu budou tíhnout náhodná čísla, generovaná funkcí random.randint. Nezajímá nás celý průběh, ale dejme tomu každá desetitisícá hodnota. Jak na to? Za prvé zde nepotřebujeme žádný vstup. Použijeme přepínač --generate s hodnotou nula, která zajistí nekonečný vstup. Zahodíme všechny řádky, jež nejsou dělitelné deseti tisíci, zbylé vypišme na konzoli ve formátu: číslo aktuálního řádku a aktuální průměr hodnot. Hodnoty se drží okolo padesátky.
pz "i+=randint(1,100); s = (count,i/count) if not count % 10000 else None" --generate 0 10000, 50.4153 20000, 50.35645
Utilita nemá žádné závislosti, pouze standardní Python ve verzi alespoň 3.6 (která je s námi už čtyři roky). Nainstaluje se balíčkovačem pip, případně stačí stáhnout a spustit jediný soubor. Její kód je hostován jako GPLv3 na adrese https://github.com/CZ-NIC/pz, kde také naleznete kompletní dokumentaci a množství příkladů.