Skript teXmake Hledá se kroužek pro stupně

Transkript

Skript teXmake Hledá se kroužek pro stupně
Skript teXmake
TeX
Poslední skript v tomto roce! – teXmake.sh se snaží usnadnit práce s překlady souborů TeX(u). Je to takový
jednoduchý pomocník, co ušetří dost práce a nervů. Navíc se s ním dají docela dobře psát dokumenty v konzoli.
Umí totiž po kompilaci udělat náhledy, které zobrazí přes fbi. Ještě větší efektivita se dosáhne napojením na
make editoru vim (nastavenní je k vidění v souboru .vimrc, který jsem sem před časem dal). A pak už je to
opravdu pohádka, takové psaní.
31.12.2014
Hledá se kroužek pro stupně
TeX
Možná to nějak jde, ale já prostě nemohl v TeX(u) tisknout ◦C. Místo kroužku jsem dostával na výstup jen
černou kostičku, což znamená, že takový znak chybí.
Prozkoumal jsem tedy font cmr a koukám že na pozici 23
je kroužek. Kouknu také do jiných fontů a tam byl také. Mohl
jsem tedy zkusit udělat makro \degree{\char23}, nebýt toho,
že ty kroužky jsou nějaké divné: Ten z fontu Computer Modern je
příliš vzdálený od krajů (první na obrázku), kroužek z rodiny fontů
Helvetica je zase taková malá blecha (druhý na obrázku), přičemž
u jiný cs fontů jsem větší hledal marně. – A tak jsem se rozhodl
vytvořit vlastní kroužek pro stupně, takový, který se mi bude líbit
(třetí na obrázku).
Není na tom nic složitého, protože kroužek se nachází v matematických fontech. Stačilo kroužek vhodně
zmenšit k používanému písmu a umístit na správné místo. Zde je mé makro \degree:
\newdimen\fontSizeMath
\def\degree{\fontSizeMath=\the\fontdimen6\font
\fontSizeMath=.6\fontSizeMath
\font\fontMath cmsy10 at\fontSizeMath
{\textfont2=\fontMath
\raise1ex\hbox{\mathhexbox20E}}}
Do fontSizeMath se nejprve uloží velikost právě použitého písma. Tu pak zmenšíme v takovém poměru,
aby měl kroužek k písmu odpovídající velikost. Dále nastavíme patřičnou základní velikost matematického
fontu (rodiny fontů 2). A konečně pomocí \mathhexbox dostaneme znak z pozice 0x0E fontu cmsy; první
číslice 2 udává rodinu matematických fontů (viz str. 467 v TBN). Boxík s kolečkem se jen posune k horní
hraně a je hotovo.
Pokus o vylepšení
Po té, co jsem objevil také tučný kroužek, rozhodl jsem se provést úpravu, která se snaží o detekci právě
používaného fontu, a pokud se jedná o tučný font, vloží se kroužek také tučný. K tomu jsem potřeboval tři
makra, která nedělají nic jiného, než že se snaží najít v názvu fontu písmeno b nebo d, přičemž pokud tam je,
vrátí makro \bl ASCII znak b, a pokoud tam není, nevrátí nic:
\def\B#1#2#3#4#5#6#7#8#9/{%
\if b#3b\fi\if b#4b\fi\if b#5b\fi\if b#6b\fi\if b#7b\fi\if b#8b\fi
\if d#3b\fi\if d#4b\fi\if d#5b\fi\if d#6b\fi\if d#7b\fi\if d#8b\fi}
\def\fontBold #1{\expandafter \B #1xxxxx/}
\def\bl{\fontBold{\fontname\font}}
1
Předpokládá se, že název fontu není delší než 8 znaků, přičemž první dva znaky se ignorují (nikdy neobsahují u
csplain informaci o řezu). Používaný font dostaneme pomocí \fontname\font; včetně parametru (nejčastěji
at. .pt). Zbytek už je pouze akrobacie se zpracováním textu a nalezení b a d. – U původního řešení jen přidáme
naše nové makro \bl takto:
\font\fontMath cm\bl sy10 at\fontSizeMath
Tím by se měl vložit tučný kroužek, pokud je použito tučné písmo, a to nejen pro CM fonty, ale i pro jiné fonty
v csplain, protože i ty používají v názvu b jako bold nebo d jako demi pro tučný řez.
Problém
Pokud by se v názvu fontu od druhého znaku až po osmý vyskytovaly dvě písmena, která makro detekuje, bude dosazeno b
do názvu fontu dvakrát (cmbbsy10 místo cmbsy10) a dojde k
chybě. Dostat ze stejných písmen za sebou jen jedno, se ukázal
být problé, který v tomto případě nelze asi tak snadno vyřešit.
\expandafter lze totiž použít pouze do určité hloubky (má-li být
makro ještě čitelné) a v případě podmínek \if si jejich rozvinutí
nedovedu dost dobře představit. Nejspíš lze vše vyřešit zcela jinak,
ale tudy cesta k vyřešení zřejmě nevede. Jelikož jsem zatím nenašel v csplain font, u kterého by tento stav
mohl nastat, odkládám řešení na neurčito.
Vyřešeno 30.12.2014: Nastavení flagu jsem se sice chvíli bránil, ale když se to vhodně provede a není třeba
podmínka pro testování, vychází to dobře. Jde o pár změn v makru \B a jedno makro \b, pro jistotu platné
jen uvnitř skupiny s makrem \B:
{\def\b{\toks0={b}}
\gdef\B#1#2#3#4#5#6#7#8#9/{%
\if b#3\b\fi\if b#4\b\fi\if b#5\b\fi\if b#6\b\fi\if b#7\b\fi\if b#8\b\fi
\if d#3\b\fi\if d#4\b\fi\if d#5\b\fi\if d#6\b\fi\if d#7\b\fi\if d#8\b\fi
\the\toks0}}
Průzkumník fontů (nemám kam ho zatím jinam umístit)
Zde se mi opět osvědčil pomocný skriptík, který jsem začal utvářet kdysi na začátku, když jsem k TeX(u) prvně
přičichl. Vytvořit si jakous takous představu o fontech a symbolech, byla pro mě jako uživatele WYSIWYG
aplikací trochu vyšší dívčí. Až když jsem si nechal udělat listing všech fontů v adresářích, vytvořil si náhledy
fontů, prohlédl rodiny fontů a k tomu také pár poznámek, které jsem do skriptu postupně připisoval, pak už
začalo být mnohé celkem jasné.
Náprava znaku copyright a znak registered
S kroužkem se nakonec svezlo i tohle, zdroják je k dispozici zde. S drobnými úpravami – jak bylo výše popsáno
– by makra mohla tvořit znaky i tučně, možná je časem vylepším.
27.12.2014
2
Jak jsem poznával TeX IV - nějaké to počítání
TeX
A je to tady, já to tušil. Chtěl jsem si těch pár zásad totiž už kdysi sepsat, ale pak jsem nad tím mávl rukou
s tím, že si to budu pamatovat. Jenže uplynulo několik měsíců, já se rozhodl trochu zas něco poladit a ejhle. . .
jeden zádrhel za druhým. Takže nezbývá než si to sem naťukat pro další připomenutí.
Registry \count a \dimen
Pro naše numerické počty budeme používat registry typu number a dimen. Registrů je u obou typů 255 a jsou
pojmenované count a dimen, například \count43. Použít se dají přímo \dimen0=12cm, kdy se do nich
uloží přiřazením hodnota (u typu dimen musí následovat také jednotka) nebo obsah z jiného registru. Pokud
registr použijeme uvnitř skupiny (pozn. závorky makra netvoří skupinu!), je platnost omezena pouze uvnitř
skupiny, něco jako lokální proměnná. V takovém případě, pokud už byla registru přiřazena hodnota, bude po
opuštění skupiny zpět zapsána do registru.
\def\regtest{\count11=15 {\count11=20}}
\count11=18 \regtest
\the\count11
Takto budeme mít na konci v registru \count11 hodnotu 15. Pokud si představíme sekvenci po jejím rozvinutí,
pak bude vše už nejspíš jasné:
\count11=18 \count11=15 {\count11=20}
První se tedy uloží do registru hodnota 18, ta se dále přepíše hodnotou 15 a jako poslední se do registru
zapíše hodnota 20, která je ale uzavřena ve skupinových závorkách, a proto bude její působnost jen lokální a
po opuštění skupiny se hodnota v registru \count11 obnoví na původní, která byla před skupinou. Lokální
charakter registru uvnitř skupiny je možné porušit pouze použitím primitivu \global před registrem. Uvnitř
skupiny můžeme tedy bez obav použít stejný registr, který se už používá někde před skupinou. Například registry
count v rozsahu 0-9 jsou interně používané pro údaje o čísle strany, která se vkládá do dvi nebo pdf dokumentu;
obvykle se používá jen \count0. Registry count 10-21 používá plain také interně, jak si ještě dále vysvětlíme.
Je tedy třeba používat tyto registry opatrně a mít na paměti, že pokud je nepoužijeme uvnitř skupiny, může
nám přestat fungovat číslování stránek nebo si tím můžeme způsobit i jiné nečekané problémy.
Používat číselné registry není zrovna přehledný způsob psaní kódu. Naštěstí je tady ale způsob, jak přiřadit
registrům jména. Takovou vlastní deklaraci ke konkrétnímu registru lze vytvořit prostřednictvím primitivu
\countdef respektive \dimendef. Jedna taková deklarace už je v plainu vytvořena:
\countdef\pageno=0
a od tohoto okamžiku je \pageno něco jako pointer do \count0, který lze vedle číselného registru také použít.
I přes uvedenou výhodu přehlednosti se však tento způsob moc nepoužívá. Důvodem je nepředstavitelný chaos,
který tím vzniká, kdy musíme mít neustále na paměti, které registry jsou ještě volné, a které můžeme použít
jen lokálně ve skupině, přičemž pojmenováním jsou navíc čísla registrů skryta. Proto je tu třetí způsob, který
se doporučuje používat: definice registru pomocí makra \newcount nebo \newdimen. Registry takto nelze
deklarovat uvnitř makra: mají nastavený příznak \outer! Pojmenovaný registr se chová uvnitř skupiny stejně
jako číselné registry; uvnitř skupiny je lokální i přesto, že je deklarovaný vně skupiny. (viz funkce makra v
TBN na str.400)!
\newdimen\mujregistrdime
3
Tímto se vyhneme absolutnímu přiřazení pozice v sadě registrů a necháme makro, aby si samo přiřazovalo
do prázdných registrů v rozsahu 11-255 u dimen a 23-255 u count; při každém zavolání makra \newcount
nebo \newdimen se alokuje další registr v uvedeném rozsahu, přičemž se číslo posledního alokovaného registru
count ukládá do \count10 a číslo posledního alokovaného registru typu dimen do \count11, atd. i pro
ostatní registry (registry count v rozsahu 10-21 bychom proto neměli používat!). U dimen máme jistotu, že
registry 0-10 budou vždy volné (pokud je někdo z nevědomosti globálně neobsadí ve svém makru) a je proto
vhodné, používat je jako registry k odkládání dočasných hodnot. Registry count v rozsahu 1-9 budou s největší
pravděpodobností také volné), ale může se stát, že je plain použije pro ukládání čísel stránek. (Číslování strany
v dvi může obsahovat až deset čísel, běžně se však používá jedno číslo, a to je uloženo v \count0.) (makro
\newcount je popsané na str. 399 v TBN)
Manipulace s registry a konverze
Ohledně manipulace – v případě našich dvou registrů – není nic až tak překvapivého: za = jednoduše uvedeme
hodnotu nebo registr, ve kterém je hodnota uložena. Pouze v případě dimen musíme za číselnou hodnotou
uvádět ještě jednu z veličin (pt, pc, in, bp, cm, mm, dd, cc, sp, em, ex). Pokud bychom přiřadili do
registru typu dimen číslo bez jednotky, dojde k chybě. (Interně se do registru hodnota ukládá v jednotkách sp.)
Hodnotu v registru si můžeme extrahovat s pomocí \the, přičemž výstupem u registru dimen bude desetinné
číslo s pt na konci (tokeny budou kategorie 12!).
Pokud přiřadíme do registru typu number konstantu reálného čísla (třeba i s jednotkou), uloží se jen desetinná
část. To zní sice slibně, ale ve skutečnosti zbylá desetinná část zůstane a vloží se do sazby nebo způsobí jinou
neplechu; podle mne je to tedy nepoužitelné a pokud chceme odseknout desetinnou část, musíme si vytvořit
makro, kde nám nepotřebnou část „požere” druhý parametr. V případě přiřazení registru (z typu dimen do
number) se provede přesná kopie 4 bytů, např. 0.5pt = 32768.
Obráceně: V případě konverze z number do dimen nesmíme zapomenout přidat jednotky, a to hlavně pokud
použijeme místo konstanty registr, kde by se na to mohlo snáze zapomenout:
\dimen0=\count1pt
Jinak se zapíše do dimen 0! Do registrů dimen i number lze konvertovat také hodnotu z glue: tady se vezme
pouze první číslo z glue (tzv. základní velikost) a ta se uloží do dimen, případně se ještě převede uvedeným
způsobem na number.
Sčítání a odčítání
Konečně mohu začít tím, o co mi od začátku šlo. Začneme sčítáním (a odčítáním), které je ještě nejméně
zamotané; používá primitiv \advance. Funguje totiž se všemi uvedenými typy – jak s reálnými (typu dimen), tak přirozenými čísly typu number. K operaci potřebujeme alespoň jeden příslušný registr, ve kterém
je první číslo:
\advance\dimen0 by 2.5pt
\advance\count9 by -3
\advance\dimen0 by \count9
Pokud potřebujeme provést odčítání, přidáme jen znaménko minus. Registr, ve kterém máme uložené první
číslo, se po provedení operace přepíše výsledkem! Přičítat jiný typ než má registr nebude asi žádoucí, ale je
možné. V takovém případě se nejprve provede konverze, jak byla popsána. Obdobě jako dimen funguje také
\advance s typem glue, kde se budou zvětšovat/zmenšovat všechny tři údaje, tedy pokud všechny zadáme
(více o glue v TBN na str. 79). Místo konstanty za by lze samozřejmě pužít také registr s hodnotou. (pozn. \the
spolu s registrem nelze za by použít; povolená je jen přímá konstanta nebo registr!)
4
Hodnoty ze všech registrů (kromě box) lze extrahovat s použitím
také hodnoty typu font, chardef a mathchardef.
\the; kromě toho lze extrahovat
K expanzi maker slouží \expandafter. Někdy je třeba použít ve spojení s
podléhá jen druhý token. Expanze se provede do hloubky jedné úrovně.
\relax, protože expanzi
Násobení a dělení celými čísly
Obdobně jako sčítání funguje také násobení a dělení s použitím primitivů
omezením – možné je jen násobení a dělení pouze celým číslem.
\divide a \multiply. Ovšem již s
\multiply\dimen0 by-3
\divide\count10 by 4
Jednotky za násobitel ani dělitel nepatří, jak tomu bylo u sčítání s dimen. (Pokud jadnotky zadáme, použije
se jen číslo a jednotky v textu zůstanou, přičemž se nejspíš objeví v sazbě na výstupu!) U dělení typu number
se výsledek vždy zaokrouhlí na celé číslo. V případě typu glue jsou pronásobeny/děleny všechny tři hodnoty.
Ještě bycho měli poznamenat, že by může být vždy vynecháno a hodnota nebo registr může následovat přímo
za prvním registrem s mezerou i bez ní.
Násobení dimen reálným číslem
Zbývá už jen specifické násobení, které je podporované pouze u typu dimen. Jedná se o jediný způsob, jak
vynásobit reálné číslo reálným. Násobení 0.5 * 2.5pt = 1.25pt vytvoříme takto:
\dimen0=2.5pt
\dimen1=0.5 \dimen0
U konstanty, kterou násobíme nesmí být jednotka (za číslem může následovat mezera). Místo konstanty můžeme
použít také celočíselnou hodnotu number z registru nebo číslo předané makrem. Makrem lze hodnotu předat
u všech způsobů, ale zde se nám to bude obzvlášť hodit. Registr dimen totiž přímo použít nemůžeme, protože
obsahuje jednotku pt. Makrem tedy musíme nejprve jednotku odstranit (dále uvedeno jako trik 1).
Jak už bylo napsáno, je tento způsob násobení pouze pro hodnoty typu dimen. Pokud bychom však použili
stejný způsob pro glue, například .2\baselineskip, pak bude fungovat násobení také, ač jen napůl – dojde jen k
vynásobení prvního čísla a zbylé hodnoty budou nulovány. Podle pravidel konverze se totiž hodnota glue nejprve
převede na dimen, pak proběhne násobení a pak – pokud opět uložíme hodnotu zpět – se převede na glue:
\baselineskip=.2\baselineskip
Můžeme tedy násobení s úspěchem použít, pokud nám nevadí, že budou hodnoty roztažení a stažení rovny
nule, a to i v případě, že před tím byly nenulové. Jinak bychom je museli ještě přidat za klíčová slova plus a
minus nějak takto:
\baselineskip=.2\baselineskip plus 1pt minus 1pt
Tímto jsme vyčerpali možnosti, které
nějakým menším trikem.
plain pro počítání nabízí. Pokud bychom chtěli víc, musíme to řešit
5
Trik 1 - násobení dvou reálných čísel (dvou hodnot typu dimen)
Pokud bychom takové násobení opravdu potřebovali, musíme si udělat makro (\soucin), kde využijeme uvedené
násobení typu dimen.
{\catcode‘p=12 \catcode‘t=12
\gdef\noPT #1pt{#1}}
\def\thedimen #1{\expandafter \noPT \the #1}
\def\soucin #1 * #2 {\thedimen{#1} #2}
a pak již můžeme vynásobit dvě hodnoty typu dimen:
\dimen0=0.4pt
\dimen1=3.3pt
\dimen2 = \soucin \dimen0 * \dimen1
Celý trik spočívá tedy v odstranění pt u prvního dimen. Pokud bychom chtěli mít v
reálné číslo bez jednotky, pak použijeme ještě makro \thedimen.
\dimen2 jen výsledné
\thedimen{\dimen2}
a hodnota se nám extrahuje jako při použití \the, jen bez
přidal do předchozího dílu, kde je vysvětleno.
pt na konci. Makro pro odseknutí pt jsem dodatečně
Trik 2 – poměr dvou reálných čísel
Vynásobit dvě reálná čísla už tedy dokážeme a zbývá získat poměr dvou reálných čísel. Zde si můžeme pomoci
převodem hodnoty z dimen na number. Dělení pak provedeme pomocí primitivu \divide. Zajímavý příklad,
který ukazuje jak na to, je v TBN na str. 82. Mějme dvě reálná čísla A a B a potřebuje zjistit poměr C = A /
B (pozn. znak % uvozuje v TeX(u) komentář):
\dimen0=2.5pt
% A
\dimen1=5.6pt \count1=\dimen1 % B
\divide\count1 by1000
\divide\dimen0 by\count1
\count2=\dimen0
% C * 1000
\the\count2
number bude obsahovat tisícinásobek se třemi desetinými ciframi (pozn: přesnost nic moc
a může být i +/- 3 desetiny). Hodnotu z \dimen0 bychom mohli přímo použít například za scale pro
nastavení velikosti fontu. Jelikož je za scale vyžadována hodnota typu number, došlo by ke konverzi a my
Výsledek typu
bychom jí nemuseli řešit jako zde, na předposledním řádku. Pokud by nám tisícinásobek nevyhovoval a chtěli
bychom reálnou hodnotu dimen, pak jen číslo uložíme jako dimen a vydělíme tisícem:
\dimen2=\count2pt
\divide\dimen2 by 1000
\the\dimen2
% C
(Doplněno: Uvedené řešení k posunutí desetinné čárky je sice jednoduché, ale výsledné číslo musí být v intervalu
<0,16.364>, jinak dojde k přetečení. Tento jednoduchý způsob se může hodit, jakmile hodláme vypočítat poměr
s výsledkem třeba v intervalu <0,1>. Jinak musíme provést posunutí desetinné čárky na úrovni ASCII znaků,
o což jsem se také nakonec pokusil. Trochu jsem si při tom pohrával s makry, aby se chovala stejně jako makra
pro násobení a dělení.) (pro ukládání hodnoty do registru dimen viz TBN na str.76)
6
Malý trik – dělení dvou reálných čísel s celočíselným výsledkem
V tomto případě lze využít výše popsané vlastnoti konverze z
dimen na count a provést dělení s čísly typu
count.
\count3=\ht0
\count4=\baselineskip
\divide\count3 by\count4
Takto lze snadno vypočítat počet řádků, které zabírá např. obrázek uložený v box0.
Závěr
Tím je snad vše potřebné osvíceno a zbývá maximálně sestavit krátká makra, která vše v případě lenosti ještě
víc usnadní. Makrům už jsem se pomstil posledně, tak už nemají šanci moc vzdorovat. – Jak jsem si jen mohl
tehdy myslet, že si tohle všechno budu pamatovat: holá naivita to byla.
26.12.2014
Bashism
shell ; bash ; Linux
Rozhodl jsem se vyhlásit válku bashismu, který už příliš bují v mých skriptech. Od teď se snažím skripty
optimalizovat i pro jiný shell, a ne jen pro bash. Je to celkem zábava, taková vojna. Jako detektor používám
perlovský skript checkbashisms, který vyčuchá kdeco. Jelikož výstup jen upozorní, ale řešení vždy neposkytne,
hodí se také tahle stránka, kde lze najít hlášku z výstupu skriptu checkbaskisms a u ní vhodné řešení, které
bashism odstraní. Hezká tabulka uvádějící, co je a není bashism, se nachází zde. Také je třeba myslet na
to, že některý extra lehký shell může protestovat i proti optimalizovanému skriptu. Například ash neumí
aritmetické operace s hexa čísly a místo $(($i hex+3)) je třeba použít pro výpočet externí program, třeba awk
(s přepínačem –non-decimal-data). Pokud dodržíme i takové maličkosti, je už velká pravděpodobnost, že skript
poběží s kdejakým shellem (ne vždy je to ale nezbytně třeba).
Na závěr jsem si vzpomněl na jednu legrácku. Je to hra vytvořená v bashi, která demonstruje, jednak co je v
bashi možné, ale hlavně, jak takový skriptík – ve kterém jsou záměrně používané bashovské operace – dokáže
procesor zatížit.
19.12.2014
Nový slovník Lexicon 5
program
Po deseti letech jsem až nyní investoval pár korun do softwaru. I tentokrát se jedná o slovník Lingea, tentokrát
však ruský. Myslím, že nemá cenu cokoliv vymýšlet, třeba hledat různá on-line řešení: 397 korun (se slevovým
vánočním kupónem) není mnoho peněz a za ty ušetřené nervy a pohodlí to jistě stojí. Navíc, když je možné
provést instalace na dvou počítačích. – Nejedná se sice o velký slovník, ale myslím že v případě ruského jazyka
to není až tak kritické; obsahuje 73 000 slov, zatímco velký 97 000. – Zatím se zdá, že funguje vše jak má. Pro
svoji zhýčkanost jsem jen musel trochu upravit spouštěcí skript run once only, aby oba slovníky nekolidovaly
při spouštění, maximalizaci či minimalizaci klávesovou zkratkou. Nový slovník Lexicon 5 je navíc už taková
ta moderní mrcha, co se snaží o super vzhled bez pořádného okýnka, a tak uvidím jak to s run once only
pojede; zatím se zdá že jo.
19.12.2014
7
Pár skriptů pro úpravu textu hlavně v konzoli
vim ; TeX ; WEB ; bash ; CONFIG ; mutt
E-mailový klient mutt mi léta spolehlivě slouží, a já se až nyní rozhodl pro pár změn; ani ne z důvodu že bych
to opravdu potřeboval, ale prostě jen z dlouhé chvíle. Jedná se o dva skripty, které vytvoří z textového výstupu
v konzoli náhled tak, jak by mohl vypadal e-mail v běžném prohlížeči, a to jak mail ve formátu /text/plain,
tak i maly v text/html. Občas se totiž hodí mít text v trochu úhlednější podobě, než jen výstup v terminálu.
Další skript slouží k extrahování zpráv a příloh z uloženého e-mailu (nejčastěji s příponou eml). A posledním
skriptem bude nedávno slibovaný parser z prostého textu – vybavený několika formátovacími značkami – do
pdf. (Plus ještě jeden zapomenutý na závěr.)
emailViaTex.sh
O textové e-maily (přílohy) se nyní v programu mutt stará skript emailViaTex.sh. Jak už název napovídá, má
úpravu na svědomí TeX. Skript jen doplní potřebné řídící značky a program cspdfplain upravený soubor převede
do pdf; soubor se pak otevře v nastaveném pdf prohlížeči. Tak tomu je pod pod x-serverem, ale v konzoli se
tento soubor ještě dále převede na obrázky ppm, které se zobrazí přes framebuffer pomocí prohlížeče fbi. – Po
ukončení se navíc zobrazí dialog, který nabídne tři možnosti: zanechat soubor v /tmp/printable message.pdf
tak jak je; před vytvořením verze pro tisk ještě provést úpravy v tex souboru (např. umazat staré zprávy aj.);
a třetí možnost – skončit bez vytvoření souboru pro tisk.
Skriptu je možné zadat – kromě vstupního souboru – přepínač -f (formated). S použitým přepínačem dojde
ke spojení řádků, které byly ukončeny odřádkováním. Prakticky to znamená, umístění řídící značky TeX(u)
\obeylines až do místa začátku citace (původní zprávy v odpovědi, pokud tam nějaká je). Odstavce spojených
řádků musí tvořit prázdné řádky – pokud je tímto způsobem zpráva napsána, může se formátováním výrazně
vylepšit vzhled výstupu, třeba pro tisk.
Skript emailViaTex.sh je třeba nastavit v ~/.mailcap – pro přílohy typu text/plain. A pak už lze v okně
příloh programu mutt u vybrané přílohy skript spustit klávesou m; přičemž funkce klávesy enter zůstává
zachována pro zobrazení zprávy v textové podobě. Formátovaný výstup – skript volaný s parametrem -f –
mám namapovaný na sekvenci kláves ,m. V tomto případě je ale spouštění skriptu programem mutt trochu
odlišné; nepoužívá ~/.mailcap. Klávesová zkratka je svázána s makrem v konfiguračním souboru ~/.muttrc,
ve kterém makro spouští skript. Zde jsem musel vyřešit jeden menší problém, protože jediný způsob, kterým
se nechá předat skriptu v makru zpráva, je nejspíš pomocí stdin. Tím jsem narazil na potíž s programem fbi,
který v takovém případě hlásí chybu framebufferu; ani nastavení -device v parametru nepomohlo. Proto
jsem se rozhodl v makru v souboru ~/.muttrc, vyřešit vše ukládáním do dočasného souboru; ten se zadá v
argumentu skriptu.
Spouštění skriptu mám namapované na klávesu F6 v editoru vim a není proto problém, vytvořit si rychlý náhled
i u rozepsané zprávy. Navíc, pokud se v editoru text označí ve visual módu (a opět stiskneme F6), skript vytvoří
náhled jen z označeného textu. Namapování na klávesu F6 mají na svědomí dva řádky ve ~/.vimrc.
mailcapHTML.sh
Druhý skript mailcapHTML.sh funguje ve stejném duchu, ale pro zprávy ve formátu text/html. – Pro převod
z html formátu jsem chvíli experimentoval s programy CutyCapt a wkthtmltoimage. Nakonec jsem vybral
už osvědčený wkthtmltoimage, který – zdá se mi – dělá výstup o něco lepší. Hlavně při exportování do pdf
nebyl CutyCapt schopný správně vytvořit předěl stránek, a někde tak odřádkoval v půlce řádku. O stejném
problému jsem psal také v případě wkthtmltopdf, ale statická verze tímto nešvarem netrpí. Oba programy
(vyjma statické verze) používají knihovny WebKit a může být tedy problém v mé instalaci, která není aktuální.
Díky tomu byla moje volba jasná: wkthtmltopdf (statická verze) pro tisk a pro náhledy wkhtmltoimage
(nemusí být static).
8
Skript má kromě samotného souboru jen jediný přepínač, a tím je název použitého kódování. Pokud není kódování uvedené, pak se skript pokusí provést autodetekci. Ta probíhá ve dvou fázích: nejprve se hledá charset=
na prvních dvou řádcích zpracovávaného textu a pokud neuspěje, dojde na detekci pomocí file. Tento způsob se používá také u prvního skriptu. Důvod, proč jsem přidal detekci u prvních dvou řádků, bude zřejmý u
dalšího skriptu.
Tento skript je dělaný především pro konzoli, protože pod x-serverem není třeba zprávu v html převádět, ale
stačilo by ji otevřít v html prohlížeči. Spouštění zadaného prohlížeče je ve skriptu přidáno jen pro nezávislost na
používaném prostředí – tento skript byl vytvořen především pro konzoli. V konzoli je zapotřebí wkhtmltoimage,
který zprávu převede na obrázek, který se pak zobrazí prostřednictvím programu fbi. Na konci skriptu je opět
dialog, který nabízí možnost vytvoření pdf souboru (pro tisk). V případě potvrzení vytvoření pdf souboru dojde
ke spuštění wkhtmltopdf. Skript – stejně jako předchozí – musí být zapsán v ~/.mailcap; tentokrát pro soubory
text/html. V menu příloh programu mutt se skript spouští obdobně klávesou m.
eml2txt.sh
Jelikož oba skripty obdrží z programu mutt vyseparovanou zprávu z těla e-mailu, nemusel bych extrahování
z mailu řešit. Občas se mi ale dostane do ruky soubor s příponou eml, který obsahuje celé tělo zprávy včetně
příloh. Abych mohl uvedené skripty použít pro prohlížení zpráv takového souboru, vytvořil jsem tento krátký
skript eml2txt.sh, který dokáže ze souboru vycucnout jen požadovanou část a poslat ji na stdout. Předchozí
skripty zvládnou číst ze stdin a tak se nabízí, přidat je za tento skript přes rouru; pouze v konzoli je třeba opět
provést vše přes cat a dočasný soubor, kvůli uvedenému problému s framebufferem, takto:
eml2txt.sh file.eml html | cat > /tmp/eml.tmp; mailcapHTML /tmp/e
ml.tmp; rm /tmp/eml.tmp
Kromě výstupu zprávy v plain/html nebo plain/text, se na prvních dvou řádcích zobrazí informace z hlavičky
Content-Type a Content-Encode, kde je možné najít informaci o kódování (viz předchozí autodetekce
kódování). (Lze je vždy snadno dodatečně odmazat.)
Jednotlivé segmenty mailu se nechají zobrazit přepínačem list na konci (stejné jako bez přepínače). Po jejich
zobrazení už jen zbývá rozhodnou, co má být extrahováno a použít jeden z přepínačů: plain, html, plain64,
html64 nebo attach. Poslední přepínač extrahuje z mailu přílohy a uloží je do stávajícího adresáře.
blogViaTex.sh
V předchozím zápisku jsem slíbil, že se tu objeví parser prostého textu se značkami, kterým píši svůj blog. A je
to tady! Vzhledem k tomu, že značky, které jsem si vytvořil, nejsou nějak specifické jen pro psaní blogu, lze je s
výhodou používat i pro psaní běžných dokumentů. Skript blogViaTex.sh funguje obdobně jako popsaný emaiViaTex.sh, jen s tím rozdílem že formátuje text podle popsaných značek. Dokonce i text vedle obrázku zvládá,
ale je to zatím jen v experimentální fázi, která bude chtít ještě odladit a možná i udělat trochu jinak. Tento
skript mám v editoru vim namapovaný na klávesu F7 a stejně jako u prvního skriptu se nechá vytvořit náhled
také z označeného textu. Nechybí ani možnost zanechat kopie pro tisk v /tmp/printable message.pdf.
bibleViaTex.sh
9
A konečně poslední upravovač textu se jmenuje – bibleViaTex. Jedná se
o jednoduchý skript, který dokáže zformátovat text ze skriptu bible.sh a
vytvořit z něho úhledný pdf soubor (v konzoli opět výstup převede na
obrázky a zobrazí přes framebuffer). Zajímavé na tomto skriptu je snad
jen velké počáteční písmeno, které se vkládá na začátek názvu kapitoly.
Jedná se o poměrně složité makro z Olšákovo stránek. Makro ale nebylo
přizpůsobené pro písmo s diakritikou; háček nebo čárka musí ze sazby
vyčnívat, jinak výsledek nevypadá hezky. Přidal jsem proto do makra, vedle nastavení horizontálního posunutí, také vertikální a tím jsem problém
vyřešil.
17.10.2014
V bashi ve vim stylu
bash ; vim
Tak to jsem opravdu nevěděl, že to jde! A mám z toho radost, protože jsem se, v případě dlouhých
už několikrát s příkazovou řádkou pral. Přitom stačí jen napsat: set -o vi.
sed zápisů,
Peteris občas uhodí přímo na hlavičku, a proto jeho blog rád pročítám. Navíc tam má parádní tahák, který se
hodí i pro základní práci s editorem vim.
6.10.2014
Když zatuhne terminál
Linux ; terminal
Stalo se vám někdy, že vám po nějakém tom přehmatu na klávesnici zamrzlo terminálové okno nebo dokonce
konzole? Mně jo, a nikdy jsem nepřišel na to, co bylo příčinou. Až dneska jsem narazil na jednom zajímavém
blogu na článek, a myslím, že je záhada vyřešena: Ctrl+s = freeze. Kdybych znal ještě Ctrl+q = unfreeze,
tak jsem mohl terminál používat bez problému dál.
5.10.2014
Jak jsem poznával TeX III - záludná makra
TeX
V tomto zápisku hodlám sepsat zásadní věci týkající se maker. Dost jsem si s nimi na začátek užil, a tak jsem
si řekl, že se jim pomstím a napíšu o nich; jinak jsou makra popsána v TBN od str. 31, kde se používá pro
vysvětlení trochu jiných pojmů. Já se chci pokusit základní věci sepsat v trochu lehčím podání, ač možná s
jistými nepřesnostmi.
Když jsem s TeX(em) začínal, ukázala se být makra velkým problémem. Makra programovacích jazyků, které
znám, jsou si víceméně podobná. Na co jsem ale narazil v TeX(u) mě dost zaskočilo. Z publikací od Olšáka
vyplývá, že tvůrce maker by měl být zkušený programátor, který už mnohé prostudoval. Jenže často i pouhý
uživatel, který nemá v úmyslu vytvářet parádní makra, potřebuje jednoduché makro pro vkládání nadpisu,
vzorce nebo něčeho jiného. Ale i s těmito skromnými požadavky bude nejspíš při tvoření maker (nebo jen úpravě
již hotových) pěkně zakopávat, než se dobere očekávaného výsledku.
Po přečtení tohoto dílu by měl makra zvládnout i začínající uživatel, který toho zatím moc o TeX(u) nepřečetl.
Samozřejmě je to návod hlavně prom mě, protože se dá předpokládat, že vzhledem k mnohým zvláštnostem
brzy půlku zapomenu.
10
Makro bez parametru
Makra v TeX(u) budeme většinou potřebovat k tomu, abychom nějakým způsobem upravili text – vkládáním
jiného textu nebo řídících sekvencí. Kromě toho můžeme makra také potřebovat pro různé jiné práce, porovnání,
nastavení proměnných, separování hodnot, provádění výpočtů aj. Taková makra ale stejně použijeme uvnitř
jiného makra, kterým ve výsledku nějak ovlivníme text, aby výstup odpovídal naší představě. Pro začátek
začneme jednoduchým makrem bez parametru:
\def\mojeMakro{text {\it vložený} makrem}
Makro vytvoříme pomocí primitivu \def (jiné možnosti budou uvedeny na závěr, většinou ale použijeme \def).
Následuje název makra (tj. nově vytvořená řídící sekvence), a pak už zde máme jen tělo makra uzavřené ve
složených závorkách. Všimněme si ještě dalších složených závorek uvnitř, které však mají jen skupinovou funkci
a omezí platnost \it (vytvoření kurzívy). Zde není asi nic překvapivého.
Makra s parametry
Zvláštnosti se nám pomálu začnou objevovat u maker s parametry. Makro s jedním parametrem (bez zarážky)
vypadá následovně:
\def\mojeMakro#1{text {\it vložený} makrem s parametrem "#1" byl
právě umístěn}
Opět asi nic překvapivého. Makro se pak zavolá:
\mojeMakro {můj první parametr}
a výsledkem bude:
text vložený makrem s parametrem ”můj první parametr” byl právě umístěn
Přičemž slovo vložený bude kurzívou. Zde asi čtenáře napadne, co se stane, pokud text můj
nedáme do závorek. Já rovnou prozradím, že výsledek bude:
první parametr
text vložený makrem s parametrem ”m” byl právě umístěnůj první parametr
a to by se nám asi nelíbilo. V definici makra se totiž pod #1 ukrývá token, který za volaným makrem následuje.
Pro podrobné vysvětlení, co všechno token je, čtenáře odkáži na tabulku v TBN na str. 19,20. Zde jen krátce
uvedu, že token je třeba \it nebo naše makro \jineMojeMakro, protože token tvoří každá řídící sekvence,
ale také samotná písmena jsou tokeny, číslice a většina speciálních znaků (jedná se o token typu uspořádaná
dvojice). Přičemž jediné co parametr makra #1 dělá, je že načte (přesněji vyjme) jeden token, kterým je zde
písmeno m. Pokud by naše makro mělo dva parametry, měli bychom v druhém parametru další písmenko, nebo
pokud by následovalo třeba \it, pak by se do parametru načetl tento token typu řídící sekvence. – Ještě si
zde všimneme, že zbytek, který se nám nenačetl do parametrů, nezmizí, ale přidá se na konec za volané makro
(jakoby projde makrem bez jeho zásahu)!
Posloupnost více tokenů se může do parametru načíst tehdy, pokud je uzavřená ve složených závorkách, jinak se načte do parametru vždy jen jeden token. (To platí pouze pro parametry bez zarážek;
makra se zarážkou budou vysvětlena dále.)
V předchozí ukázce jsme za názvem volaného makra (před argumentem) udělali mezeru, a až teprve po té
následuje text, který si dělá nárok, stát se parametrem makra. Co když ale za volané makro mezeru nedáme?
Není nad to si to vyzkoušet, vlastní zkušenost je zde totiž velmi cenná. Pak poznáme, že výsledek bude stejný,
protože první mezera se vždy ignoruje (jak u parametru bez zarážky, tak u parametru se zarážkou). Pokud ale
nepoužijeme závorky nebo řídící sekvenci začínající \, pak se nám text parametru spojí s textem makra a TeX
zahlásí chybu s tím, že makro mojeMakromůj nebylo definováno, proto ta mezera.
11
V definici makra může i nemusí být v místě před prvním argumentem mezera; první mezera se totiž
vždy ignoruje jak u parametru bez zarážky, tak i se zarážkou, případně odrážkou. Stejně tak se ignoruje
mezera v případě volání makra (viz TBN str. 19).
Ještě více pravidel musí mít začínající uživatel na paměti, jakmile bude chtít předat makru parametrů několik.
Bez problémů asi ještě bude, předat parametry, které bezprostředně následují:
\def\mojeMakro#1#2#3#4{text {\it vložený} makrem s parametrem #1#
2#3#4 byl právě umístěn}
přičemž v případě zavolání makra bez závorek takto:
\mojeMakro můj první parametr
se do parametrů uvnitř makra načte #1=m, #2=ů, #3=j, #4=p; všimněme si, že ve čtvrtém parametru není
mezera, ale písmenko p. Mezera se totiž ignoruje!
A pokud bychom dali celý výraz můj první parametr do závorek? V takovém případě obdržíme chybovou
hlášku Paragraph ended before \mojeMakro was complete, protože se vše načte do parametru #1 a pro
#2, #3 a #4 už nezbude nic, co by se do nich mohlo uložit!
V případě parametrů bez zarážek se mezery ignorují, co bychom také s tokenem mezery dělali, že! U
parametru se zarážkou se ale mezery normálně načtou, tam už mohou být mezery žádoucí!
V našem ukázkovém příkladu je to celkem jednoduché, ale v praxi to vypadá tak, že makro mnohdy voláme
v dlouhém řetězci tokenů (textů a řídících sekvencí). Nejspíš by se tedy do parametrů #2, #3, #4 nějaký ten
token dostal. O to větší by mohlo být naše překvapení nad tím, jak se makro podivně chová.
Makra s parametry se zarážkou
Ostražitost při natahování tokenů do parametru uvnitř makra je tedy na místě, a my často využijeme makra s
parametry se zarážkou (v TBN uváděné jako makra se separovaným parametrem):
\def\mojeMakro #1 #2 #3 {text {\it vložený} makrem s parametrem #
1#2#3 byl právě umístěn}
Tato ukázka definice makra se liší oproti předchozí tím, že jsme za jednotlivé parametry vložili znak mezery. A
pokud opět zavoláme makro jako v předchozím případě (bez závorek), dostaneme už asi to, co jsme chtěli:
text vložený makrem s parametrem můjprvníparametr byl právě umístěn
bez mezer, protože mezery nám sežraly zarážky. Zde může dojít k několika překvapením. Pokud zavoláme makro
takto:
\mojeMakro můj první parametr
vypadá vše celkem nevinně. V argumentu za můj a za první mezera je, za parametr jsem sice mezeru neudělal,
ale TeX bere konec řádku jako mezeru, takže to projde bez problémů, aniž by nám TeX vynadal. Jenže jak už jsem
psal, praxe je jiná. V dlouhém řetězci tokenů bude zřejmě následovat nějaká jiná řídící sekvence třeba jako:
\mojeMakro můj první parametr\jinemakro
nebo prostě bude jen ukončena závorka nějakého boxu při volání makra v těle jiného makra:
12
\mojeMakro můj první parametr}
a rázem máme v parametru uloženo něco jiného. Například u výrazu:
\mojeMakro můj první parametr\nadpisFont Hlavní nadpis
bychom mohli před tím očekávat, že dostaneme předchozí výsledek textu a za ním bude velkým písmem Hlavní
nadpis. Jenže vzhledem k tomu, že jsme nešetrně připojili řídící sekvenci až k textu, kde měla být mezera, uloží
se nám do #3 parametr\nadpisFont, a tím se změní na velká písmena (fontem pro nadpis) nejen text Hlavní
nadpis, který se vloží na konec, ale také text uvnitř makra za #3. Nejčastěji k takové chybě dochází tehdy,
pokud spojujeme řádky a neuvědomíme si, že makro na předchozím řádku vyžadovalo mezeru za posledním
argumentem, která tam byla v podobě ukončení řádku. Po spojení vše funguje rázem jinak nebo nefunguje
vůbec. V takovém případě je lépe na koncích mezeru dělat a za ní vždy vkládat % (maskování konce řádku),
čímž je potřeba mezery za posledním parametrem zřejmá. Řádek s volaným makrem měl vypadat takto:
\mojeMakro můj první parametr %sem se da jeste vepsat komentar
\nadpisFont Hlavní nadpis
Zde je asi vše poměrně zřejmé, ale v praxi narazíme na cizí makra, která vyžadují zarážku za posledním
parametrem v podobě mezery, a pokud ji tam neuděláme, TeX zahlásí – v lepším případě – chybu.
Jakmile budeme používat zarážky – kdy už se do parametrů nenačte jen jediný token – hrozí, že by se nám mohl
do parametru dostat obrovský kus textu, kde až na jeho konci bude někde znak odpovídající poslední zarážce.
Tohle je ošetřeno tím, že \def hledá pouze v jednom odstavci. Pokud bychom chtěli načíst parametrem více,
musí se použít před \def ještě \long, který umožní načíst i token konce odstavce \par.
Kromě dosud použitých mezer v místě zarážek je jedna, která je v makrech velmi často používána. Je to zarážka
konce odstavce \par, tedy prázdný řádek ve zdrojovém textu. Pokud tak chceme do parametru načíst jeden
celý odstavec, bude makro vypadat následovně:
\def\mojeMakro #1\par{Jeden celý odstavec: #1\par}
Makro se zavolá na řádku před odstavcem jako:
\mojeMakro
Text odstavce, který zpracuje makro právě začíná...
Všimněme si \par na konci v těle makra. Token \par nám totiž zarážka sežere, a pokud \par chceme zachovat,
pak nezbývá než jej přidat.
Napsal jsem, že složené závorky mají skupinový efekt a vše uvnitř se načítá jako jeden token. Jakmile ale
začneme používat zarážky – čímž už nás jednotlivé tokeny vlastně nezajímají –, je funkce takových závorek
v parametru opačná! Cokoliv, co bude v závorkách spolu se zarážkou, která by jinak vyhovovala načtení do
parametru, bude přeskočeno (viz také TBN str. 35)!
Argumentů můžeme samozřejmě zadat několik a v makru pak použít třeba jen jeden. Toto na první pohled
zbytečné počínání má svůj smysl, neboť jinak se třeba ke třetímu tokenu (nebo skupině tokenů) nedostaneme.
Tak například pokud bude v argumentu číslo v hranatých závorkách [123] s nějakým textem před první závorkou,
přičemž nás zajímá jen číslo uvnitř závorek, pak abychom odebrali hranaté závorky a dostali jen číslo, musíme
použít první parametr, který ale v makru nepoužijeme:
\def\hodnota#1[#2]{Hodnota v zavorce je: #2 a nic víc}
a pokud zavoláme makro:
13
hodnota odkaz[123]
dostaneme výsledek
Hodnota v zavorce je: 123 a nic víc
závorky nám požerou zarážky a text odkaz před číslem zůstane v parametru #1 a v makru se nepoužije. Trochu
překvapivého výsledku se nám dostane, pokud zavoláme makro jen bez textu odkaz:
\hodnota [123]
Mezera za makrem být nemusí, jak už bylo na začátku napsáno. V tomto případě bychom mohli očekávat, že
dojde k chybě, protože pro parametr #1 nebude existovat patřičný obsah. Nicméně zjistíme, že vše projde bez
chyby a výsledkem bude číslo zbavené závorek jako v předchozí ukázce. Pokud si necháme vytisknout obsah
parametru #1, uvidíme, že je prázdný. – Pokud tedy existuje patřičná zarážka k prvnímu parametru a před
ní nebude žádný obsah, který by se do parametru mohl načíst, TeX nezahlásí chybu; teď mi napadá, že to
tak musí být, protože jinak by znak v zarážce nemohl být nikdy jako první znak odstavce. Pokud bychom
ale přidali ještě jeden parametr před #1 (samozřejmě s přečíslovanými stávajícími parametry #1->#2 a #2>#3), pak už TeX zahlásí chybu: Runaway argument?. (Doplněno: parametry se zarážkou mohou být
prázdné! Například \def\a#1[#2[#3[{#2}\a[[[ je ok. Doplněno2: I parametry bez zarážky mohou být
prázdné. Zdá se, že vše funguje podle tohoto pravidla: parametr může bez chyby odebrat i jen mezery nebo
dokonce nic! Takže \def\isfirsttok#1#2^^X{#1} zahlásí chybu, pokud zadáme argument prázdný nebo
jen s mezerami. Do parametru #1 se natáhne „nic” {parametr bez zarážky v masce požírá mezery}. Na #2 už
nic nezbývá, což způsobí chybu. Pokud by byla zarážka za #1^^X {musí být přidána také do argumentu},
pak bude výsledek stejný až na to, že mezery v parametru zůstanou {což je jediný rozdíl parametru se zarážkou
– mezery uchová}. )
Parametry se zarážkou i bez zarážky se nechají libovolně kombinovat, jak jsme si právě ukázali. My díky tomu
můžeme číst do prvních dvou parametrů tokeny nebo výrazy sjednocené složenými závorkami, a za tím můžeme
mít další parametry oddělené zarážkami, které mohou mít v argumentu místa ve složených závorkách, která se
mají naopak přeskočit. Pokud máte pocit, že už tomu rozumíte, nebude od věci provést malý test ze strany 36
v TBN, kde je příklad, ve kterém jsou zahrnuty různé kombinace.
Způsob jak zbavovat text v argumentech různých částí, se nám může hodit. Jiný nástroj TeX totiž pro takovou
práci nemá. Většinou budeme potřebovat ještě podmínkou uvnitř ověřit správnost očekávaného výrazu. Jedno
mé makro, které kontroluje znaménko před číslem, vypadá následovně:
\def\testminus#1#2 {\ifx-#1 1\else0\fi}
Makro vrací 1 pokud se jedná o záporné číslo, nebo 0 pokud jde o kladné. Do #1 se načte první token, kterým je
+, - nebo cokoliv jiného. Pod #2 se schová zbytek čísla až po mezeru. Mezeru si však musím uměle vytvořit při
volání makra, protože za číslem z argumentu hlavního makra mezera není. Použití pak vypadá třeba tak:
\ifnum1=\testminus#4 \paddingL={\relax}\else\paddingR={\relax}\fi
Musím si tedy dávat pozor, abych nikde na mezeru za parametrem makra nezapomněl. (V TBN na str. 36
je obdobná ukázka, kde se detekuje přípona v názvu souboru.) Stále vám nepřijde, že je kolem maker dost
zvláštností? Pak přidám ještě jednu. Význam mezery v parametru makra se zarážkou je už asi jasný. Podobný
význam, který není záhodno opomenout, má však také mezera na konci uvnitř makra. Tady by se nám mohl
spojit poslední znak nebo číslo s tím co bude za makrem následovat. Ukázka problému je v TBN na str. 68. Nyní
už je asi tuplem jasné, že je třeba – ohledně mezer při tvorbě maker – být ve střehu.
Pozor na předávání číselného registru v argumentu, není to token! Pokud použijeme parametr bez
zarážky, načte se do parametru jen část bez čísla registru, ta je brána jako jeden token! Jako další
paranetr by se načetla jedna číslice.
14
Pozor také na předávání registru, pokud v makru počítáme se zpracováním hodnoty, a ne registru
(jako tokenu): abychom měli číselný argument ve fázi expanze makra, je třeba použít \expandafter\mojemakro\the\count1.
Zvláštní zarážka před prvním parametrem - odrážka
Doposud jsem si vystačil se slovem zarážka, protože se jednalo o značku, která byla umístěna vždy těsně za
parametr makra. Jenže i na začátku (před prvním parametrem) se nechá taková zarážka použít. Vzhledem k
prvnímu parametru to však bude odrážka. Jiný název se hodí také proto, že funguje trochu jinak než zarážka
za posledním parametrem. Za poslední zarážkou totiž může následovat v argumentu zbylý text a ten se vloží
za výstup z makra (jak bylo na začátku vysvětleno), ale před odrážkou nesmí být v argumentu už nic jiného
(kromě první mezery, která se odstraní), jinak TeX zahlásí Use of definition \mojeMakro doesn’t match
its definition. Pokud tedy můžeme zaručit, že první parametr skutečně začne požadovaným tokenem (nebo
posloupností tokenů), můžeme odrážku použít třeba takto:
\def\hodnota[#1]{Hodnota v zavorce je: #1 a nic víc}
pak makro zavoláme:
\hodnota[123]
a do #1 se uloží 123. V TBN čtenář najde podobný příklad:
\def\sekce:\param=#1-{...}
, kde odrážku tvoří :\param= a zarážku pomlčka. Odrážku nemůže zřejmě tvořit jen mezera (alespoň zatím
nevím jak to provést). Zarážky a odrážka tak mohou být tvořeny libovolnými tokeny kromě #,{,}. Z těchto tří
lze jistým specifickým způsobem použít {, a to tak, že před závorku těla makra (tedy za poslední parametr)
vložíme #. Tento zvláštní způsob má navíc odlišné chování v tom, že zatímco ostatní zarážky požerou patřičný
znak (nebo více znaků), v tomto zvláštním případě znak závorky {zůstane.
Kategorie znaku (doplněno)
Pokud použijeme makro se zarážkou, nestačí někdy znát jen ASCII hodnotu znaku, ale je třeba vědět také o
kategorii znaku, který hodláme odchytit. Jinak se může stát, že dostaneme hlášku Runaway argument, aniž
bychom zaregistrovali v našem makru nějakou tu chybku. Na str. 80 v TBN je uvedeno makro, které odsekne
koncovku pt u hodnoty v registru typu dimen. – Zatímco u běžného textu jsou písmena abecedy všechna
kategorie 11 (viz tabulka v TBN na str. 20), existují jisté výjimky. Takový případ nastane, pokud použijeme \the
k extrahování obsahu v registru dimen, kdy pak dostaneme reálné číslo s jednotkou pt na konci. Jenže, všechny
znaky jsou zde kategorie 12! Pokud bychom tedy použili v makru jako zarážku pt (písmena {tokeny}kategorie
11), pak pt z textu 1.34pt, který vrátí \the, nemůže být makrem zachyceno. (Poznámku o kategorii vrácené
primitivem \the jsem našel v TBN na str. 75, třetí odstavec a pak na str. 441.) Pokud tedy chceme, aby nám
makro fungovalo, pak musíme nejprve změnit kategorii zarážky na 12 pomocí catcode. Písmena p a t se pak
nesmí v názvu makra ani řídící sekvence uvnitř skupiny vyskytovat; proto \noPT a v \def také písmena
nemáme. Vše vypadá následovně:
{\catcode‘p=12 \catcode‘t=12
\gdef\noPT #1pt{#1}}
\def\thedimen #1{\expandafter \noPT \the #1}
a po zavolání:
15
\thedimen{\dimen0}
dostaneme očekávaný výsledek – číslo bez pt na konci.
Podobné makro, které jsem si vytvořil, má za úkol odstranit desetinnou část až do konce (včetně
zarážku jsem v makru tedy použil tečku.
pt). Jako
\def\noDecimal #1.#2 {#1}%}
\def\thedimenNoDecimal #1{\expandafter \noDecimal \the #1}
Zde, jak je vidět, nemusíme kategorii tečky měnit: tečka má kategorii 12, a tím je to vyřešeno.
Jen krátce k \expandafter: Jelikož makro \noPT očekává ve svém argumentu číslo s pt na konci, musíme
nejprve rozvinout parametr #1, ve kterém se očekává token registru dimen (např. \dimen0). O rozvinutí
tokenu na hodnotu se postará – jak už bylo napsíno – \the. \expandafter zde má zá úkol přeskočit následující
token \NOpt, čímž se nejprve provede extrahování hodnoty pod \the a pak \expandafter teprve pokračuje
na token \noPT, kterému už se dostane v argumentu té správné hodnoty, ze které se odstraní pt. (pozn. token
je také závorka - tím může \expandafter skákat přes závorky a pokud je druhým tokenem závorka {neprovádí
žádnou akci}, nic se nestane) (více o \expandafter v TBN od str. 42)
Tímto bylo to hlavní okolo parametrů maker vysvětleno a nyní ještě pár věcí, na které uživatel nejspíš narazí
u cizích maker.
Makra definovaná uvnitř maker
Makra lze definovat také uvnitř jiného makra. V zásadě se nic nemění až na jednu drobnost, kterou jsou
parametry makra. U nich musí být zdvojená mřížka ##1, Pokud by byla další vnořená definice pak musíme
použít čtyři mřížky ####1 atd. (ukázka je v TBN na str.38)
Primitiv \edef a \gdef
Na začátku jsem zmínil, že existují i jiné primitivy, které lze použít místo \def k vytváření maker. Jeden z nich
je \edef. Písmeno e na začátku znamená expand. Zatímco \def v těle makra výrazy (tokeny) neexpanduje,
\edef ano. Pokud jste zatím nikde nenarazili na to, co expanze znamená, pak si stačí představit, že makro
volané v nově definovaném makru, se při zavolání nejprve celé rozbalí (expanduje) až na nejzákladnější prvky
(primitivy; stejně tak se expandují i registry a proměnné na konkrétní hodnoty).
\edef\cislo {\the\pageno}
Před použitím makra \cislo, bude \the\pageno nahrazeno číslem stránky. Pokud ale použijeme \def pak k
expanzi dojde až teprve po použití makra; změny které makro provádí tak probíhají v neexpandovaném tvaru
a expanze se provede až při konečném zpracování expnad procesorem. S \edef se nechá různě kouzlit, jako
například v ukázce z TBN na str. 39.:
\def\celkem{}
\def\naber #1 {\edef\celkem{\celkem #1}}
, kde tento zápis – který jsem v Olšákovo makrech na mnoha místech vyděl pro vytváření různých seznamů
– postupně akumuluje tokeny za sebe makrem \naber. Například po zavolání \naber 012, \naber 0345 a
\naber 013 bude na konec v těle makra \celkem 012023413. Pokud bychom, ale \edef nepoužili, pak by
došlo k rekurzi (nekonečná řada tokenů \celkem) už po prvním zavolání makra \naber a TeX by končil chybou
přetečení kapacity.
16
Ve stejné kapitole v TBN je ještě uveden primitiv \gdef, který je také někdy potřeba. Písmenko g znamená
global a říká, že definice makra bude mít globální platnost i přes to, kdy je definice provedena uvnitř skupinových značek, kterými jsou například složené závorky. Jen je dobré mít na paměti, že tyto závorky v případě
těla makra, nejsou skupinové závorky, a pokud bychom chtěli vytvořit uvnitř v těle makra skupinu, pak musíme
použít závorky ještě jednou (viz makro na začátku s kurzívou).
Něco o skupinových značkách
Tím jsem se dostal k poslední záležitosti, a tou jsou další skupinové značky. Místo složených závorek lze použít
\bgroup a \egroup, které nemají vliv na párování, viz vysvětlení s příkladem v TB na str. 340. Mohou být
použita třeba pro tvorbu maker, z nichž jedno je použito pro zahájení a druhé pro ukončení nějakého bloku.
\def\otevribox{\hbox\bgroup abc }
\def\zavribox{\vrule\egroup}
\otevribox ... \zavribox % je shodné s \hbox{abc ... \vrule}
Zde je to vlastně skupina napříč makry. Jelikož uvedené značky jsou alternativou k složeným závorkám, nelze je se
složenými závorkami kombinovat. Proto jsou zde ještě dvě obdobně fungující značky \beggroup a \endgroup,
kde tato dvojice lze s předchozími kombinovat.
Dosud všude bylo psáno a používáno párové uspořádání složených závorek (tzv. balancovaný text). Jsou ale
výjimky, kde to platit nemusí. To uvádím, jen pro to, že by to někoho mohlo vyděsit, až bude pročítat cizí
makra. Závorka }musí být sice použita vždy, ale zahajovací závorku {není třeba v některých případech psát.
Výčet těchto výjimek lze najít v TBN na str. 318.
Jen připomínám. V případě maker s parametry se zarážkou mohou mít závorky ještě i opačný efekt
než slučovací: cokoliv uvnitř závorek spolu se zarážkou, jenž by se jinak načetlo do parametru, bude
ignorováno; viz také TBN str. 35!
Diagnostika chyb
Jak je vidět, jsou makra v TeX(u) trochu víc využita, než jak bývá zvykem. Pro lazení složitějších konstrukcí
se nám tak bude hodit nastavení \tracingmacros=1, které zajistí, že se do logu zapíší parametry maker s
jejich obsahem.
Závěr
Málem bych zapomněl zmínit, že makra se nechají bez problémů předefinovat. Pokud ale přiřadíme sekvenci
nějaký význam pomocí \let, zůstane už nezměněna.
Tak to je snad vše podstatné i o něco méně podstatné, sepsáno jednodušeji, než jak jsem to našel v TBN. Je to
do začátku asi trochu guláš, ale když se to zažije, tak už to jde samo. Více přesných informací o makrech je v
TBN od str. 31.
1.10.2014
17
Jak jsem poznával TeX II – sloupcová sazba
TeX
Psát o TeX(u) je dost těžké, jak jsem zjistil, a mně se do toho vůbec nechce, protože vím, že vím ještě pramálo
a nejspíš se tak dopustím celé řady chyb a nepřesností. Jenže stále mi něco říká, že když si tohle nesepíšu, tak
za rok budu začínat opět od nuly. Už nyní – asi měsíc po tom, co jsem s TeX(em) pracoval naposledy – mnohé
nabyté vědomosti ztratily svůj glanc. Takže směle do toho a zkušenější uživatel případně promine.
Dnes mám tedy v plánu, pustit se do rozboru maker pro sloupcovou sazbu, které jsem si tak náramně na samý
začátek užil; na příklad uvedený na stránce budu často odkazovat. Je jich sice jen několik, ale fíglů a vychytávek
je v nich pro začátečníka, který ještě nemá ani přečtené potřebné publikace, poměrně dost.
Boxy
Slova box, hloubka, výška, účaří budou v textu hojně používány, a proto nebude od věci mít v paměti obrázek
s jedním takovým boxem a kouskem textu v něm.
V TeX(u) je boxem témeř vše: od největších boxů, které tvoří stránky; přes menší, ve kerých jsou uložené
řádky (horizontální boxy); až po ty nejmenší, které obalují jednotlivá písmena. Obrázek, který jsem použil, je
z publikace TeX pro pragmatiky od Olšáka, kde je navíc vše popsáno; vyčerpávající informace pak případný
zájemce nalezne v TBN od str. 94.
Řádkový rejstřík (také řádková osnova) znamená, že účaří u běžného odstavcového textu bude ve všech
sloupcích na stránce ve stejné výšce. Porušení řádkového rejstříku je v sazbě novin velkou chybou –
řádky ve sloupcích nesedí na stejných linkách.
Hloubka \prevdepth
Vzhledem k tomu, že pochopení
celý text z TBN ze strany 421:
\prevdepth je pro ukládání boxů do řádkového rejstříku klíčové, uvádím zde
\prevdepth obsahuje hloubku naposledy přidaného boxu do vertikálního seznamu. Tento parametr ovlivní
vložení případné meziřádkové mezery před další box.
Je-li vertikální seznam dosud prázdný, je \prevdepth nastaveno na magickou hodnotu -1000 pt. Tato hodnota znamená, že nebude před box vložena žádná meziřádková mezera z \baselineskip ani z \lineskip.
Makro může na tuto hodnotu nastavit registr \prevdepth záměrně, aby potlačilo meziřádkovou mezeru (viz
\nointerlineskip).
Vstupuje-li do vertikálního seznamu box, je provedena následující činnost:
•
•
•
Podle \prevdepth (předchozího) a výšky (nového) boxu se vloží meziřádková mezera (viz sekce 3.7 v
TBN).
Dále se do seznamu vloží samotný (nový) box.
Nastaví se nové \prevdepth, které bude rovno hloubce vloženého boxu.
18
Vstupuje-li do vertikálního seznamu linka (\hrule), je automaticky nastaveno \prevdepth=-1000pt, takže
následující box bude bez meziřádkové mezery.
Vstupuje-li do vertikálního seznamu jiný materiál (například \kern nebo mezera typu glue), není hodnota
\prevdepth měněna. Následující box tedy bude mít před sebou meziřádkovou mezeru podle hloubky předchozího boxu bez závislosti na tom, že jsou mezi nimi i jiné mezery.
Pokud zde budu používat zkráceně pojem
pomocí \vbox nebo \vtop.
box, bude se nejčastěji jednat o box vertikální, vytvořený
Sloupcová sazba
Základním principem je načtení textu do registru typu box s nastavenou patřičnou šířkou sloupce; tím vzniknou řádky potřebné šířky, které jsou zalomené podle nastavených pravidel (obdobně jako je tomu v odstavcovém módu). Řádky z registru se pak odebírají (odlamují) pomocí \vsplit do sloupců sazby podle potřeby
makrem \ulom.
Odlamování z registru a problémy s tím související
Primitiv \vsplit odlamuje z registru (typu box) počet řádků (nebo jiných vložených elementů) podle zadané
výšky. K odlomení – za normálních okolností – nemusí ale dojít zcela přesně v požadovaném místě: odlomení se
provede až v místě, kde je takový zlom vhodný (místo optimálního zlomu). Náš přesně zadaný počet řádek, by
se tak nemusel vždy přesně odlomit, pokud bychom neprovedli ještě několik nastavení. K přesnému odlomení
by nedošlo například tehdy, pokud by místo zlomu vyšlo do místa posledního nebo prvního řádku odstavce a
vznikl by takzvaný sirotek nebo vdova. Abychom tomuto jinak žádoucímu chování zabránili, musíme nastavit:
\widowpenalty=0 a \clubpenalty=0 (výchozí hodnoty jsou 150). Dále budeme muset vyřešit problém s
nulovou meziřádkovou mezerou u prvního řádku v registru (typu box), a pak ještě nespecifikovanou hloubku
na konci u posledního řádku (pokud není kolem nějaká výplň – nějaký box). Jelikož použijeme \unvbox, který
z registru odebírá jen text bez další výplně okolo, musíme se o tyto dva problémy postarat. – Řešení chybějící
meziřádkové mezery je jednoduché, ale s malým trikem.
\setbox\clanek=\vbox{\hsize=\colwidth \penalty0 \input historie.tex }
\setbox0=\vsplit\clanek to0pt
\setbox0=\vsplit\clanek to 9\baselineskip
Do registru clanek (registr vytvořený pomocí \newbox) se nejprve vloží obsah celého článku s patřičnou šířkou
podle \colwidth a nulovou penaltou před prvním řádkem; nulová penalta (místo vhodné k zalomení) je důležitá
pro první odlomení nulové výšky. Důležitý je další krok, který na první pohled nic důležitého nedělá, protože
výška odlomení je 0pt; navíc se \setbox0 hned v dalším kroku přepíše. Opak je ale pravdou, neboť se tím přidá
na začátek do registru clanek mezera vypočtená podle \splittopskip; mezera bude mít velikost \splittopskip
minus výška prvního řádku (prvního elementu). Mezera se pak do zbytku po odlomení (do registru clanek)
přidává (podle stejného výpočtu) už při každém dalším odlomení pomocí \vsplit (v našem případě vždy po
použitím makra \ulom). Hodnota \splittopskip je v příkladu na odkazované stránce nastavena na hodnotu
\baselineskip (výšku řádku).
Zpět ale k prvnímu odlomení, kterým bylo odlomení nulové výšky. Po něm nám v registru clanek zůstane
stejný obsah, jen s již správnou meziřádkovou mezeru na začátku u prvního řádku. V dalším kroku si můžeme
konečně odlomit třeba prvních devět řádků textu, které uložíme do stejného pomocného registru (0), ze kterého
je nakonec vyvrhneme do sazby. Ale až po té, co dáme do pořádku druhý problém.
Trochu jiné řešení přidání meziřádkové mezery u prvního řádku v registru je popsáno v TBN na str. 450.)
Odlomený materiál pomocí \vsplit může obsahovat odstranitelné elementy. Případná meziřádková
mezera na začátku – vytvořená pomocí \splittopskip (je typu glue) – po odlomení zůstane zachována
(nezmizí)!
19
A konečně poslední problém, který představuje nespecifikovaná hloubka (hodnota -1000pt) odlomeného materiálu u posledního řádku v registru (0). Ve funkci \ulom je to vyřešeno trikem s \lastbox, kde tento primitiv
vyjme poslední box (\hbox nebo \vbox) z vertikálního seznamu (v tomto případě poslední řádek v našem
pomocném registru (0)) a box opět do stejného místa vloží, přičemž při této operaci (stejně jako při vkládání
nového boxu) se nastaví hloubka posledního boxu zapsáním do \prevdepth. Makro \nointerlineskip zabrání
vložení meziřádkové mezery, protože tento materiál už tam máme.
\unvbox0 \nointerlineskip\lastbox
Nejprve se ale potřebné odlomené řádky, které jsme si předchozími kroky uložili pomocí \setbox0 do pomocného
registru, vyvrhnou použitím primitivu \unvbox0. Celý tento řádek je pak zabalen do boxu \vtop, který se
konečně umístí do sazby jako sloupec. Jen ještě jedna věc je třeba: než uzavřeme závorku \vtop, uložíme si
hloubku posledního řádku do proměnné lastdepth (typu dimen).
\global\lastdepth=\prevdepth
Za závorkou (po zpracování a vložení boxu \vtop do sazby) se totiž hloubka změní na hodnotu posledního
(uzavřeného) boxu, kterým je boxu \vtop, přičemž my budeme hloubku našeho boxu s posledním řádkem
(uvnitř boxu \vtop) ještě dále potřebovat, až budeme navazovat další text.
V ukázce na stránce odkazu si ještě všimneme zakončení v místě uvedeného výrazu pomocí % (až za závorkou
\vtop). Tím je zamezeno, aby následující znak nemohl být brán jako mezera. Pokud byste znak opomněli ve
svém makru přidat, jako já, pak mezery mezi sloupci nebudou stejné, a bude vám nejspíš dost vrtat hlavou, jak
je to možné. V makru \cl, které vždy následuje je totiž pevná nepružná mezera \kern, přičemž znak mezery
před tím by vytvářel další mezeru (pružnou mezeru)! Na tuhle drobnost je tedy třeba dát pozor.
Mezery následující za tokenem typu řídící sekvence se ignorují; za závorkou ale nikoliv, proto použijeme
% pro zamaskování konce řádku (který je v TeX(u) brán jako mezera)
Mezi každé dva boxy ve vertikálním seznamu se automaticky vkládá mezera, jejíž velikost je počítána
podle (A) z hodnoty \baselineskip nebo podle (B), kdy je přímo rovna hodnotě \lineskip; více
v TBN na str. 108. – Krátce řečeno: Jakmile se písmo (obecně hor. nebo vert. box určité velikosti)
nevejde mezi linky o vzdálenosti nastavené v \baselineskip, (u boxu je brána jen výška {tj. od účaří k horní
hraně}a nikoliv hloubka, která může přesahovat směrem dolů dle libosti), pak se takový box vloží hned za
předchozí s nastavenou mezerou \lineskip. Je-li ale box nízký tak, že se mezi řádky vejde, pak se takový box
položí svým účařím na pomyslnou linku vzdálenou o \baselineskip od účaří (linky) předchozího boxu, přičemž
meziřádková mezera (výplň typu glue) se dopočte podle (A).(Nejlépe je, představit si hodně malé písmo a velkou
vzdálenost linek, a pak je jasné, co musí být podle (A) doplněno do výšky řádku.) Pouze způsob (A) je žádoucí,
pokud hodláme dodržet řádkový rejstřík; v logu by se neměl nikde vyskytovat \lineskip, ale jen \baselineskip
a před prvními řádky na stránce \topskip (případně \splittopskip)!
Použitím \showbox0 ve správném místě, kdy je v registru (typu box) potřebný obsah, se nechá obsah
registru zapsat do logu; obdobně funguje \showlists pro právě rozpracovaný materiál v hlavním
vertikálním módu. Pro zobrazení hodnoty hloubky se hodí \message{\the\prevdepth} vložené za
zkoumaný box.
Referenční bod není nic jiného, než pomyslná tečka umístěná v úrovni účaří na levé straně boxu.
Vzhledem k požadavku zachování řádkového rejstříku musí být všechny referenčí body ve stejné vzdálenosti
pod sebou. U odlomeného textu tomu nemusíme věnovat velkou pozornost, protože účaří všech boxů s textem
mají hloubku danou podle písma a referenční body jsou tak pod sebou ve stejné vzdálenosti \baselineskip:
řádky se usadily na linky s meziřádkovou mezerou vypočtenou podle (A) (pokud jsme použili výšku písma
20
adekvátní nastavené vzdálenosti řádků v \baselineskip) během načtení do registru clanek na samém začátku.
– Pozornost ale musíme – v našem případě – věnovat vložení správné meziřádkové mezery tam, kam hodláme
vložit obrázek nebo box s jiným obsahem. Svojí velikostí by se totiž takový box vložil s mezerou typu \lineskip
a to – jak jsme uvedli – je nepřípustné. To je také důvod, proč se v makru \ulom vždy ukládá hloubka
posledního řádku do proměnné \lastdepth. Ve správný okamžik si ji totiž vždy z této proměnné vyzvedneme,
jak si ještě ukážeme.
V ukázce na uvedené stránce se navíc pracuje s maximální hodnotou uložené hloubky v \maxdepth pro případ,
že by bylo několik sloupců s odlomenými řádky vedle sebe například takto:
\hbox{\ulom[9]\ulom[9]\ulom[9]}
, pak by se použila hodnota posledního řádku z toho sloupce – po použití makra \ulom –, který má největší
hloubku; jako v první ukázce (na již zmíněném odkazu), kde čtenář čte nejprve text ve sloupcích nad obrázkem
a pak pod ním. Uložení jen té největší hloubky má na svědomí podmínka:
\ifhmode \ifdim\lastdepth<\maxhdepth \global\lastdepth=\maxhdepth
\fi \maxhdepth=\lastdepth \fi
Doposud uvedeným postupem si už můžeme z našeho textu odlomit přesný počet řádků potřebné šíře, které
vložíme do vertikálních boxů, pomocí funkcí \ulom a ty pak už jen umísťujeme vedle sebe s potřebnou mezerou, čímž máme základ pro sloupcovou sazbu. Ke způsobu vkládání obrázků nebo boxů s jiným obsahem se
ještě dostaneme.
\vbox nebo \vtop
Začínajícímu uživateli bude možná záhadou použití \vtop namísto \vbox, přičemž když vyzkouší použít \vbox,
dostane sloupce, které jsou zcela mimo stránku. Rozdíl obou boxů je v pozici účaří. Zatímco \vbox má (bez
explicitně nastavené hloubky) účaří v místě spodní hrany (respektive: kryje se s účařím posledního boxu seznamu;
viz TBN str. 102), \vtop má účaří v místě účaří prvního řádku, který je v boxu umístěn. Jelikož se boxy
dovnitř horizontálního boxu pověsí za účaří, je jasné, že pokud vertikální boxy \vtop vložíme (vedle sebe) do
horizontálního boxu, bude výška hor. boxu stejná jako výška prvního řádku uvnitř \vtop (tedy dost malá),
přičemž hloubku hor. boxu bude tvoří celý zbylý obsah ve sloupci (tedy bude dost velká). Pokud bychom použili
\vbox místo \vtop, pak se boxy \vbox opřou o spodní hranu, kde mají účaří, čímž by hloubka obalujícího
horizontálního boxu byla nula a výšku by tvořila celá výška nejvyššího vertikálního boxu – vert. boxy by tak
vyčnívaly kamsi daleko nad horní hranu oblasti sazby a my bychom obdrželi hlášení o přetečení. Zobrazit
účaří boxů \vtop a horizontálního boxu – který je obaluje – jsem se snažil znázornit na obrázku, který je
umístěn na konci.
Přesné usazení obrázků
Bylo by sice možné vložit box s obrázkem přímo za text, a obrázek by se jen vložil způsobem na základě
vypočtení meziřádkové mezery podle (B), protože jeho výška bude jistě přesahovat výšku jednoho řádku. Také
bychom obdrželi nejspíš zprávu o přetečení, ale obrázek by byl na svém místě. Problém by nastal s umístěním
dalšího textu ve sloupci, který bychom už nebyli schopni nějak spořádaně usadit tak, aby zůstal zachován
řádkový rejstřík. Proto se zde postupuje trochu jinak, než by různými WYSIWYG programy cvičený uživatel
mohl očekávat.
Nejprve za text vložíme mezeru (typu glue) makrem \vynech, která bude mít přesnou výšku velikosti zadaného
počtu řádek. Výšku zvolíme tak, aby se do ní vešel náš obrázek.
Vstupuje-li do vertikálního seznamu jiný materiál (například \kern nebo mezera typu glue), není
hodnota \prevdepth měněna. Následující box tedy bude mít před sebou meziřádkovou mezeru podle
hloubky předchozího boxu bez ohledu na to, že jsou mezi oběma boxy mezery.
21
Mezera
Nyní nastal okamžik, kdy se nám bude hodit uložená hloubka textu. V makru \vynech totiž poprvé nastavíme
\prevdept na hodnotu v \lastdepth. Děláme to zde jen pro případ, že by měl text pokračovat hned za
prázdným místem bez vložení obrázku (jinak se hloubka nastaví také při vkládání obrázku); nastavit hloubku
ale musíme pro případ, kdy obrázek zasahuje do sousedního sloupce, a kde už se jen vynechává prázdné místo.
Nastavení hloubky musíme provést, protože současná hloubka v \prevdepth neobsahuje hloubku posledního
řádku textu, ale hloubku celého boxu \vtop, ve kterém je část sloupce před obrázkem. Po tom co jsme o \vtop
a jeho účaří v místě prvního řádku napsali, je asi jasné, že hloubka v \prevdepth bude o mnoho větší než naše
potřebná hloubka textu, a proto ji bez milosti přepíšeme správnou hodnotou – tou z proměnné \lastdepth.
Obrázek
Obrázek vložíme makrem \placepic. Ten se umístí svojí spodní hranou těsně na spodní hranu vytvořené
mezery (díky \nointerlineskip). Obrázek vyplní prázdné místo, které jsme si před tím vytvořili, případně
bude zasahovat do textu nad ním nebo vyplní jen část prázdného místa (podle velikosti obrázku). Všimněme
si, že obrázek je zapouzdřen do boxu nulové výšky, ze kterého díky \vss vyčnívá směrem nahoru. Tento způsob
pokládání obsahu mimo svojí oblast je také jistým trikem, který se začátečník musí naučit. Je to totiž asi jediná
možnost, jak překrývat celé obsahy boxů.
Obdobně jako u mezery, i zde musíme nastavit \prevdepth hodnotou z \lastdepth. Správná hodnota, kterou
jsme už před tím nastavili ve funkci \vynech, se nám totiž přepíše po té, co se vložil nulový box (nová
nastavená hodnota bude nulová). Proto musíme opět \lastdepth zapsat do \prevdepth. Tímto máme uložený
obrázek a dokonce i s nastavenou správnou hloubkou, načež bychom mohli pokračovat v sazbě textu – opět
funkcí \ulom.
Korekce usazení obrázku
Podívejme se ale nejprve na výsledek s vloženým běžným textem (který se implicitně zabalí do boxu \hbox)
namísto naší funkce \ulom. V takovém případě by byl výsledek podle očekávání takový, že by se text vložil pod
obrázek s meziřádkovou mezerou vypočtenou podle (A), případně (B) pokud by byl příliš vysoký. Pokud ale
použijeme naše makro \ulom, výsledek bude vypadal jinak. Pod obrázek se totiž vloží mezera výšky jednoho
celého řádku podle (A), jak je také patrné z logu, o kterém budu psát dále. Pokud se ptáte jak je možné, že se
najednou před textem vkládá jeden celý prázdný řádek, aniž by tam byl potřebný kern nebo mezera typu glue,
pak je to tím, že box \vtop, který je v makru \ulom má účaří až u horní hrany – výška takového boxu je tedy
nula a vkládaná meziřádková mezera vyjde podle (A) – jeden celý řádek (velikost \baselineskip). – Po tom,
co jsme si uvedli o \vtop a účaří v místě prvního řádku (účaří), bude asi kdekomu vrtat hlavou, kde se vzalo
účaří až těsně u horní hrany. Nyní jsem se tedy dostal k dalšímu malému triku, který jsem doposud ponechal
v tajnosti. Bez něj bychom totiž měli pod obrázkem onu mezeru jednoho celého řádku, a to by nevypadalo
hezky. Přitom musíme mít na paměti, že pouhé malé posunutí následujícího textu nepřichází v úvahu, protože
by se tím narušila řádková osnova. Vše je tedy uděláno takto: V makru \ulom je na začátku posunutí směrem
nahoru o třetinu řádku (\kern-.3\baselineskip). Tím se text za obrázkem posune blíž k obrázku. A aby se
neporušil řádkový rejstřík, je posunuta směrem nahoru i vložená mezera (s ní i obrázek, který následuje) o
3/4 (\vskip-.7\baselineskip). Takto už je vše jak má být. Jen je třeba mít na paměti, že o 1/3 jsou všechny
sloupce posunuté nahoru, a při přesném nastavení horního okraje stránky musíme toto posunutí textu započítat.
– Nyní můžeme dovysvětlit záhadu s účařím u horní hrany boxu \vtop. Jako první je totiž do boxu \vtop v
makru \ulom vložen zmíněný záporný \kern. Ten si můžeme představit tak, že vyčnívá z boxu směrem nahoru
o patřičnou velikost a spodní hrana se dotýká horní hrany boxu \vtop, kde má tedy box \vtop účaří, protože
mezera typu \kern je zde prvním elementem seznamu. (Doufám, že jsem to popsal správně, protože zatím jsem
na zmínku o tom, že by mezera typu \kern také mohla vytvářet účaří nenašel. Každopádně výška je podle logu
jasně 0, přičemž těžko pro to hledat jiné vysvětlení.)
22
Obrázek přes několik sloupců se řeší tak, že jej do prvního sloupce vložíme popsaným způsobem a v dalších
sloupcích už jen vynecháme řádky pro obrázek (vložíme mezeru makrem \vynech), který tam svojí velikostí
zasahuje. Různá úvodní slova nebo vysvětlivky na závěr (přes více sloupců) se nechají v boxu umístit obdobně jako obrázek.
Nahlédnutí do logu
Pokud si vložíme \showlists za patřičné makro
obrázkem tuto část:
\ulom, můžeme v logu najít před textem, který následuje za
\glue(\baselineskip) 10.05556
\vbox(0.0+44.79996)x142.26378
.\kern -3.60004
.\glue(\splittopskip) 5.05556 plus 0.1 minus 0.1
.\hbox(6.94444+1.94444)x142.26378, glue set 1.96382
, ze které vyčteme, že náš box \vtop má hloubku 44,7px a výšku 0px (v logu se \vtop nezobrazuje, ale
poznáme jej podle nulové nebo malé výšky a hloubky odpovídající výšce řádků v \ulom). Je tedy jasné že
se jedná o náš \vtop box, který má účaří na horní hraně. Tento box se uloží za předchozí (box s obrázkem)
vložením meziřádkové mezery podle výpočtu (A) o velikost 10px (tedy jeden celý řádek). V boxu se dále děje
tohle: je vložen \hbox s textem prvního řádku ze čtyř, které jsme si odlomili. Před tento řádek, který již má
meziřádkovou mezeru z registru, kde byl uložen, je ještě vložen náš záporný kern, který posune text o kousek
výš, jak bylo popsáno. Také vidíme před boxem \hbox vloženou meziřádkovou mezeru, která pochází z registru
\clanek, kam se po odlomení předchozích řádků vložila; jak bylo uvedeno na začátku. Následuje už první řádek
textu zabalený do hor. boxu.
Z logů si lze všimnou, že výsledek jakékoliv práce v TeX(u) se ve výsledku skládá jen z boxů a mezer (\kern nebo
glue).
Vložení obrázku
Pro vložení obrázku se využívá makra inspic z balíku opmac. Nyní si ještě trochu rozebereme samotný způsob
umístění obrázku. Má ho na svědomí tento zápis v makru placepic:
\tmpdim=\prevdepth \nointerlineskip
\vbox to0pt{\vss\rlap{\hbox to#2{\hss #3\inspic #4 \hss}}\kern#1}\pr
evdepth=\tmpdim}
Nejprve si do proměnné (typu dimen) uložíme hloubku předchozího boxu. Jelikož budeme před tím vkládat
vždy prázdné místo pomocí \vynech, bude to hloubka prázdného boxu, která je – jak už jsme si vysvětlili –
převzata z proměnné \lastdepth. Makro \nointerlineskip zajistí, že se obrázek (přesněji box s obrázkem)
umístí hned za vynechané místo bez vložení meziřádkové mezery, která by jinak byla vypočtena podle (A). Box,
který se za mezeru vloží má nulovou výšku a obrázek, který se do něj vloží tedy vyčnívá směrem nahoru (díky
\vss). Pokud bychom chtěli ukládat obrázky jen do jednoho sloupce, mohli bychom už jen vložit obrázek pomocí
makra inspic. Jelikož, ale máme v plánu vkládat obrázky, které budou přes dva nebo více sloupců, musíme
zajistit také přesah obrázku směrem doprava do dalších sloupců, kde pak už jen vynecháme pro obrázek místo.
Tedy. . . Obdobně jako je zde nulový vertikální box, je zde také nulový horizontální box, ze kterého přečnívá
obrázek směrem doprava. Mohli bychom sice použít \hbox 0pt{...\hss}, ale to samé provede makro \rlap
(viz tbn str. 427). Celý obrázek je navíc zabalen ještě do dalšího boxu velikosti předané druhým parametrem
makra \placepic; třetí parametr pak nastavuje skutečnou šířku obrázku; čtvrtý parametr obsahuje jen jméno
souboru obrázku; kromě toho je zde ještě do vertikálního boxu přidána mezera o velikosti předané prvním
parametrem makra, kterým se může ještě doladit vertikální pozice obrázku. Po vložení obrázku je potřeba opět
23
nastavit předchozí hloubku (předchozí hloubka bude nastavená z makra \vynech tj. \lastdepth), aby u dalšího
vloženého řádku s textem mohla být dopočtena správná meziřádková mezera.
Layout
Tímto způsobem, kdy je potřeba udělat layout každé stránky, má sazeč výsledný vzhled plně pod kontrolou.
layout jedné stránky může vypadat například následovně:
\def\obrA{\picwidth=.8\colwidth \placepic[0pt,\colwidth,\picwidth
]{ruce.png}{}}
\hbox{ \vtop{\ulom[24]
\vynech[7]\obrA
\ulom[25]}
\cl
}
\eject
\ulom[56] \cl \ulom[56]
Makro pro vkládání obrázku mám ve své verzi trochu upravené, než jak je v ukázce na
odkazované stránce. Také si zápis upravuji mezerami do přehledné podoby sloupců
tak, aby čísla ulomených a vynechaných řádků korespondovala s umístěním na papíře.
Nejprve tedy připravím obrázek s popiskem zapouzdřeným do makra obrA, kde v
závorkách za placepic je nulový vert. posuv obrázku, \colwidth obsahuje hodnotu
šířky boxu, ve kterém bude obrázek umístěný, dále název souboru obrázku a případný
popisek k obrázku (zde jsou složené závorky prázdné). Pak již následuje rozložení
na stránce, kde jsou tři sloupce tvořené vertikálními boxy \vtop. Samotné makro
\ulom vytvoří uvnitř také vertikální box \vtop, a tak není potřeba jej ještě balit
do dalšího boxu \vtop, pokud je celý sloupec tvořen jen textem (přesněji jen jedním
makrem \ulom). Všechny tři sloupce ještě obalíme horizontálním boxem. Na konci je
\eject, kterým přepneme na další stránku. Výsledek takové stránky je na obrázku,
kde jsem se snažil vyznačit jednotlivé boxy barvami, a také naznačit místa, kde
se sloupce posouvají o kousek nahoru, jak bylo popsáno v části Přesné usazení
obrázků.
Různé drobnůstky
Ve svém souboru mám ještě pár maličkostí, které jsem postupně přidal. Brzy mi začínala vadit každá desetina
sekundy, o kterou trval překlad déle. A tak jsem přidal možnost vytvářet dokument bez obrázků jen s prázdným
rámečkem ve formátu dvi; spouští se csplain namísto pdfcsplain. Bez načítání obrázků je totiž sestavení
rychlejší. K tomu je ale potřeba vytvořit adekvátní soubory k obrázkům s příponou eps, které budou obsahovat
jen dva řádky s hlavičkou, ve které je rozměr obrázku např.:
%!PS-Adobe-3.0 EPSF-3.0
%%BoundingBox: -0 -0 632 420
Pro tohle mám samozřejmě skript v bashi, které se o vytvoření souborů a následný překlad postará; o skriptu
někdy příště. V souboru TeX(u) to znamená vypodmínkovat všechny řádky, které používají makra z balíku
pdftex pro sestavení pdf dokumentu a nahradit je těmi z balíku epsf pro sestavení dvi dokumentu. Některé
vlastnosti, které dvi nepodporuje se pak v dvi nepoužijí; v mém případě například rámeček se zakulacenými
rohy. Malé překvapení na mne čekalo u makra \spceial z balíku epsf, které nechtělo brát názvy souborů,
ve kterých bylo podtržítko (hlásí chybu: ! Missing $ inserted)?. Podtržítko je totiž možné bez zpětného
lomítka použít pouze v matematickém módu. Řešení problému jsem nalezl zde; a skutečně, znak $ před i za
názvem souboru (přepnutí do matematického módu) problém vyřešil.
24
Další drobností, která byla v ukázce v TBN na str. 277, je vytištění souboru pro korekturu. Takový výstup má
charakter jednoho dlouhého sloupce textu se stejným zalomením jako v článku a bez obrázků.
Další specialitou, kterou jsem si vymyslel, bylo vložení okna přes dva sloupce, které obsahuje v souboru s textem
makro s různými vysvětlivkami. Jelikož je to také jen box, je jeho vložení obdobné jako u obrázku.
Na konci článku jsem ještě potřeboval text v rámečku, který je už přes celou šířku stránky a nemá se sloupcovou
sazbou už nic společného. Zajímavostí u něj jsou snad jen zakulacené rohy, přičemž tahle parádička (pouze pro
výstup do pdf), potřebuje poměrně rozsáhlé makro, které jsem však jen zkopíroval z Olšákovo publikace TeX
pro pragmatiky.
A konečně tam mám také pár šikovných instrukcí pro lazení maker na konci: \tracingmacros,
spolu s \showboxbreadth. Výsledek popisovaného výtvoru jsem uveřejnil v předchozím díle.
\showlists
Závěr
Hladký průběh k pochopení maker pro sloupcovou sazbu by vyžadoval už vyšší znalost TeX(u) a také trochu
zkušeností. Pak by asi nebylo třeba mnoho psát, protože by bylo vše jasné. Já ale začal se sloupcovou sazbou, a
tak jsem se s tím musel trochu poprat. Výsledkem je tedy tento zápisek, ve kterém se snažím prokousat makry
s dosud minimálními znalostmi a zkušenostmi TeX(u). Pokud začínáte a podaří se vám to alespoň jako mně,
bude vám nejspíš dost věcí v TeX(u) už jasných, protože o přesném ukládání boxů výpočtem meziřádkových
mezer, a tedy prací s hloubkou boxu a umístění účaří, je celá sazba v TeX(u). Já se přiznám, že jsem se při
prokousávání místy pěkně zapotil a některé části jsem musel žvýkat dost dlouho. Dobrá zpráva je ta, že pokud
nepotřebujete dodržet řádkový rejstřík, pak vás tyhle věci zajímat příliš nemusí a on si TeX sám boxy za sebe
vloží s mezerami, které uzná za vhodné; tj. první řádek na stránce uloží účařím na vzdálenost \topskip a boxy
pokládá podle toho, jestli se vejdou mezi řádky s meziřádkovou mezerou (A) nebo (B). Jistou složitost způsobu
dodržujícího řádkový rejstřík může omluvit to, že většina známých neprofesionálních WYSIWYG programů
tohle pořádně nesvede ani dvacet let po vzniku TeX(u) (přesněji jeho třetí verze), dokonce i zalamování řádků
dokázal MS Word až od roku 2008.
Doplněno 15.10.2014: Zde uvádím mírně poupravený tex soubor, se kterým jsem sázel poslední přílohu do
obecních novin. Kompletní archiv je zde.
26.9.2014
vimrc
vim; CONFIG
Našel jsem si dnes chvilku a udělal pořádek ve .vimrc.
23.9.2014
Změna je život – zarovnávám do bloku
Deník
Lehce ovlivněn TeX(em) jsem se rozhodl – zhruba po čtyřech letech – odstranit rámečky a zarovnat zde text
do bloku. Mezery mezi slovy jsou sice děsivé, ale změna je život, ne. Tak uvidíme, jestli se mi to neokouká a
nevrátím to do měsíce zpět.
Doplněno 15.9.2014: klasické zarovnání vráceno zpět. Původní rámeček, nahradila tenká postranní
čára, která dojem z jinak opticky nevyrovnaného textu trochu zlepší. Rozhodně je to vkusnější než zarovnání
do bloku, to je v případě HTML opravdu odporné; nevím co mě to v pozdních nočních hodinách napadlo. No,
alespoň jsem se dopracoval k decentní lince.
25
Doplněno 26.9.2014: Opět jsem trochu experimentoval se vzhledem svých stránek a musím konstatovat, že
to se zarovnáním do bloku nemusí být až tak strašné. Čím větší písmo, tím se zdá být výsledek lepší. Hlavně
bez prokládání písmen, které je použité na této stránce, je výsledek znatelně lepší. A konečně podstatně lepší
výsledek jsem pozoroval u patkového písma. Pokud se tyto předpoklady dodrží, pak už je zarovnání do bloku
dobré. Zatím jsem jej použil na svém včelařském blogu, který má působit méně technicky.
3.9.2014
Jak jsem poznával TeX I
TeX
Od doby, kdy jsem prvně použil TeX, uplynul už víc jak měsíc, a já jsem stál před úkolem vydat další číslo
oběžníku. Tak jsem byl znovu donucen poznávat TeX. Všechna úskalí jsem sice u prvního vydání překonal, ale
už tehdy mi bylo jasné, že takhle to dělat nepůjde; hlavně jsem si nedokázal představit složitější layout nebo
větší rozsah stran. – Jakoby tohle všechno ostatní tušili, vzedmula se vlna požadavků – změnit počet sloupců
na tři, více obrázků a celkově barevnější layout a navíc rozsah vycházel tentokrát jednou tak velký. Aúúú. . .
”Nu což,” řekl jsem si a dal se do práce.
Ono se totiž každému lehce řekne o jeden sloupec navíc, jenže to znamená pokládat obrázky přes dva sloupce
(aby na nich bylo něco vidět), písmo vhodné velikosti, a také nadpisy přes více sloupců a nejspíš ještě úvodní
slovo přes dva nebo tři sloupce a případně také přes dva sloupce nějaký dodatek na závěr; prostě trochu
to rozsekat, aby z toho nebylo moře textu, ve kterém by se mohl četbě nezvyklý čtenář utopit. – Makro
\begmulti. . .\endmulti, se snaží za všech okolností o vybalancování sazby v dolním okraji sloupců (sloupce
končí vždy ve stejné úrovni), což však pro tvorbu novin není žádoucí; vlastně se dnes divím, jak jsem takovým
způsobem mohl první číslo dát dohromady. S dalším vydáním jsem tedy stál před problémy, se kterými jsem si
nevěděl ani po dnech pátrání rady.
V jisté slabé chvilce, kdy jsem toho měl už celkem dost, jsem se rozhodl požádat o radu přímo pana Olšáka.
Stejně jsem mu chtěl poděkovat, protože bez jeho rozsáhlého počinu bych první číslo nevydal a nejspíš bych
se ani do TeX(u) nepustil. Za několik dní jsem dostal odpověď se všemi potřebnými informacemi, ukázkou a
odkazy do TBN. Byl jsem opravdu potěšen. Mohl jsem se pustit do studia a začít konečně tvořit. Asi za tři dny
jsem měl třísloupcovou sazbu i se svým příspěvkem zhruba hotovou. Navíc konečně způsobem, kterým už se
opravdu noviny dají dělat bez toho, aby měl člověk po jisté době šediny.
Makra, která jsem si k tomu vytvořil – převzatá a upravená z TBN –, byla sice funkční, ale k dokonalosti
měla opravdu hodně daleko. Záplava varování o přetečení nebo podtečení boxů nebrala při překladu konce a
navíc jsem musel sáhnout po několika ”Ovečkinových konstantách”, které musely sloupce ještě trochu dorovnat,
protože dopátrat se chyby a plně pochopit některá převzatá makra jsem ještě nedokázal. Hlavně že jsem měl svoji
historickou přílohu hotovou a věděl tak, že svedu udělat celý oběžník; bez ohledu na ”špinavost” mého řešení.
Někdy v té době mi přišel od pana Olšáka další e-mail, ve kterém psal, že příklad s třísloupcovou sazbou umístil
na stránku do sekce Tipy, triky, návody. . . Já zatím od té doby něco málo nastudoval, a když se objevila hotová
odzkoušená verze na jeho stránkách, vrhnul jsem se ji prozkoumat a následně použít, přičemž jsem si svůj
původní výtvor uložil pro ukázku, jak se to dělat nemá.
Od příkladu zveřejněného na internetu jsem se už celkem dobře odpích a dodělal jsem další věci, které jsem tam
chtěl ještě mít. S výsledkem maker pro třísloupcovou sazbu (a zde zdrojový soubor textu v extra souboru) jsem
mohl být konečně spokojen. – A tak se ze mne stával sazeč :), co si pomálu začínal myslet, že už dokáže vysázet
cokoliv: prolistoval jsem doma pár časopisů a nad každou stránkou vždy znalecky pokýval hlavou, pokud jsem
věděl, že bych to také sved ;); bulvární noviny nebo časopis se u nás doma žádný nenacházel, a tudíž ”divočinu”
letákového-novinového vzhledu jsem se svým přesvědčením konfrontovat nemohl.
Jenže co se nestalo. Někteří náhle usoudili, že jim vzhled přijde příliš strohý a vytvořili si vlastní verzi ve
Wordu – inspirovanou e-novinami obcí z okolí – plnou podbarvených sloupců, barevných obrázků a různých
druhů písma. Sazba ve Wordu samozřejmě hrozná: sloupce mimo rastr, řádky sousedních sloupců v jiné výšce
26
a navíc zalamování bez zapnutého dělení slov, a tudíž s hroznými mezerami mezi slovy. Typografii prý dneska
stejně nikdo na vesnici neocení. Přičemž já jsem nemohl nic namítat, protože co já vím o smýšlení venkovských
lidí. A tak jsem dodal svojí historickou část vytvořenou v TeX(u), která se přiloží k barevné verzi (která však
bude vytištěna stejně černobíle). A jelikož jsem z toho typografického úpadku v dnešní společnosti byl jaksi
smuten, poslal jsem onu hroznou sazbu ve Wordu panu Olšákovi, aby věděl co tady u nás TeX válcuje. A také
jsem se zmínil, že už páchám podbarvený text sloupců, abych dokázal vyhovět v budoucnu případně i v tomto
požadavku. Pan Olšák mi odpověděl i s ukázkou, jak se takové barevnosti nechají udělat v TeX(u); což bylo pro
mne velmi inspirativní. Podbarvené sloupce jsem měl sice už připravené, ale opět trochu krkolomně; a přitom
může být řešení jednoduché, jen když se ví jak.
Druhé číslo našeho oběžníku tedy vyšlo ve Wordovsko-TeX(ovském) stylu, půl na půl. Dál se zřejmě
uvidí, jak dál. Já se tím každopádně naučil mnoho nového a TeX začal konečně systematicky studovat a
doma používat.
V dalších dílech hodlám psát o svých zkušenostech s tímto pozoruhodným programem, na kterém je znát, že
Knuth ho vytvářel opravdu s láskou – pro tvorbu hezkých dokumentů.
24.8.2014
AsciiPisálek – tahák
TEXT; WEB
Po dlouhé době jsem zase trochu vylepšoval pisálka: navíc jsem si udělal parser pro TeX. Nejen že dokážu tento
blog psaný od začátku v prostém textu pověsit na web, ale už si dokážu udělat také slušně vypadající pdfko ;).
– Ač zatím pisalek.php neni k publikovaní, parser pro TeX se tu možná brzy objeví. Dnes jediné co sem dám,
bude návod v podobě taháku, který jsem si konečně sesumíroval. Oproti kdysi zde zmíněné krátké verzi je tahle
podoba už asi i finální. Ani za několik let jsem totiž – pro tohle mé psaní – nic víc nepotřeboval.
20.8.2014
Sed-tahák
terminal ; sed
Konečně jsem dodělal svůj sed-tahák. Na internetu jsem sice pár pěkných taháků našel, ale není nad to, udělat
si tahák svůj – podle vlastního gusta.
6.8.2014
Stallman na přednášce TEDx
komunita
Opravdu hezká, stručná a pro širokou veřejnost srozumitelná přednáška Richarda Stallmana v Ženevě.
8.7.2014
27
První seznámení s TeX(em)
TeX
Nečekal bych, že se pustím také do TeX(u). Párkrát jsem zkusil LaTex hlavně ze zvědavosti, pro pár kancelářských dokumentů, ale moc spokojenej jsem nebyl: nějak to všechno drhlo. Ale to už je nějaký ten pátek. Možná
se dost změnilo, a také já mám více zkušeností, takže těžko hodnotit.
Stalo se to tedy tak, že na mne spadl úkol – vydat oběžník pro svoji rodnou ves. Ono to přesněji bylo tak, že nikdo
nevěděl přesně jak, a také se do toho moc ostatním nechtělo. Já toho také moc nevěděl, ale alespoň jsem tušil,
že to nějak půjde i bez koupě softwaru, což se záhy potvrdilo, když jsem našel mnoho balíčků na CTAN. Byly
to balíčky: newsletr, newspaper, papertex, flowfram – tedy mix latexových a texových; na začátku jsem
to totiž moc nedokázal rozlišit: teprve až když jsem zkoušel přiložené ukázkové příklady – při tom jsem čekal,
jestli z toho nevypadne něco, co by se podobalo jednoduchému, avšak důstojnému vzhledu oběžníku. Jenže čím
víc jsem se v tom hrabal, tím víc jsem měl pocit, že to nějak diverguje, místo aby to konvergovalo k nějaké jasné
představě o výsledku. Z toho množství maker a různých balíčků jsem byl pěkně zmatenej a ve výsledku z toho
nic jiného, než stejnej vzhled jako je v ukázce, nekoukalo. Navíc číst desítky stránek dokumentace ke každému
z nich se mi také moc nelíbilo. Trochu volněji jsem si připadal u TeX(ového) balíčku newsletr, se kterým
jsem si nakonec hrál trochu déle. Ale pak jsem zjistil, že s csplain nějak nefunguje správně, a tak jsem byl s
holýma rukama opět na začátku. – Teď jsem, ale trochu předběhl, protože jsem nejprve neměl nainstalovaný
na slacku csplain a v balíčku slacku TeXová distribuce s csplain není. Testování newsletr předcházela
tedy instalace.
Stáhl jsem si ze SlackBuild TexLive, a chtěl jsem se pustit do překladů. Velikost několik gigabajtů mě
zaskočila. Musel jsem přesměrovat /tmp a také 3 giga místa v kořenovém adresáři nebylo. – Vůbec s tím bylo
mnoho starostí, až jsem nakonec zjistil, že musím stáhnout kvůli jedné závislosti dva roky starou verzi. A to se mi
nechtělo, protože jsem zahlédl v seriálu od Olšáka, že tam má dost věcí nových. No, starostí kůpa. A zbytečných,
jak jsem nakonec zjistil. Jenže každej nějak začíná. (Stálo mně to jeden večer.)
Až když jsem začal pročítat nějaké odkazy k TexLive, jsem narazil na on-line instalátor. Ten stačilo spustit,
nastavil instalaci do /opt, kde bylo akorát potřebné místo, a bylo za okamžik hotovo. Tento bezproblémový
postup bych doporučoval každému.
Tex pro češtinu byl tedy připraven. Jenže jsem stále neměl nic, čeho se chytit. Nad uvedenými balíčky jsem lámal
hůl a nezbývalo než zkoušet internet. Tak jsem se dostal na Olšákovo OPmac stránky. Makro pro dva (a více)
sloupce tam měl. To tedy byla první vlašťovka, ač jsem o tom nic moc víc nevěděl. Záhy jsem zjistil, že tam jde
dát také obrázek do sloupce a moje radost vzrostla. Za krátko už byl první text i obrázek ve sloupci a výsledek
nevypadal špatně. To byl tedy začátek. – Dále jsem si vzpomněl na Olšákův článek na abclinux.cz, který jsem
tehdy ignoroval a jen ze zvědavosti přečetl nějaký ten odstavec. Současně s tím jsem také otevřel dokument
opmac u.pdf a to už jsem začal tušit, že to půjde a nebude to takový problém. Pak jsem se jal prozkoumat
Tex pro pragmatiky.pdf, a když něco nešlo prolomit, pak jsem hledal v opmac d.pdf a také v tbn.pdf. Jo, abych
nezapomněl, zajímavou inspirací byly Tipy, triky, návody na stránkách Opmac a párkrát také opmac.tex.
Vesměs ale šlo o hledání pomocí search a ne žádné systematické studium dokumentů.
Po týdnu jsem měl noviny hotové i se psaním své historické části, popoháněním přispěvatelů a korekcemi dle
připomínek kibiců. Já myslím, že je to úctyhodný výkon, pro začátek. Přitom pro mne byl Tex dlouhá léta
strašák určený pro několik nadšených matematiků v akademické sféře. Samozřejmě, že TeX(ový) soubor za moc
nestojí, ale vše jsem tam nějak dostal a teprve v dalším čísle (pokud na něj dojde) budu více makrovat, a vůbec
dělat to tak nějak systematičtěji.
Nikdy bych nevěřil, že je práce s TeXem taková zábava, a že s tím napoprvé za týdne sesadím dvousloupcový
booklet pěkného vzhledu. A to jen díky Olšákovo práci; jinak si tak rychlý a úspěšný začátek nedovedu představit. – Jsem zvyklý, že věci najdu na zahraničních stránkách, ale v tomto případě jsem nakonec měl jednoznačně
lepší výsledky z vyhledávače, když jsem psal ”tex . . . Olšák”, nebo hledal ve zmíněných pdfkách. Jinak mě dost
prudilo, že mi vyhledávač vždy hodil nějaká přihlouplá latexová řešení, ač jsem psal plaintex, až jsem začal
být na LaTex lehce alergický.
28
Jakmile jsem dodělal oběžník, pustil jsem se – ještě za tepla – do etiket na med. Hezky aby seděly obě strany
textu (takové pexeso). K tomu jsem se hodně inspiroval z Olšákova počinu slovicka.tex. Nestačil jsem se divit,
co všechno se nechá udělat.
Tak jsem tedy začal s programem TeX a mám z jeho používání radost.
LaTex se na závěr také hodil. Potřeboval jsem totiž stránky sesadit do brožury a nejlepší způsob, který jsem
našel, bylo latexové řešení.
pdfnup StN_maje.pdf 4,1,2,3 --a3paper --offset ’0cm 0cm’ --delta
’-0.7cm -0.7cm’ --outfile StN_majeB.pdf
První kroky sice vedly přes pstops, ale s rozpaky jsem tento program, který snad funguje dobře jen pro
A4 formát, opustil. Vlastně je mi tento program trochu záhadou, neboť nechápu, jak se může tak nefunkční
program dlouhou dobu držet v open source prostředí. Zde naopak latexový pdfnup mile – svými možnostmi – překvapil.
25.5.2014
Awk
awk ; terminal
Předně upozorňuji, že tento zápisek je opravdu dlouhý. Běžně vše co v něm je, bývá rozděleno do několika
kapitol. Já však v tomto jednom monolitu shledávám několik výhod: mám vše hezky pohromadě a dostal jsem
se tím hned na stejnou úroveň poslední napsané sed-árny. Zatímco program sed jsem se učil – a také jsem
o něm psal – podle aktuální potřeby něco vyřešit, v případě awk jsem se rovnou při psaní tohoto článku
snažil prokousat od píky až k pokročilým věcem. Navíc jsem neměl tušení, jak vše dopade. Dopadlo to tedy
trochu nad mé očekávání a výsledkem jsou hned dva prográmky – nastavky.awk a tabular –, o kterých jsem
psal posledně.
Awk a sed
Pokud člověk nemá ještě ten správný cit pro programy awk a sed, nejspíš bude tápat, který z nich vlastně pro
konkrétní věc použít. Vím to i z vlastní zkušenosti. Mnohdy se totiž nechá požadovaná operace provést s awk i
sed. Bez znalosti obou ale můžeme strávit hromadu času nad hledáním krkolomného řešení jedním z programů,
který je pro věc méně vhodný. Jsou však i případy, kdy se bez druhého – častěji awk – prostě neobejdeme.
Omezení editoru sed je dáno jen dvěma pracovními registry. sed je jen editor streamu, ale awk má svůj
programovací jazyk, který efektivně pracuje nad regulárními výrazy, přičemž používá podobné funkce a syntaxi
jazyka C; samozřejmě používá i proměnné, pole a matematické výrazy. Program awk zabírá přibližně 350
kilobajtů, zatímco sed asi 75 kilobajtů. Přesto však – i když awk je programovací jazyk – má awk s editorem
sed dost společného. Tak jako sed i awk běžně zpracovává data ve streamu řádek po řádku až do konce
souboru/(ů). Nemusí to však už být pravidlem: kontinuita streamu se nechá snáze přerušit ukládáním do
proměnných a dodatečným zpracováním. Dokonce awk vůbec vstupní stream nepotřebuje (nezahlásí chybu
jako sed). Program může proběhnout pouze s lokálními nebo externími proměnnými, nejčastěji pro různé
výpočty. Také můžeme u obou programů vidět jistou podobu v syntaxi, kdy výraz začíná nejčastěji podmínkou
(filtrem), jenž propustí ke zpracování jen řádky, které vyhovují zadanému výrazu. Za ní pak většinou následuje
ve složených závorkách operace, kterou vykoná program jazyka awk:
awk ’/filtr/{operace}’
Tento obecný zápisu je tedy stejný jako v případě programu sed. Také u obout platí, že filtr můžeme vynechat,
čímž se operace provedou pro každý řádek. Jediný rozdíl je v tom, že u sed nelze použít samotný filtr; vždy
29
musí následovat alespoň jedna instrukce např. p a v případě operace jen s jednou instrukcí se u sed složené
závorky většinou vynechávají. Namísto toho awk může obsahovat samotný filtr bez operace. Tak se vytisknou
řádky, které filtr propustí:
awk ’/karel/’
Vytiskne řádky pouze se slovem
příkazem print:
karel. Lze se však – pro lepší čitelnost – držet plné syntaxe a použít operaci s
awk ’/filtr/{print}’
Nestane se, že by se řádky tiskly v tomto případě dvakrát, neboť s použitím složených závorek se interní
deaktivuje. Snadno zjistíme, že následující výraz na výstup nic nepropustí:
print
awk ’/filtr/{}’
Závorky u awk tedy fungují jako přepínač -n v editoru sed (deaktivuje interní funkci print na konci programu).
Předchozímu příkladu s print odpovídá tak výraz v editoru sed:
sed -n ’/filtr/{p}’
, složené závorky u p být samozřejmě nemusí (pokud je použita jen jedna instrukce). Stejně jako sed i awk často
obsahuje několik výrazů, které lze řadit za sebe a oddělovat středníkem (nebo jen mezerou).
awk ’/filtr/{operace}; /filter2/; /filter3/{print}’
A pro srovnání řazení výrazů u
sed:
sed -e ’/filtr/{operace} ; / filtr3/{operace} ’ -e ’/filtr2/{operace2}’
, kde používáme také středník nebo přepínač -e, kdy pak lze začít nový výraz v uvozovkách:
A nyní, když jsme si ukázali, že není potřeba měnit dosud zažité zvyky z programu
programem awk.
sed, můžeme začít s
Podmínka místo filtru
Doposud jsem používal pojem vstupní ”filtr”, který mi přišel v případě zpracování streamu u krátkých výrazů
výstižnější, a také poukazoval na jistou podobnost se sed, kde je slovo ”filtr” na místě. Filtry propouští k
operacím vždy jen vyhovující řádky a takto se stream postupně dostává až na konec, kde je upravený předán na
výstup k vytištění. U awk je tomu stejně ovšem ne vždy, jak si ještě dále ukážeme. Mnohem lépe je tedy – pokud
se nejedná o krátké idiomatické výrazy s jedním výrazem – používat pojem podmínka. V podmínce totiž mohou
být i složitější výrazy, logické operátory, proměnné a čísla, a zde tedy bude pojem ”podmínka” vhodnější.
Podmínku lze chápat tak, že následující operace bude provedena pouze tehdy, pokud je podmínka splněna
(nejčastěji vyhovuje nějakému regulárnímu výrazu). Další případ, kdy je již pojem ”podmínka” zcela na místě,
je umístění uvnitř složených závorek v místě programu za if. Vně i uvnitř výrazu se totiž používá stejné syntaxe
pro vytvoření podmínky, jen s tím rozdílem, že v programu je podmínka až za if uzavřena v kulatých závorkách
(jak je tomu v C).
Již jsem napsal, že filtr/(nově)podmínka nemusí být vůbec použita. Jedná se vlastně o jistý ústupek od přesného
dodržení syntaxe, neboť správně bychom měli použít před závorkou hodnotu > 0, která odpovídá splněné
podmínce takto:
30
awk ’1{operace}’
Také jsem již zmínil, že ani operace nemusí být u awk mnohdy vůbec použita. V takovém případě dostaneme
nejkratší zápis, který s awk lze vytvořit, kde výraz bude pouze 1 (splněná podmínka). Takový zápis bude jen
obdobou programu cat; vše ze vstupu vytiskne. Pokud použijeme několik 1 za sebou, dojde k tomu, že se řádky
vytisknou několikrát:
awk ’1;1;1’
, v tomto případě třikrát. Častěji se však setkáme s 1 až za operací:
awk ’/jirka/{sub(jirka, Josef)} 1’
1 zde tvoří druhý výraz (před 1 by mohl být středník, který se však většinou nepoužívá). 1 způsobí, že se
upravený stream v prvním výrazu v tomto místě vytiskne. Ušetříme si tak psaní příkazu print uvnitř programu.
A to je někdy u jednořádkových zápisů (one-liners) žádoucí.
Tak jako sed ukládal vždy načtený řádek do pattern registru, awk si jej uloží do proměnné $0; před tím ale zbaví
řádek znaku EOL na konci; ten se na konci zas automaticky přidá těsně před vytištěním; přidaný zakončovací
znak lze u awk nastavit v proměnné ORS (Output record separator), výchozí hodnota je \n.
Nyní je čas, rozebrat trochu víc samotné podmínky. Jednoduchá podmínka může vypadat následovně:
awk ’/^[0-9]*/ || NR==5’
Zde máme dvě podmínky oddělené logickým operátorem OR. První bude vyhovovat pro řádky, které začínají
číslicí a druhá hodnotě 5 v proměnné NR (NR obsahuje číslo právě načteného řádku). Vytisknou se tak pouze
řádky začínající libovolným číslem a také pátý řádek (který číslem nemusí začínat). Regulární výraz v podmínce se testuje pro obsah v proměnné $0. Pokud bychom chtěli testovat regulární výraz pro jinou proměnnou
musíme ji uvést:
awk ’( ($3 ~ /^[0-9]*/) || NR==5 )’
V tomto případě je regulární výraz testován v proměnné $3, která obsahuje třetí sloupec z aktuálního načteného
řádku. (Proměnná musí být vždy vlevo a reg. výraz vpravo!) Pro větší přehlednost jsem zde použil závorky,
ale vše funguje správně i bez nich. Nyní podmínka, která propustí pouze řádky, které mají v řádku 10 až 20 v
prvním sloupci karel (nic jiného):
awk ’(NR>=10 && NR<=20) && ($1 == "karel")’
Samozřejmě lze používat také další logické operátory a negace (!~, !=, !). Zatímco regulární výrazy se vkládají
mezi lomítka, přesný výraz lze dát do uvozovek. To platí u zkráceného zápisu (jak bylo uvedeno) bez logického
operátoru. Pro nezkrácenou podmínku lze však použít uvozovky i pro regulární výraz.
awk ’$0 ~ "^záznam.*karel"’
, je zde tedy jistá volnost v používání syntaxe. (V manuálu se nicméně uvádí, že je lépe se uvozovkovému
způsobu vyhnout – bude vysvětleno.) Častá asi bude negace jednoho výrazu nad celým řádkem. Jednoduchý
způsob vypadá takto:
awk ’!/^[0-9]*/’
, vytiskne jen řádky, které nezačínají číslem; stejné jako grep -v ”[0-9]*”. Místo logických operátorů se nám
může hodit použít | přímo v regulárním výrazu:
31
awk ’/AAA|BBB|CCC/’
A konečně je zde také ”multi-line” podmínka:
awk ’/karel/,/jirka/’
, jenž propustí pouze řádky, od řádku s
u sed. Pokud totiž máme:
karel až po řádek s následujícím jirka. ”Filtr” zde funguje stejně jako
seq 1 20 | awk ’/3/,/10/’
, vytisknou se řádky, které obsahují číslici 3 až po následující řádek s číslicí 10; což je správně. Číslice 3 je
ale také v čísle 13 a odtud se bude tisknout další rozsah, až po další číslici 10, která se však již na vstupu v
žádném řádku neobjeví, a tak dojde k vytištění druhého rozsahu 13 až 20 (poslední řádek). Takové specifické
chování ”multi-line” operace jsem u sed již popisoval ve IV. díle Sed-árny. Zde je to tedy naprosto stejné. A
pokud bychom se chtěli nežádoucího konce v případě chybějícího druhého výrazu zbavit, musíme vše ošetřit
například takto:
seq 1 20 | awk ’/3/,/10/{x=x $0"\n"} ;/10/{print x; x=""}’
Zde se vyhovující řádky ukládají do proměnné a vytisknou se až tehdy, pokud je dosaženo druhého výrazu 10.
Pokud bychom chtěli vytisknout vše od řádku, kterému odpovídá výraz až do konce souboru, lze to udělat následovně:
awk ’/karel/,0’
0 v tomto případě znamená False, a nikdy tedy druhý výraz není splněný a řádky se tak tisknout až po dosažení
konce souboru; také lze použít EOF místo 0. U programu sed by místo 0 musel být znak konce $!
Nyní si ukážeme, jak nám budou stejné podmínky užitečné také v místě programu. Pokud bychom chtěli
vytisknout rozsah řádek od jirka až po karel kromě řádků s jirka a karel, pak si pomůžeme jednoduchým
krátkým programem s podmínkou a příkazem print:
awk ’/karel/,/jirka/{if (!/karel/ && !/jirka/) print}’
Funkce print se provede pouze tehdy, pokud vyhovuje podmínka; pokud by pod podmínkou bylo více operací,
uzavírají se do složených závorek a oddělují se středníkem (v awk skriptu pak novým řádkem); stejné jako v C.
Stejného výsledku můžeme dosáhnout také jiným způsobem:
awk ’/jirka/{p=0}; p; /karel/{p=1}’
Jak výraz funguje?. Již jsem vysvětlili význam 1 (jedná se vlastně o splněnou podmínku). V tomto případě
jsme jen použili proměnnou p (může se jmenovat libovolně). Zápis s awk obsahuje v tomto případě tři výrazy.
První a poslední nastavuje proměnnou p, pokud řádek obsahuje karel nebo jirka. Prostřední výraz obsahuje
pouze proměnnou, která je 0 nebo 1. Na základě toho co již bylo zmíněno o 1, se tedy bude chovat výraz s
proměnnou buď tak, že se v tomto místě tiskne obsah z $0 (pokud je proměnná 1), nebo se výstup potlačí
(pokud je proměnná 0). Výchozí hodnota proměnné odpovídá 0 a nice se tedy od začátku netiskne až do řádku
s karel; teprve pak podmínka s operací nastavení p na 1 způsobí, že se od dalšího řádku bude tisknout obsah z
$0. Jakmile však řádek obsahuje jirka, proměnná se vynuluje a nic se od tohoto řádku (včetně) netiskne až do
doby, než se znovu načte řádek s karel. – Jak jsme si právě ukázali, podmínkou může být také proměnná, která
se v programu nastavuje. Navíc proměnná nemusí obsahovat jen hodnotu True nebo False, ale také regulární
výraz, který se může nastavovat dle libosti v programu. Tím jsou možnosti téměř neomezené.
32
Na závěr podkapitoly o podmínkách uvedu pár dalších zajímavých podmínek a krátkých idiomatických výrazů,
které jsem našel různě na internetu:
awk ’NR % 6’ . . . . každý šestý řádek vynechá
awk ’NF >= 6’ . . . . vytiskne řádky, které mají více jak šest sloupců
awk ’NF’ . . . . prázdné řádky netiskne (NF obsahuje počet sloupců)
awk ’NF–’ . . . . odstraní poslední sloupec
awk ’$NF == 2014’ . . . . vytiskne pouze řádky které mají v posledním sloupci 2014
awk ’$0 = NR” ”$0’ . . . . očísluje řádky
awk ’NR==FNR’ . . . . pokud je zadáno více souborů, je tato podmínka platná pouze pro první soubor
(bude dále vysvětleno)
awk ’ !a[$0]++’ . . . . odstraní duplicitní řádky (dále bude vysvětleno)
awk ’ORS=NR%5?”,”:”\n”’ . . . . spojí vždy pět řádků (dále bude vysvětleno)
Výstup a jednoduchá substituce
Jakmile použijeme ve výrazu složené závorky, musíme se o vytištění postarat sami – jak již bylo napsáno na
začátku. Většinou máme na výběr z již výše uvedených možností: osamocená splněná podmínka 1, pomocí
nastavované proměnné na 1 a 0 nebo pomocí print v místě programu. Kromě toho má awk také příkaz printf,
o který také někde dále zavadím. Nyní začneme tím, že vytiskneme prvních 10 řádek pomocí příkazu print:
awk ’NR<=10{print}’
Pokud bychom chtěli tisknout v místě mimo oblast programu, musíme použít 1 místo print. Následující příklad
vytiskne prvních deset řádek stejně jako předchozí:
awk ’1; NR == 10 { exit }’
Jen jsme se zde vyhnuli použití příkazu print a místo toho jsme použili 1 a příkaz exit. 1 způsobí, že se od
začátku tiskne každý řádek až do řádku 10, kdy se provede příkaz exit a program se ukončí. Pro srovnání uvedu,
jak by vypadal zápis pomocí sed:
sed -n ’p;10q’
Nyní se podíváme na to, jak provést běžnou substituci, která v případě sed vypadá následovně: s/vzor/nahrad/g.
Pokud bychom chtěli provést tuto jednoduchou operaci pomocí awk, pak použijeme funkci sub:
awk ’{ sub(/[ \t]+$/, ""); print }’
Tímto se vytiskne původní vstupní text, u kterého se pouze odstraní mezery (space a tab) na koncích řádků.
(Zde již používáme jednu z funkcí, kterou awk nabízí a mohli bychom tedy zde již mluvit o tvoření programu
v jazyce awk; narozdíl od předchozích krátkých výrazů.). Nyní si ukážeme možnou variabilitu ve způsobu
vytištění. Použitím 1 si můžeme předchozí výraz o něco zkrátit takto:
awk ’{ sub(/[ \t]+$/, "")} 1’
, za složenou závorkou středník být nemusí. Vše dopadne stejně, pokud bude vypadat výraz následovně:
awk ’{ sub(/[ \t]+$/, "")}; {print $0}’
za print je uvedena proměnná $0, která se však v případě $0 zadávat nemusí. Pouze pokud bychom chtěli
tisknout jinou proměnnou – například konkrétní sloupec –, musíme proměnnou za print zadat.
33
Výstup lze také přesměrovat do souboru pomocí >>. Samozřejmě lze také název výstupního souboru předávat parametrem:
awk ’/^file/ { print $3 >> $2 }’
Uvedený výraz bude z každého načteného řádku začínajícího file brát položku ze třetího sloupce a zapíše ji do
souboru, který pojmenuje podle názvu ve druhém sloupci (za file).
Idiomatické výrazy
Existuje mnoho krátkých (idiomatických) výrazů, které jsou často velmi mazané a vyplatí se je mít někde sepsané
nebo o nich alespoň mít tušení. Následující zápis přidá za každý stávající řádek jeden prázdný:
awk ’1; {print ""}’
Vzhledem k tomu však, že awk umožňuje nastavit ukončovací znak (separátor), který se přidá, existuje vhodnější
řešení:
awk ’BEGIN { ORS="\n\n" }; 1’
BEGIN, který se zde používá v místě podmínky, zajistí, že operace ve složených závorkách se provede hned na
začátku, ještě před tím než dojde k načtení prvního řádku. Obdobou může být END, který naopak zajistí, že
se následující operace provede až na konci po dosažení EOF. (BEGIN i END může být několik a vykonávají
se postupně jak jdou za sebou.) V našem výrazu se nastaví systémová proměnná ORS programu awk a nový
výstupní separátor se bude vkládat za každý řádek, který se má tisknout. Jiný příklad přidá prázdné řádky
pouze za řádky, které nejsou prázdné:
awk ’NF { print $0 "\n" }’
Trik spočívá v tom, že testujeme počet sloupců v proměnné NF. Pokud NF je 0, jedná se o prázdný řádek
a následující operace se tak neprovedou. Další zápis vytiskne pouze následující řádek za tím, který vyhovuje
regulárnímu výrazu:
awk ’/regex/ { getline; print }’
Načtení dalšího má na svědomí funkce getline (obdoba N u sed). Funkce aktualizuje hodnoty v $0, NR, NF,
FNR. Pokud bychom chtěli vytisknout naopak řádek před, lze to provést následovně:
awk ’/regex/ { print x }; { x=$0 }’
Každý řádek se zde ukládá do proměnné x. V okamžiku, kdy odpovídá výraz právě načtenému řádku, provede
se operace s příkazem print x, která vytiskne předchozí uložený řádek. Tento výraz ovšem nebude fungovat správně, pokud by regulární výraz odpovídal hned prvnímu řádku. Aby vše bylo korektní, přidáme ještě
podmínku, která testuje, kdy je x prázdné:
awk ’/regex/ { print (x=="" ? "hledany vyraz na radku 1" : x) }; { x=$0 }’
v takovém případě se vytiskne, že výraz je na prvním řádku. (Awk podporuje podmínkový operátor ve tvaru
statement ? true : false, který lze snadno použít v parametru příkazu print). A nyní jeden velmi mazaný
výraz, který nahradí prázdná místa jen jednou mezerou:
awk ’{ $1=$1; print }’
34
Trik spočívá v tom, že awk znovu sestaví hodnotu v $0 a použije separátor v proměnné OFS (výchozí hodnotou
je jeden prázdný znak), jakmile je zapsáno do některé buňky – v tomto případě se do $1 uloží stejná hodnot
$1. Po tom, co jsme si ukázali, že se nechá do buněk libovolně zapisovat, asi nikoho nepřekvapí, že vymazání
konkrétního sloupce lze provést takto:
awk -F ":" ’{ $2 = ""; print }’
Navíc je zde nastaven přepínačem -F jiný separátor pro sloupce než výchozí používaná mezera. Hodnotu separátoru lze také nastavit v programu přímo zapsáním do proměnné FS. Dále si ukážeme, jak lze odstranit stejné
řádky, které následují za sebou (obdoba programu uniq).
awk ’a != $0 ; { a = $0 }’
Ve druhém výrazu se do proměnné a vždy uloží nový řádek z $0. V prvním výrazu se pak porovná nový a
předchozí; pouze pokud jsou různé, je podmínka splněna (odpovídá 1) a tiskne se. A nyní jeden extra zajímavý
výraz, který je zároveň famózně mazaným řešením, jak spojit vždy pět řádků do jednoho s oddělovačem | :
seq 1 30 | awk ’ORS=NR%5?" | ":"\n"’
Význam ORS už zde byl uveden a podmínkový operátor také. ORS se tedy zde nastavuje podle podmínky tak,
že pro každý pátý řádek se použije \n, jinak se nastaví svislá čára. Tím se na konec každého řádku vždy přidá
patřičný separátor. – Zatímco proměnná ORS obsahuje výstupní separátor, je zde ještě systémová proměnná
RS, která obsahuje vstupní separátor – separátor, podle kterého se načítají jednotlivé řádky (pokud je nastaven
\n). I s touto proměnnou se nechá dále kouzlit.
awk -v RS=’#’ ’1’
Proměnná je zde nastavena mimo oblast programu jako parametr za -v; abychom nemuseli z důvodu nastavení
proměnné v místě programu použít složené závorky. Výchozí hodnota obsahuje separátor \n, nyní jsme tuto
hodnotu nahradili znakem #. Každý cyklus tak bude číst text až po další znak #. Znak si na vstupu awk
vždy odstraní a na výstupu přidá znak v ORS (výchozí hodnota je \n). Výstupem budou tak řádky, které jsou
vytvořené z bloků uvnitř # (plust také první a poslední blok). Pokud bychom chtěli, aby nedošlo na výstupu
k odřádkování i v místě původního místa na konci řádku, odstraníme v programu všechny znaky \n z textu
pomocí funkce sub. K čemu taková rošáda může být dobrá, ukáže až další příklad:
echo -ne "kamarádi:\nkarel{rybář}\njirka{myslivec}\njosef{velký\n
náčelník}" | awk -v RS=’{|}’ ’!(NR%2)’
Pomocí echo si vytvoříme tři řádky se jmény a povoláním ve složených závorkách. Příjemným překvapením je,
že RS (ale i některé další systémové proměnné) podporují hodnotu s regulárním výrazem. Tentokrát máme v
RS vstupní separátory dva { a }. Výraz !(NR%2) je pouze podmínka, která je splněna jen pro sudé řádky.
Dostaneme tak z textu všechna povolání ve složených závorkách; každý na novém řádku. Tedy až na jednu
výjimku, kterou je poslední text velký náčelník. Záměrně jsem totiž za slovo velký vložil \n. Jak je vidět nyní
\n nám náš záměr trochu kazí. Jednoduše je tedy odstraníme pomocí substituce (funkce sub):
echo -ne "kamarádi:\nkarel{rybář}\njirka{myslivec}\njosef{velký\n
náčelník}" | awk -v RS=’{|}’ ’!(NR%2){sub("\n"," "); print}’
Uvedený příklad není moc rozumný. Chtěl jsem jen, aby to byl jakýsi mezikrok k dalšímu. awk ve verzi GNU
totiž podporuje rozšířené regulární výrazy a má také proměnnou RT, která obsahuje právě načtený ukončovací
separátor, který odpovídá výrazu v RS. Můžeme tak snadno definovat párový tag.
gawk -v RS=’</?tag>’ ’RT=="</tag>"’
35
a dostaneme všechny výrazy uvnitř tagů. Pokud by obsah tagu byl přes několik řádek, odstraníme také znak
\n – jako v předchozím případě –, abychom dostali obsah do jednoho řádku. Zde má awk oproti sed jednoznačně navrch. – Další příklad s regulárním výrazem v RS a testováním RT může být nalezení vzoru, který
výrazu odpovídá.
gawk -v RS=’[0-9]+’ ’RT{print RT}’
Proměnná RT obsahuje vždy právě načtený text, který odpovídá regulárnímu výrazu v RS. Je to tedy obdoba
grep -o. Pokud chceme ještě dále s hodnotou pracovat, může být způsob s gawk výhodnější než použití grep.
V případě, že nemáme GNU verzi awk, musel by se tento uvedený příklad řešit krátkou funkcí.
Funkce pro práci s řetězci
Po té co jsme si ukázali několik idiomatických výrazů, je čas na krátkou ukázku výrazů, který bude více
programem. Funkce, které často potřebuji pro práci s textem jsou: index, substr, sub, gsub, length, match.
Tak třeba takový příklad:
awk -v j=$TERM IGNORECASE=1 ’{if ( match($0, "^[0-9]*") )
"1", "#>" j "<#", $0) } print }’
{ sub(
v podmínce uvnitř programu je pro nalezení řádku s odpovídajícím regulárním výrazem použita funkce match,
která vrátí 0, pokud by se výraz v řetězci nenacházel nebo číslo pozice hledaného výrazu. Šel by také použít
více idiomatický výraz v podmínce /^[0-9]*/; já si zvykl používat funkci match – přijde mi to přehlednější.
Dále je zde použita externí proměnná bashe $TERM, která se uloží na začátku do proměnné j; tímto způsobem
lze předávat externí proměnné do programu awk! Také je zde nastavena systémová proměnná programu awk
IGNORECASE, která způsobí, že chování všech výrazů a funkcí nebude case-sensitive. Tento krátký program
nahradí v řádku, který začíná číslem, jedničku znakem #>xterm<#. Tento hloupý příklad lze snadno vyzkoušet
vytvořením řady pomocí seq, jak už zde bylo několikrát použito.
Awk nabízí tyto funkce pro práci s řetězci:
•
•
•
•
•
•
•
•
•
sub(r,s,t) . . . . nahradí první místo odpovídající regulárnímu výrazu (r) řetězcem (s) v proměnné (t); pokud
je (t) vynecháno použije se $0; modifikace se provádí přímo na vstupním řetězci ; nahrazovací funkce sub
také podporuje skupinový operátor & stejně jako u sed
gsub(r,s,t) . . . . stejné jako sub ale funguje globálně; vrací počet provedených změn; i zde se modifikace
provátí přímo na vstupním řetězci
gensub(r,s,h,t) . . . . stejné jako gsub jen s tím rozdílem, že v (h) lze zadat kolikátý výraz v pořadí se má
nahradit; pokud parametr (h) obsahuje g, pak funguje stejně jako gsub; oproti sub a gsub však vrací
upravený výraz a zadanou proměnnou nepřepíše
length(s) . . . . vrátí délku řetězce
match(s,r) . . . . vrátí pozici v řetězci (s), pro kterou vyhovuje regulární výraz; nebo vrátí 0 pokud výraz v
řetězci není
index(s,t) . . . . vrací pozici řetězce (t), v řetězci (s) nebo vrátí 0 pokud řetězec nebyl nalezen; na rozdíl od
match nepracuje s reg. výrazy
split(s,a,fs) . . . . rozdělí řetězec (s) podle zadaného separátoru (fs) a uloží části do pole (a); pokud fs není
zadáno použije se separátor z FS
substr(s,i,n) . . . . vrátí (n) znaků od pozice (i) v řetězci (s); pokud není (n) zadáno je bráno jako poslední pozice
sprintf . . . . funguje jako printf – jen hodnotu netiskne, ale ukládá do proměnné
Tyto a další funkce lze najít na manuálové stránce programu gawk, kde jsou rozdělené do sekcí; případně v
manuálu zde nebo stručněji v man awk.
36
Počítání
Začněme klasickým číslováním řádků (obdoba cat
-n):
awk ’{ printf("%5d : %s\n", NR, $0) }’
Je zde použitý příkaz printf, která funguje obvyklým způsobem. Proměnná NR obsahuje vždy číslo aktuálního
řádku. Při použití printf nepřidá awk znak(y) v ORS na konec řádku. Musíme tedy \n přidat sami!
Další zápis počítá pouze řádky, které nejsou prázdné:
awk ’NF { $0=++a " :" $0 };1’
Operace je zde trochu nepřehledná. Musíme si dát část výrazu do závorek, aby vše bylo jasné: $0=(++a ” :”
$0). Do proměnné $0 se tedy zapíše nová hodnota, která se skládá z hodnoty v a, dvojtečky a původní hodnoty
v $0 (načtený řádek). 1 vytiskne upravený obsah v $0; ať již modifikovaný u neprázdných řádků (podmínka NF)
nebo nemodifikovaný u prázdných řádků. Další zápis jen vytiskne počet řádků souboru:
awk ’END { print NR }’
Lépe bude použít
řádku:
wc -l. Nyní si ukážeme použití cyklu, a sečteme všechny hodnoty ve sloupcích každého
awk ’{ s = 0; for (i = 1; i <= NF; i++) s = s+$i; print s }’
a s malou obměnou sečteme všechny buňky v tabulce:
awk ’{ for (i = 1; i <= NF; i++) s = s+$i }; END { print s+0 }’
print v tomto případě necháme až na konec (podmínka END je platná tehdy, kdy je dosaženo EOF), tehdy
budou v proměnné s všechny sečtené buňky. Pro případ, že by buňky neobsahovaly čísla a výsledná proměnná
s byla prázdná, přidáme +0: jedině tak se vytiskne 0, pokud je s prázdné. Jinak awk nepozná jedná-li se o
číselnou hodnotu nebo string. – Jiný příklad udělá absolutní hodnotu čísla v každé buňce. Pro přehlednost
použiji tentokrát zápis pod sebou, který se jinak používá v awk skriptu:
awk ’{
for (i = 1; i <= NF; i++) {
if ($i < 0) {
$i = -$i;
}
}
print
}’
Proměnná, která začíná $ ukazuje vždy do buňky v aktuálním načteném řádku podle čísla, které následuje
($0, $1, $2, $3), jak již bylo popsáno. Kromě přímé pozice buňky (dané číslem), můžeme použít proměnnou,
která obsahuje patřičné číslo pozice (něco jako nepřímá adresace). Již zde bylo použito například $NF, kde NF
obsahuje číslo poslední buňky, tudíž s $ na začátku dostaneme hodnotu poslední buňky. Nyní máme proměnnou
i; kterou v cyklu inkrementujeme. S $i můžeme číst nebo zapisovat vždy do buňky na pozici odpovídající číslu
i. – V systémové proměnné length se nachází délka aktuálního řádku. Můžeme proto snadno nechat vytisknout
pouze řádky, které jsou delší než 50 znaků:
awk ’length > 50’
37
V předchozím dílu o awk (kdysi dávno) jsem uvedl příklad, který vyrovná text do sloupců na daných pozicích v
řádku. Nyní si ukážeme o něco složitější příklad, který zarovná text na střed.
awk ’{ l=length(); s=int((79-l)/2); printf "%"(s+l)"s\n", $0 }’
Funkce length($0) vrací délku řádku (bylo by možné použít také proměnnou length); pak trochu počítání
a příkaz printf vše vytiskne. A nyní ukázka toho, jak do proměnné s uložit 513 znaků x, které se následně vytisknou.
awk ’BEGIN { while (a++<513) s=s "x"; print s }’
Jeden z mých úplně prvních programů obsahoval hexadecimální čísla. Musel jsem tehdy dost pátrat, abych se přes
toto úskalí přehoupl. awk totiž s hexa číslicemi ve tvaru 0xFFFF (nebo jiném podobném) bez přepínače –nondecimal-data nefunguje. Uvedený krátký zápis najde nejvyšší číslo , které přichází na vstup v hexa tvaru.
awk --non-decimal-data -v max=0 ’{if ($1>max) max=$1} ; END{print max}’
Přepínač zřejmě pouze aktivuje další stupeň ověření vstupních hodnot (řetězce), na základě kterého se rozpozná,
jedná-li se o číslo. Vzpomínám si, že jsem tehdy narazil na vysvětlení k přepínači –non-decimal-data, kde
bylo uvedeno, že se tím awk o něco zpomalí, a proto není toto chování jako výchozí.
Zatím jsme vždy v této podkapitole počítali s daty ze vstupního souboru. Mnohokrát však použijeme
pro výpočet nějaké hodnoty:
awk jen
awk -v x=143.4 ’BEGIN{print int(x/16)}’
V takovém případě musíme použít BEGIN, protože jinak by awk čekal na vstupní stream. END také použít
nelze, protože v tomto případě by awk čekal na EOF na konci vstupního souboru. Nyní si ukážeme jedno
úskalí podmínek:
awk -v x=0 ’BEGIN{if(x>0){ print " s " x " splněno" } else
" s " x " nesplněno" }’
print
V tomto případě bychom chtěli ověřit, zdali je číslo z proměnné v x kladné. Samozřejmě se může stát, že někdo
zadá číslo chybně například ”abc”. Lze se přesvědčit, že i v tomto případě bude podmínka splněna; nebude ale
splněna pokud bude x prázdné (nezadáme žádnou hodnotu). Je tedy často potřeba tyto stavy – kdy hodnota
není číslo – ošetřit. První způsob už zde byl uveden: v programu bychom přičetli hodnotu 0 takto x=x+0.
Druhou možností je použít funkci int(x), která vrací 0, pokud se nejedná o číslo. Ještě jedna zajímavá vlastnost
je zde zřejmá: Pokud se používá za if ještě else, musí být výraz před else ve složených závorkách i tehdy,
pokud je tam jen jedna funkce nebo příkaz (jako v našem případě); za else však být jeden výraz v závorkách
už být nemusí!
Na závěr této podkapitoly uvádím matematické funkce, které máme v awk k disposici:
•
•
•
•
•
•
•
•
•
atan2(y,x) . . . . arc-tagens y/x
cos(x) . . . . cosinus
exp(n) . . . . exp (n musí být integer)
int(x) . . . . odsekne desetinou část
log(x) . . . . přirozený logaritmus
rand() . . . . pseudo-náhodný generátor čísel (vrací čísla 0 až 1)
sin(x) . . . . sinus
sqrt(x) . . . . odmocnina
srand(x) . . . . nastavení generátoru náhodných čísel
38
Jednorozměrná a vícerozměrná pole:
Awk umí používat proměnné typu asociativní pole, které lze však také používat s klasickým indexováním.
Začneme tím, že si funkcí split naplníme pole řetězcem, který rozdělíme:
split("Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec", month, " ")
Tím dostaneme pole month, kde index odpovídá číslům od jedné a hodnoty pole tvoří zkratky měsíců:
month[1]=Jan, month[2]=Mar. . . Index pole nemusí být jen číslo (viz asociativní pole), ale také název
nebo celý řádek; jedná se totiž o stejný prostor pro uložení proměnné jako hodnota. A takto bychom mohli
vytvořit pole mdigit:
for (i=1; i<=12; i++) mdigit[month[i]] = i
, kde bude prohozen index a hodnota: mdigit[”Jan”]=1, mdigit[”Mar”]=2. Právě používání tohoto obráceného způsobu se v awk s výhodou využívá pro mnohé operace. Již jsem zde uvedl způsob jak odstranit stejné
sousedící řádky. Nyní si ukážeme, jak pomocí pole snadno odstranit duplicitní řádky v celém souboru:
awk ’!($0 in a) { a[$0]; print }’
Podmínka kontroluje, není-li již nový řádek uložen v poli a. Pokud je podmínka splněna, uloží se řádek do pole
a také se vytiskne. Právě snadné porovnání hodnoty na místě indexu v celém poli pomocí operátoru in dělá
z tohoto způsobu mocný nástroj pro práci s polem (databází). (Podobný výraz s in se nechá použít také k
rychlému vytvoření cyklu pro procházení pole {databáze}, jak si ještě dále ukážeme.) Obdobou předchozího
výrazu je pak tento idiomatický:
awk ’!a[$0]++’
Do pole a se ukládá každý řádek na místo klíče pole takto – a[”obsah řádku”]. Každý nový řádek má nejprve
hodnotu prázdnou; v podmínce si ji awk přebere jako hodnotu 0 nebo false. Při dalším stejném řádku (odpovídá
stejnému indexu pole) se hodnota zvýší. Pokud bychom měli čtyři řádky:
karel
jirka
karel
josef
pak první buňka v poli bude a[”karel”]=0, které s negací odpovídá podmínce, která je splněna. Právě načtený
řádek v $0 se tedy vytiskne. Současně je díky ++ hodnota zvýšena na a[”karel”]=1. Pro další řádek bude
nová položka v poli a[”jirka”] a vše proběhne stejně, jako v předchozím případě. Na třetím řádku však máme
stejný obsah jako na prvním; buňka s indexem a[”karel”] je už vytvořena a má hodnotu 1. Po negaci je tedy
false a $0 se tentokrát nevytiskne. Zároveň je hodnota inkrementována, čímž bude a[”karel”]=2 (důležité je,
že stále zůstane true). Pokud tak bude v souboru další stejný řádek, který obsahuje pouze text karel, bude
už vždy blokován, neboť podmínka (po negaci) vyjde vždy false. A to je celý trik. Je už asi zcela zřejmé, že
pole se v awk mnohdy s výhodou používá opačně a hodnota se ukládá jako index, kde pak také lze snadno
otestovat přítomnost uvedeným způsobem, nebo způsobem, který byl použit v předchozím příkladu – ($0 in
a). Zatím vše vypadá asi poměrně jednoduše – a také je – až na jednu vlastnost, kterou nám odhalí následující
jednoduchý příklad. Budeme předpokládat, že již máme naplněné pole funkcí split zkratkami měsíců, jak bylo
uvedeno na začátku. Kromě již uvedeného indexového způsobu procházení pole (kde index musí být číslo, které
se inkrementuje) se nechá použít in takto:
awk ’{ for (i in month) {print month[i]} }’
Tím se nám vytisknou všechny hodnoty polí. A pokud bychom použili print i, tak hodnoty indexů, které však
nemusí být jen číslem. Možná už jste vyzkoušeli a zjistili, že je vše, tak jak má být, až na pořadí. Toho totiž
39
tento způsob nedbá, ani v případě za sebou jdoucích čísel v místě indexu. To jak awk pořadí jednotlivých
položek v poli vybírá je mi zatím záhadou. Tento způsob se nám tedy bude mnohdy hodit, pokud nás pořadí
příliš netrápí – například při substitucích v položkách. Jakmile však budeme chtít položky vytisknout v pořadí,
ve kterém jsme je zadali, nebo se načetli, pak budeme muset použít indexový způsob. (Proto se také mnohdy
hodí popsaný způsob prohození hodnoty a indexu.) Ačkoliv split tvoří položky s indexem od 1, lze číslovat také
0 nebo i jiných hodnot bez kontinuity.
Tento zajímavý příklad s polem prohodí pořadí řádek souboru (lépe bude používat tac):
awk ’{ a[i++] = $0 } END { for (j=i-1; j>=0;) print a[j--] }’
Awk umí pracovat i s více soubory. Poměrně snadno lze pomocí pole porovnat obsah dvou souborů a vytisknout
pouze shodné řádky takto:
awk ’NR==FNR{a[$0];next} $0 in a’ file1 file2
Podmínka NR==FNR bude splněna pouze pro první soubor. Pokud se tedy zpracovává první soubor, ukládají
se již zmíněných způsobem řádky do pole a; next funguje jako break v jiných jazycích – nepokračuje se v dalších
operacích, ale začne se nový cyklus s dalším řádkem. Awk na výstup tiskne až při zpracování druhého souboru,
kdy se již první část neprovádí. Provádí se jen test $0 in a, který propustí pouze řádky (druhého souboru),
které jsou uložené také v poli a (obsah prvního souboru). Podobný užitečný zápis doplní do třetího sloupce v
druhého souboru hodnoty ze třetího sloupce prvního souboru:
awk ’NR==FNR{a[$1]=$2;next} {$3=a[$3]}1’ file1 file2
První a druhý soubor mohou být jeden a ten samej. To se nám bude hodit, pokud potřebujeme pro další operace
nejprve projít celý tento soubor a najít třeba nějakou výchozí hodnotu pro další operace s ní:
awk ’NR==FNR{if($3>max) max=$3;next} {$4=$4-max}1’ file1 file1
, zde se nejprve najde maximální hodnota ve třetím sloupci a následně se tato hodnota odečte od všech čísel ve
čtvrtém sloupci a vytiskne se takto upravený původní soubor.
Začínal jsem naplněním pole month, mohl bych tedy zakončit jeho vymazáním. To se nechá rychle provést
pomocí delete(month), ale lze mazat pomocí delete také jen jednotlivé položky v poli například delete
month[1], což se také může hodit.
Jak jsme si právě ukázali, je pole mnohem více uzpůsobené k efektivní práci s textem a ukládání celých řádků.
Klasický způsob práce s polem přitom zůstává zachován.
Občas se nám hodí vícerozměrné pole, což u awk platí dvojnásob, protože něco jako strukturu awk nemá. –
Awk v podstatě klasická vícerozměrná pole nemá, ale i zde je řešení, které vytváří jakési pseudo vícerozměrné
pole pomocí klíčů. Jelikož pole může místo indexu obsahovat libovolný řetězec, stačí indexovat vícerozměrné
pole běžným způsobem – array[1,3], přičemž interně je ”1,3” zpracováváno jako řetězec (klíč) a ne tedy index 1
a 3. Navíc je interně čárka nahrazena oddělujícím znakem v proměnné SUBSEP, kterým je \034; lze ale nastavit
libovolný znak (nejlépe takový, který se nemůže v řetězci vyskytovat). To že se jedná o pouhý řetězec, a ne
dva indexy, dokazují už možnosti zápisů: totiž kromě uváděného array[1,3] je možné používat klidně array[1
SUBSEP 3]. Takto je to snad jasné. Jedná se tedy jen o jednorozměrné pole s klíčem místo indexu. Místo
číslic samozřejmě může být použitý text například array[jirka,novak,Praha]. – Všechny uvedené způsoby
zacházení popsané u jednorozměrného pole zde lze použít, občas si jen budeme muset pomoci funkcí split nebo
jinou funkcí k rozdělení řetězců; s jistou oklikou se musí řešit také například situace, kdy potřebujeme znát
délku: nelze použít jen funkce length(), ale hodnota se musí vydělit ještě počtem sloupců v poli; a jsou zde i jiné
nuance, které lehce práci s pseudo-vícerozměrným polem zesložiťují. Není to však až zas tak velká překážka. U
vícerozměrného pole budeme vzhledem ke svým uvedeným vlastnostem nejspíš častěji používat klíče k ukládání
40
dat a případně hodnoty pak pro čísla indexů – tedy obrácý princip. Rozhodně zde není zásadní omezení a nechají
se dělat i složitější seznamy s velkým množstvím sloupců.
Spouštění externího programu
Zde je jednoduchý příklad, který používá externí program sort k seřazení výstupních hodnot těsně před vytištěním:
awk ’BEGIN { FS=":" } ; { print $1 | "sort -n" }’
Samozřejmě bychom mohli použít rouru a sort v zápisu v příkazovém řádku až za awk. Použití uvnitř však
může být výhodné při psaní skriptu, který awk zpracuje; zpracování dalšími programy je pak přímo zahrnuto ve
skriptu. Z příkladu se zdá být asi vše jasné, ale není tomu tak. Zkuste například použít print dvakrát takto:
awk ’BEGIN{ for(i=10; i>0; i--) x=x i "\n"; print x | "sort -n";
print "konec" }’
Vše vypadá, že se vytisknou seřazená čísla a následně ”konec”, ale bude to přesně naopak. Výstup s rourou a
externím programem se totiž vždycky provede až jako poslední. Samozřejmě by se mělo vždy používat proměnné
a pole, a až na konci jen jeden print, který vše vytiskne s externím programem, který nám výstup pak ještě
zpracuje. – Ani druhý způsob, který awk nabízí není ideální: je jím funkce system(). Ta umožňuje spustit externí
program a výstup vytisknout na nový řádek, návratová hodnota je pak exit status programu (název volaného
programu s parametry musí být ve dvojitých uvozovkách {nebo v proměnné}– system(”date ’%d%b%Y’”)).
Tato funkce se tedy hodí především pro různé akce typu system(”rm ’/tmp/tabular.tmp’”); přičemž můžeme ověřit status, jak akce dopadla. Mnohdy bychom však potřebovali, aby se nechal výstup z externího
programu uložit do proměnné (například data z konfiguračního souboru). To je možné provést pro jeden řádek
menším trikem pomocí roury na začátku nebo na konci streamu:
awk ’BEGIN{ "date" | getline c ; print c}’
Načtení více řádek by bylo teoreticky možné provést dalším trikem, kdy bychom nahradili ”\n” jiným separátorem (například programem tr), a tím tak dostali vše do jednoho řádku. Nahradit značky zpět na \n by se
již nechalo v awk snadno. Zde jsem však narazil na problém ve výrazu uvnitř dvojitých uvozovek. cal | tr
’\n’ ’:’ totiž bude nahrazovat n místo \n. Banální věc jako uvozovky jsou totiž u awk někdy trochu problém.
U awk nelze použít nikde jednoduché uvozovky, kromě uzavření celého výrazu; na druhou stranu je to trochu
zjednodušení uvozovkových lapálií. Pro vše se tedy používají ve výrazech dvojité uvozovky a lomítka (jak již
bylo na začátku uvedeno). Vzhledem k tomu, že je již celý výraz externího programu ve dvojitých uvozovkách
a jednoduché uvnitř použít nelze, není zřejmě způsob, jak u externího programu použít uvozovky; řešení se mi
zatím nepovedlo najít. Posléze jsem si ale uvědomil, že máme pro uvedený příklad řešení v podobě separátoru
RS. Stačí nastavit separátor na znak, který se v textu nebude vyskytovat a awk načte vše jako jeden řádek. Na
konci bychom opět RS nastavili na původní hodnotu:
awk ’BEGIN{ rs_old=RS; RS=" "; "cal" | getline c ; print c; RS=rs_old}’
Tímto způsobem by také bylo možné snadno načíst soubor do proměnné mimo hlavní stream; a to bez použití
cyklu while a způsobu čtení řádek po řádku. Nakonec jsem narazil na jednoduché řešení, které se může také
někdy hodit. Jednoduše se program spustí pomocí $(program) nebo ‘program‘ za -v v bashi a vloží se do
awk proměnné:
awk -v calendar="$(cal)" BEGIN{print calendar}
Pokud nám tedy nebude vadit zadávat tento přepínač a proměnnou za něj, pak je to asi nejrychlejší řešení.
Nehodí se však například pro spouštění awk programu ze spustitelného awk souboru.
41
Globální a lokální proměnné ve funkci a návratová hodnota
Asi nebude velkým překvapením, že awk umožňuje vytvářet v místě výrazu také funkce. Trochu záhadný však
může být způsob používání proměnných. Pokud není proměnná definována v parametru funkce, je s ní zacházeno
vždy jako s globální proměnnou; volaná funkce, ovlivní stejnou proměnnou ve všech ostatních funkcích, jakmile
je někde zavolána. Vše je zřejmé z následující ukázky:
awk ’function p() {print x} function first() {x=x+50}; function s
eccond() {first(); x++}; BEGIN{x=10; seccond(); print x; p()}’
vytiskně dvakrát hodnotu 61. V žádné funkci není x uvedené jako parametr funkce a tak je ve všech funkcích
zacházeno s proměnnou jako s globální. Jakmile však uvedeme proměnnou jako parametr funkce, stává se z ní
lokální proměnná (hodnota se parametrem předávat nemusí, nebo se nastaví argumentem na nulovou hodnotu).
Pouze z toho důvodu – aby bylo zřejmé, že se jedná o lokální proměnné, a ne o skutečnou hodnotu předávanou
parametrem – používá se před proměnnou v parametru několik mezer takto:
awk ’function p() {print x} function first( x) {x=x+50}; functio
n seccond() {first(); x++}; BEGIN{x=10; seccond(); print x; p()}’
Nyní bude hodnota 11, protože ve funkci first je x lokální. Používat proměnné u delších programů chce tedy
určitý systém, jinak se nám snadno může stát, že si v nové funkci, nebo v jednom z mnoha výrazů ovlivníme stejnou proměnnou, která je již někde použita, a my jsme na to už dávno zapomněli. Pokud to tedy je
možné, bude lépe – ač ten způsob není moc šikovný – nastavovat proměnné ve funkcích jako lokální. Zde je vše
vysvětleno podrobněji.
Předáním hodnoty přes parametr funkce, se stává z proměnné automaticky proměnná lokální, jak již bylo
uvedeno. Poslední možnost, jak by ještě mohlo dojít k ovlivnění jinde používané hodnoty je předání odkazu
místo hodnoty. V tomto ohledu je zde jistá zvláštnost. Ať již konstanta nebo proměnná se chovají tak, že svoji
hodnotu do lokální proměnné uvedené v parametru funkce překopírují – odkazem se tedy předávat nedá –, u
polí tomu tak není, a je předáván pouze odkaz na původní pole.
awk ’function first(a){a[1]=a[1]+100}; BEGIN{x[1]=3; first(x); pr
int x[1]}’
Veškeré změny které funkce first s polem a provádí, jsou ve skutečnosti změnami v poli x!
Většinou potřebujeme po provedení funkce vrátit výsledek. K tomu slouží příkaz return, který vrátí zadanou
hodnotu. Návratová hodnota nemůže být odkaz; bylo by to také zbytečné – pracuje se s globálními proměnnými,
u lokálních to nedává smysl a u pole se pracuje s odkazem přes argument vždy.
Způsoby spouštění awk skriptu
Awk už je celkem slušný skriptovací jazyk, ve kterém se nechají dobře vytvářet různé prográmky pro počítání
nebo formátování textu. Častěji než u sed tedy nejspíš sáhneme ke způsobu zápisu skriptu do souboru; než do
příkazové řádky. Psaní výrazů je naprosto stejné, jen místo středníků (nebo mezery) pro oddělování výrazů a
příkazů budeme oddělovat novým řádkem. Komentáře ve skriptu lze psát klasickým způsobem za # na konec
řádky. Pro spuštění skriptu pak stačí jen zadat cestu k souboru za přepínač -f.
awk -f mujskript.awk
Pokud bychom chtěli mít rovnou soubor spustitelný; bez dalšího zabalení třeba do
začátek skriptu:
42
bashe. Je potřeba zadat na
#!/bin/awk -f
plus změnit souboru práva na spustitelný soubor. V tomto případě už můžeme náš skript spouštět, jako běžný
program. Tedy až na jednu podstatnou maličkost, která je pro takové použití omezující. Většinou totiž potřebujeme předat do skriptu také různé parametry a v tomto případě máme jedinou možnost – naplnit si proměnné
za -v. Jakýkoliv náš parametr – zadaný jinak – bude awk zkoumat jako soubor, který se má přečíst. Ono to
vlastně neplatí až tak docela. Neplatí to totiž v případě, kdy použijeme v awk skriptu jen BEGIN. V tomto
případě bychom mohli číst a testovat hodnoty v argumentu takto:
BEGIN {if ( ARGV[1] && (!system("test -f " ARGV[1])) ) { while(g
etline < ARGV[1]) ") print $0 } else exit}
Zde se například otestuje, je-li argument soubor. Pokud ano, pak se načte, jinak awk končí. (exit v tomto
případě ukončí program, ale pokud by následoval ještě výraz s END, pak se před ukončením provede.) Jedná
se ale spíše o specialitu, kdy ztratíme možnost číst také ze stdin. A navíc musíme použít pro načítání řádků ze
souboru while a getline. Tento způsob je tedy spíše okrajový. Jak potom tedy předávat našemu spustitelnému
skriptu parametry jinak než nešikovnou proměnnou?. Zatím se mi zdá, že je to u awk jediný problém, jenž
neumožňuje u spustitelného souboru zadávat parametry stejně jako třeba u skriptu bashe. Jisté řešení zde ale
přece existuje. Tím je spouštění našeho skriptu v awk ze skriptu v bashi. V bashi si tak zpracujeme hodnoty
z argumentu; což se dá poměrně snadno a na konci spustíme awk -f . . ., kterému předáme za -v přes proměnné
hodnoty z bashovského skriptu. Nevýhoda je zde v tom, že náš program bude tvořen dvěma soubory – awk
skriptem a spustitelným bash skriptem. I tento nedostatek se však nechá chytře odstranit. Můžeme totiž obsah
awk skriptu nakopírovat přímo do skriptu bashe mezi řádky:
cat > /tmp/awkskirp.awk << END_AWK
a
END_AWK.
Tímto se řádky uvnitř zkopírují do dočasného souboru. Na konci pak zavoláme ve skriptu bashe awk s tímto
dočasným souborem, který obsahuje celý awk skript; pak soubor smažeme. Může se to zdát sice jako geniální,
ale přesto to má jedno úskalí. Ve skriptu bashe se totiž všechny znaky, které začínají $ berou jako proměnná
a rozvinou se. Zřejmě v awk skriptu použijeme také $0, $1, $2, . . . ., jenž ve zkopírovaném souboru v dočasném
adresáři budou nahrazeny hodnotou vstupních parametrů bash skriptu (minimálně se $0 nahradí názvem bashovského skriptu a další $1. . . budou chybět). Jediné řešení je, v části se skriptem uvnitř našeho bash souboru
použít před každým znakme $ zpětné lomítko \$ (jedna substituce to jistí). No a tím jsme u konce. Máme hotový
awk program, který se na venek chová stejně jako třeba program v bashi, kterému můžeme předávat parametry
klasickým způsobem. Takto jsem řešil skript tabular, který jsem popsal v předchozím zápisku. Všimněte si také
zakomentovaného výrazu sed na konci, který jsem používal pro zmíněné nahrazení znaků $ a rychlé vkládání
skriptu tabular.awk do skriptu bashe mezi uvedené značky.
Pár otravných záležitostí – uvozovky, znak |, přepínač re-interval, neinicializované
proměnné
\047 je osmičkové číslo jednoduchých uvozovek. U awk má tato ascii hodnota podstatný význam. Vzhledem
k tomu totiž, že se výraz uzavírá do těchto uvozovek, je jejich použití uvnitř výrazu problémové (asi nemožné).
Pokud tak budeme potřebovat například nahradit funkcí gsub všechny jednoduché uvozovky v našem textu,
nezbude, než použít právě ascii hodnotu. Různá zpětná lomítka totiž v tomto případě nefungují. Například:
awk /\’/
, bude fungovat a vytiskne jen řádky, které obsahují uvozovky. Jakmile však výraz uzavřeme do uvozovek:
awk ’/\’/’
bude hlásit awk chybu. A to dokonce i v případě, že bychom použili dvojité uvozovky. Je tedy jediná možnost:
43
awk ’/\047/’
Používání ’ má prostě v awk zákaz. Zatímco u jiných programů můžeme většinou uzavřít řetězec nebo alespoň
jeden znak do jednoduchých uvozovek, u awk tomu tak není. Proto pokud budeme chtít nahradit jeden znak
funkcí gsub, musíme použít dvojité uvozovky:
gsub("P","q")
Pokud bychom tak chtěli nahradit jednoduché uvozovky v textu, nemáme jinou možnost než \047.
gsub("\047","|")
Znak | skrývá také jistá překvapení a pokud budeme chtít v textu nahradit znak | něčím jiným takto:
gsub("|","%")
, zjistíme, že se vloží znak (%) za každé písmeno. Abychom tedy nahradili skutečně znak
výraz vypadat následovně:
| třeba čárkou bude
gsub("\\\|",",")
nebo bychom museli použít místo uvozovek lomítka a tím se vše trochu zpřehlední:
gsub(/\|/,",")
Používání dvojitých uvozovek a lomítek není u awk také tak jasné, jak se zdá. Obecně u awk platí, že do
lomítek se uzavírá regulární výraz a do dvojitých uvozovek konstanta; jak už jsem také zmínil na začátku. V
manuálu se ale dočteme (Using Dynamic regexp), že je vhodnější uzavírat mezi lomítka také konstanty. Výraz
ve dvojitých uvozovkách je méně přehledný a musí projít nejprve expanzí. To je také důvod proč musí být u
uvedeného příkladu o dvě zpětná lomítka víc, pokud použijeme dvojité uvozovky.
Doplněno 15.8.2014: Pokud hodláme do textu umisťovat internetovou adresu pomocí funkcí sub, pravděpodobně se nevyhneme nemilému překvapení. Internetový odkaz, který v sobě bude mít znak & způsobí, že se do
tohoto místa vloží obsah podle zadaného vzoru (jak bylo popsáno v podkapitole s řetězci). Vždy musíme tedy
znaky & v textu, které hodláme použít v substituci vybavit zpětným lomítkem. Nejlépe takto:
gsub("\046","\\\\&",link)
a pak už můžeme v substituci adresu v proměnné link použít:
sub("/"[0-9]+"/"," \\ulink["link"]")
O ”špeky” tohoto druhu není u
kousek zaskočí.
awk nouze a po delší době, kdy awk nechávám spát, mě vždy nějaký takový
Přepínač –re-interval přináší další zvláštnost, na které může člověk uvíznout. Například běžné používání
rozsahu v regulárních výrazech.
sub("^s{2,6}","")
smaže znaky s na začátku řádku pokud je jejich počet za sebou v rozsahu 2 až 6 znaků. Jenže pozor, nic
se nenahradí, pokud nepoužijeme u gawk přepínač –re-interval (případně -W re-interval). Jinak totiž awk
rozsahy v regexp nepodporuje! (Doplněno: Není to nic až tak zvláštního, jak jsem dodatečně zjistil. Jen je třeba
znát rozdíl mezi klasickými a rozšířenými reg. výrazy. V uvedeném příkazu by vše fungovalo, pokud bychom
použili zpětná lomítka před závorkami.)
44
Také pozor na neinicializované proměnné! Většinou inicializaci proměnných neřešíme. Standardně je totiž proměnná prázdná a pokud ji použijeme jako číslo a inkrementujeme, pak se zvýší na 1 (prázdná proměnná je tedy
brána také jako 0). Někdy ale awk nemůže rozpoznat, jde-li o číslo nebo string. Například pokud použijeme
array[i] a proměnná není inicializována před tím na 0, použije se místo číselného indexu hodnota klíče prázdného stringu. Pokud pak dále inkrementujeme bude již další i=1,2,3 . . . Číselný index 0 však bude chybět.
Závěr:
Můj úplně první program v awk vznikl někdy před rokem a půl. Tehdy jsem jsem s vypětí všech sil sesmolil
takový prográmek, který hledá v textu maximální hodnotu (samozřejmě s nějakými těmi specialitami okolo, jak
to bývá). Psal jsem o něm tehdy v prvním díle o programu awk. To byly tedy mé zkušenosti s programováním
v awk; a až do současnosti jsem mu nevěnoval mnoho času. Až nyní jsem si řekl, že když už docela ovládám
sed, chtělo by to také něco vědět o awk. A to byl důvod ke studování a sepsání tohoto dlouhého zápisku, díky
kterému jsem si mnohé informace konečně srovnal v hlavě.
Během psaní jsem na internetu objevil několik velmi zajímavých odkazů, ze kterých jsem také dost čerpal.
Opravdu hezký blog píše Peteris Krumins; v jeho seriálu o Awk jsem pochopil mnoho z principů a naučil se pár
triků. Autor blogu také napsal parádní tahák, který je ke stažení na jeho stránkách ještě i v pdf a doc verzi; kdo
si to má všechno pamatovat, že. Eric Pement je dalším takovým textovým guru, jehož seznam jednořádkových
awk zápisů se šíří po internetu. Také z těchto stránek jsem načerpal pár krásných idiomatických výrazů. A
konečně již známá stránka – z psaní o sed – plná především různých příkladů.
Dlouho jsem přesně nevěděl kam awk vlastně zařadit: je to programovací jazyk nebo editor streamu. awk
je totiž oboje. Pokud budeme používat krátké idiomatické výrazy bude awk většinou jen editor streamu
jako sed. Jakmile však začneme plnit složené závorky funkcemi a příkazy a vytvářet program, který ani nemusí
pracovat se vstupním streamem, bude awk něco jako interpreter jednoduchého skriptovacího jazyka awk.
Po té co jsem psal jen o sed – a sed také výhradně používal pro vše možné včetně přečíslování řádků – mohu
konečně mnohdy sáhnout také po méně krkolomném řešení, které se bude víc hodit pro awk.
Ostatní díly:
’
Awk-árna
24.5.2014
Můj vlastní tabulkovač
awk ; bash
Čas od času potřebuji udělat jednoduchou tabulku. Samozřejmě že ne v excelu, ale většinou generovanou
nějakým skriptem spolu s daty v prostém textu. Proto jsem zajásal, když jsem narazil na tabulkovač, o kterém
jsem psal v předchozím zápisku. Jenže. . . – Od té doby se mi sešlo několik událostí, které vedly k tomu, že jsem
si udělal svůj – podle mne lepší ;) – tabulkovač.
Začalo to tím, že jsem se pustil do experimentování s awk. Na awk jsem se chystal už delší dobu: po tom, co
jsem prolomil tabu editoru sed. Navíc jsem potřeboval vylepšit skript na výpočet nástavků, který byl před tím
v bashi, kde počítání s reálnými čísly nebylo zrovna moc pohodlné. Tak jsem tedy vytvořil svůj první awk
program nastavky.awk, který generuje výstup do hezkých tabulek. Awk se mi opravdu zalíbil a já oněm začal
také psát – poznámky a zkušenosti (vyjde příště). A tak se mi základ funkce pro tvoření tabulek hodil k tomu,
abych na něm ověřoval své nové poznatky. S tím pomálu funkce na obalování textu rostla, přičemž se s novými
nápady stávala stále univerzálnější, až z ní vznikl plnohodnotný ASCII tabulkovač tabular.
45
Aby byl tabulkovač tak říkajíc user-friendly, bylo třeba nejedné vychytávky, nad kterou jsem si musel lámat
hlavu. Použil jsem například bash k obalení awk skriptu; awk totiž umožňuje nastavit parametry jen přes
proměnné za -v, a to by se asi žádnému uživateli nelíbilo. Awk program je tak součástí bash skriptu, ze kterého
se awk program uloží vždy do /tmp adresáře a spustí se. V bashi jsou také vytvořeny počáteční testovací funkce
a help. Vznikl tak jakýsi hybrid, který je oproti původní představě celkem – na svůj význam – dlouhý a složitý.
Ale myslím, že obsahuje mnohé zajímavé vlastnosti, které jsem zatím u jiných ASCII tabulkovačů neviděl ;).
Nakonec jsem zjistil, že udělat si vlastní tabulkovač pomocí awk, je ten nejlepší způsob jak awk dobře poznat.
22.5.2014
ASCII tabulkovač
perl ; bash ; vim
Nedávno jsem na internetu narazil na užitečný skriptík v perlu, který dokáže vytvářet hezké ASCII tabulky.
Několik tabulek už jsem před tím ručně vytvářel a nebyla to žádná rychlovka. A tak mi tento skriptík potěšil;
je opravdu promakanej. Jen malinko jsem jej upravil, aby byl poslušný také pro znaky s diakritikou. Upravená
verze je ke stažení zde.
V editoru vim je jeho použití také velmi snadné. Stačí jen označit řádky s položkami oddělenými čárkou a do
příkazového řádku editoru napsat:
:’<,’>!tab -t
, původní řádky se tak nahradí hezkou tabulkou. Pokud se nám tabulka časem znelíbí, lze aplikovat také opačný
postup a tabulku přeměnit zpět na položky oddělené čárkou (je popsáno v nápovědě). Tak lze rychle vytvářet
tabulky, třeba jako jsou tyhle dvě.
1.4.2014
Zvláštnosti Window Makeru
WindowMaker
Po roce a půl používání okenního manažeru Window Maker si troufám napsat, že WM mi opravdu sedí.
WM jsem párkrát zkoušel už před tím, ale byly to jen unáhlené pokusy, které vedly vždy zpět k Xfce. Dnes
už vím, kde byla chyba – pokud se člověk nezbaví návyků postavených na klasické liště a nabídce start, pak
je používání WM prostě problematické a neintuitivní. Tento zápisek tak bude o hlavních zvláštnostech, které
odrazují mnohé začínající uživatele. – Raději hned upozorňím, že se jedná jen o mé osobní zkušenosti získané
pouze na základě používání. Nikdy jsem totiž nevydržel číst článek o Window Makeru do konce, natož pak
nějaký manuál.
Ikony na ploše
Asi první věc, které člověk musí přijít na chuť, je práce s ikonami. Ikony jsou poměrně velké čtverce, které
nicméně dobře vyhovují současnému modernímu stylu, kdy se umisťují větší ikony po levé nebo pravé straně
pod sebe (například nové Ubuntu Unity). Člověka také hned napadne, že tyto obří ikony (velikost lze v
nastaveních upravit) jsou jak dělané pro dotekové displeje, což bych rád někdy vyzkoušel. V tomto ohledu WM
předběhl dobu o mnoho let. Tím nejvíc zvláštním na ikonách však zůstává jejich způsob vytváření.
Zatímco se tradičně ikona vytvoří a teprve po té se k ní přiřadí program, u WM je tomu naopak; je to postup
naprosto jedinečný, není proto divu že nováčka zaskočí. Samotná ikona ve WM nic neznamená a nelze ji proto
ani vytvořit. Místo toho se každý program, který spustíme, otevře se svojí ikonou na všech plochách; nejčastěji u
spodní hrany (v doku) případně kolem sponky (pokud ji používáme). Jakmile okno programu minimalizujeme,
46
přidá se ještě k ikoně – ale už jen na konkrétní ploše – další ikona s malým černým proužkem s názvem programu,
která ovládá okno (okno jí lze maximalizovat, čímž ikona opět zmizí). Druhá ikona plní tedy stejnou funkci jako
záložka v klasické liště; jen s tím rozdílem, že lišta u WM není vidět, ale můžeme si ji představit u spodní nebo
horní hrany (lze vybrat v nastaveních). Aplikační ikona (ikona na všech plochách) setrvává v doku po dobu
spuštění programu bez ohledu na stav okna. Pokud bychom se nacházeli na jiné ploše, pak kliknutí na tuto ikonu
programu dojde k přepnutí na plochu, kde je okno programu nebo ikona minimalizovaného okna. Díky tomu
se stejný program nespustí nikdy dvakrát, což je většinou žádoucí. Pokud bychom přece chtěli spustit program
ještě jednou, lze tak učinit v plovoucím menu ikony (zobrazí se pravým tlačítkem myši). Přes tato pravidla má
WM zřejmě ještě i jiná, protože například xpdf volaný přes xdg-mime (například u souborového manažeru
nebo v prohlížeči) se spustí bez aplikační ikony a navíc tolikrát, kolikrát je libo. (V takovém případě je pak u
okna v menu atributy složka atributy aplikace neaktivní; menu bude ještě popsáno dále.)
Na obrázku je můj současný dekstop, kde jsou po levé straně ukotvené aplikační
ikony nejčastěji používaných programů plus několik dockapps. Ve spodní liště jsou
pak aplikační ikony spuštěných programů (byly spuštěné z menu) – xpdf a screengrab. Vedle se nachází ikona minimalizovaného okna slovníku Lingea, u kterého
mám aplikační ikonu zakázanou (bude ještě popsáno). Na ploše je pod oknem programu xpdf okno souborového manažeru spacefm, který má svojí ikonu ukotvenou
v postranním doku.
Na obrázku bohužel není ukázka situace, kde by vedle sebe byly ikona minimalizovaného okna a aplikační ikona
stejného programu. To je nejspíš ona zvláštnost, nad kterou se mnozí začínající uživatelé pozastaví. – Pokud
se tak nad tím zamýšlím, bylo by nejspíš méně matoucí, kdyby kolem obou stejných ikon byl rámeček. Pak by
to vypadalo, jako klasická – větší – záložka v liště rozdělaná na půl, kde v jedné půlce je tlačítko pro ovládání
okna a v druhé tlačítko programu, které přepne vždy na patřičnou plochu. Myslím, že rámeček okolo a případně
také lišta, tak jak ji známe, by chování částečně přiblížily tomu, na co je uživatel už zvyklý. – Jenže to už by
nebyl WM a jeho jedinečné promyšlené chování. Totiž oddělené ikony mají ve WM svůj význam: Tím že si
každý nově spuštěný program nese svou ikonu vždy na plochu, lze snadno ikonu již na ploše ponechat – pouhým
zaškrtnutím ”Držet zde” v plovoucím menu ikony. Ovšem položka se nám v menu objeví pouze tehdy, pokud je
ikona umístěna v místě, kde lze takové ikony ponechávat! Taková místa jsou na obrazovce kraje vpravo, vlevo (u
novějších verzí WM to lze tuším nastavit i jinak) nebo okolo sponky, kterou je možné umístit na ploše kamkoliv.
Jakmile po takovém ukotvení aplikační ikony zavřeme okno programu (ukončíme program), zůstane již ikona
na ploše a my s ní můžeme kdykoliv program spouštět; další aplikační ikona se po spuštění – pro tento program
– už neobjeví.
Asi každý uzná, že je to celé zvláštní oproti běžnému chování. Má to ale svůj důvod: taková ikona je totiž o
něco chytřejší, než ta co se vytváří konvenčním způsobem u jiných manažerů. Kromě uložené cesty k programu
si totiž uchová atributy k oknu programu z WM CLASS (lze zobrazit pomocí xprop), na základě kterých
ikona pak pozná, byl-li spuštěn program třeba jiným způsobem než přes ikonu, přičemž lze i takto spuštěný
program ikonou dále ovládat. Tímto jsou tedy svázány ikona, okno a program, přičemž jim lze nastavit celou
řadu vlastností. To je důvod proč má každé okno mnohem víc nastavovacích prvků: v plovoucím menu okna v
položce atributy nalezneme vše nejen pro okno samotné, ale také ikonu, aplikaci a jiné.
Vtipné je odstraňování ikony – v menu není u ikony položka odstranit: stačí ukotvenou aplikační ikonu vzít
(nesmí být program spuštěný) a odtáhnout ji někam od místa, kde byla uchycena – mimo aplikační dok nebo
sponku – a bude jednoduše po ní; pokud máme zapnuté v menu efekty, pak s patřičným efektem roztříštění.
A nyní se asi chytnete za hlavu, protože když už jsem sáhodlouze vysvětlil výhody aplikační ikony, tak vám
hodlám nyní sdělit, že v nastaveních atributy jsou pro nastavení aplikace tři položky a jedna z nich je vypnout
aplikační ikonu. Pokud to provedeme, pak tento spuštěný program už nebude mít na ploše aplikační ikonu.
To se hodí třeba tehdy, pokud chceme nějaký program spouštět klávesovou zkratkou nebo z menu a o jeho
aplikační ikonu na ploše nestojíme.
Krásné, že?. Další možnosti plné překvapení se otevřou, jakmile člověk vyzkouší některé dockapps, o kterých
jsem psal krátce zde. Jejich chování je – přes svoji vizuální podobnost – od běžné ikony odlišné a řekl bych, že
47
už i více intuitivní, takže není asi třeba žádné vysvětlení.
Různá jiná překvapení
Ikona si dokáže pamatovat – pokud je to nastaveno v menu u chování oken – poslední místo okna na ploše a
jeho velikost. To bývá výhodné, protože některé programy (mám zkušenst s gimp, mtpaint) mohou mít po
spuštění část svého okna mimo obrazovku (gimp má volbu nastavení okna také v Preferences – tam jsem
nakonec problém vyřešil pouhým resetem na výchozí, tím se okno zmenšilo). Zajímavé je, že si WM pamatuje
pozici a velikost okna i po odstranění ukotvené ikony.
Další ze zvláštností zřejmých na první pohled, je hlavní spouštěcí menu, které se aktivuje pravým tlačítkem myši
na ploše. Na tom by nebylo nic až tak zvláštního, nebýt toho, že se každé otevřené podmenu zavírá samostatně
horním křížkem a lze jej oddělit a kamkoliv umístit. Má to opět své výhody, které jistě mnozí ocení; já menu
zas tak často nepoužívám.
U mnohých extra lehkých manažerů bývá nastavení prováděno pomocí konfiguračního souboru. To ale u WM
není běžné, ač je to také možné – vše je v ~/GNUstep. Grafické menu má WM snad nejlepší co jsem kdy
používal. Je v něm vše přehledně rozděleno – nastavení bývá snadné a intuitivní, navíc je výborně počeštěné.
Až po čase člověk ocení co všechno se zde nechá nastavit, ale přitom to podstatné dokáže nastavit snadno i
nový uživatel. – Jednou je to grafický manažer, tak proč do toho plést nastavení přes textový soubor, přijde mi
to velmi rozumné, když je menu tak vylazené. – Také pro ikony a okna je celá řada nastavení, o kterých zde
nechci psát; jsou přístupná z plovoucích menu.
Svými vlastnostmi nutí WM celkově k jinému zacházení s okny a využívání plochy. Zejména na menších monitorech totiž budeme mnohem víc využívat nastavení, které umožní překrývat ikony v doku oknem. Zde se mi
mnohdy hodí rychlé zvětšení okna na celou obrazovku, které je u poslední verze WM vyřešeno k mé naprosté
spokojenosti – stačí jen stisknout Ctrl+Shift a poklepáním na horní lištu okna se okno zvětší. Samozřejmě se
nechá okno také přizpůsobit prostým tažením za roh okna.
Po prvním spuštění může být pro mnohé záhadnou sponka. Ta se však vůbec nemusí používat, pokud nám
budou stačit místa pro ukotvení ikon ve vymezených oblastech po stranách. Zatoužíme-li ale po ikonách nebo
dockapps někde jinde na ploše, nechá se tak učinit pouze přichycením ke sponce. – Asi nejlepší představa WM
obrazovky je jakási černá díra; tedy ne jako plocha. Mimo oblast vyhrazených míst nelze ikony dávat, jinak
nám zmizí v nenávratnu. Zvláštní, že?. Ale proč ne. Alespoň už nemám na ploše ”bordel” :). Po kratší době
používání mi to přišlo opět velmi rozumné.
Někdy je přece jen potřeba vytvořit ”hloupou ikonu”, neboli pouhé tlačítko třeba pro nějaký náš skript, který
vůbec nemá okno. Samozřejmě je to možné. Já zatím objevil nicméně jen způsob, kterým se na to jde trochu oklikou: Půjčím si aplikační ikonu libovolného programu, kterou ukotvím. Pak v plovoucím menu ikony
změním v nastaveních spouštěný program (třeba na: bash -c ’mujskript.sh > out.txt’) a také změním
obrázek ikony. Ikona už tak není svázána s původním programem a oknem. Nová ikona tím zřejmě ztratí i
vlastnosti původního okna z WM CLASS a chová se pak už jen jako obyčejná ikona, která je dobrá akorát tak
ke spouštění programu ;).
Další problém, který jsem řešil, bylo spouštění programů v terminálovém okně. Jelikož mám už terminál xterm
spojený s ikonou na ploše, bylo zde opět nutné použít malého triku, abych nové okno s programem – který se
má zobrazit v terminálovém okně – neměl také svázané s ikonou čistého terminálového okna. xterm naštěstí
umožňuje nastavit hodnotu WM CLASS. Stačí tedy spustit program (např. konzolový přehrávač) takto xterm
-class ncmpc player -e ncmpc, čímž se na ploše objeví aplikační ikona s jiným WM CLASS než má původní
terminálové okno, které také spouští xterm. K aplikační ikoně pak zbývá jen nastavit jiný obrázek ikony a ikonu
ukotvit na požadované místo. Obě ikony které spouštějí stejný terminál se nebudou ovlivňovat.
48
Závěr
Už pod Xfce jsem si vytvořil skript run once only, který tamní ikony dokázal povýšil do stavu chytrosti těch u
WM. Není divu, že jsem přechodem na WM zajásal. run once only však dokáže nejen přesunout na plochu,
kde je program, ale také ještě program maximalizovat nebo minimalizovat (případně aktivovat a deaktivovat)
a to vše jen jednou ikonou, má tedy stále své výhody a pro některé programy (zejména spouštěné klávesovou
zkratkou) se stále hodí ;). – Obdobné chování ”chytrých” ikon pomocí skriptu run once only by bylo u WM
možné také vytvořit. Stačilo by jen v nastavení ”hloupé ikony” (její vytvoření bylo popsáno) zadat v nastavení
ikony program spolu se skriptem např. takto: bash -c ’run once only mtpaint’. Ráze by se stala z hloupé
ikony chytřejší, která také umí pracovat s WM CLASS; tím jsem jen chtěl ukázat, že vytvoření sofistikovanější
běžné ikony není zas taková věda, jak by se z mého popisu zde mohlo snad zdát: skript run once only je jen
pár řádek bashe.
Běžnému uživateli budou některé věci zřejmě chybě. Binárka WM má pouze 5,4 MB, tak ani není divu, že
není dodán všechen luxus jako třeba u Xfce. To co nejspíš většina bude postrádat je souborový manažer
typu Thunar, který by byl schopen sofistikovaně připojovat a odpojovat externí média. Dále pak nástroj pro
nastavení vlastních klávesových zkratek pro spouštění různých programů. Já jsem tyto věci vyřešil pomocí
udevil, spacefm a xbindkeys. O programech – i o pár dalších – jsem zde během předchozího roku psal. Nyní
tak mohu shrnout, že stačí opravdu málo – málo dalších programů, málo místa na disku, málo paměti či výkonu
CPU – a člověk může být s tímto manažerem spokojen.
To, co mne v rychlosti napadne, pokud se pokusím porovnat WM s Xfce, je – větší pořádek na ploše, kontrola
spuštěných programů a oken, maximální využití celé plochy obrazovky, velmi decentní vzhled oken.
Nechtěl jsem psát návod, který se dá najít na internetu, ale jen charakteristiky a největší zvláštnosti. Snad se
mi to trochu povedlo. Psát o WM není totiž snadné a to je také zřejmě důvod, proč většinou články na internetu
o něm nedočtu do konce. WM se prostě musí poznat.
21.3.2014
Spustit jenom jednou II.
bash
Je asi jen jeden skript, který používám několikrát denně: tím je run once only, který světlo světa spatřil už
déle jak před rokem a půl. Psal jsem jej v době, kdy jsem ještě neměl ponětí o mnohých finesách ukrytých
v programech xargs, grep a sed. Zbytečně jsem tak používal smyčky a pole v bashi, které jsou neskutečným žroutem času. A když už jsem nabral pár těch vědomostí, hned mne napadlo, že by si tenhle skript
zasloužil upgrade.
Je zde proto desátá verze. Zatímco předchozí byly převážně testovací a odlazovací, tentokrát už jsem princip
jen okopíroval a vybral náhrady za smyčky bashe. Skript je nyní výrazně kratší a rychlejší; a to jsem tenkrát
myslel, že už se nic zrychlovat nedá. Na některá drobná vylepšení ale stejně došlo.
12.3.2014
Mplayer
bash ; mplayer; VIDEO ; terminal
Kdo by neznal mplayer. Přehrávač, který snad na každé platformě přehraje vše, co se přehrát dá: už pouhým
zadáním souboru nebo internetového odkazu. Někdy ale přecejen může přijít nějaký ten parametřík vhod.
Přičemž hledat v manuálu ten správný, člověka snadno odradí, protože takové počínání připomíná hledání jehly
v kupce sena. A tak vznikl tento zápisek, kterým bych chtěl poukázat na některá užitečná nastavení, která jsem
doposud použil.
49
Přehrávání několika souborů
Vstupním parametrem přehrávače nemusí být jen jeden soubor (nebo playlist), ale můžeme uvést několik souborů, které budou postupně přehrávány, přičemž je podporována také maska například takto:
mplayer -fixed-vo video_[0-9]*.mp4
Přepínač -fixed-vo je v takovém případě většinou žádoucí, jinak se totiž okno přehrávače vždy po skončení
každého videa zavře, což působí dost rušivě. (Pokud by se nejednalo o video soubor, ale jen playlist, musíme
použít volbu -playlist.)
Grafický výstup a výstup do souboru
Začnu zcela běžným nastavení video výstupu:
mplayer -vo fbdev2 filem.mp4
, nastaví framebuffer jako video výstup. Většinou jej použijeme, pro přehrávání videa v konzoli. Seznam
dostupných výstupních ovladačů si můžeme nechat zobrazit zadáním help za -vo. Další užitečné nastavení –
zejména pro konzoli – změní velikosti obrazu a pozici na obrazovce:
-vf scale=300:-3 -geometry +750+0
, šířka 300px a výška (-3) se přizpůsobí; pozice bude x=750, y=0 od levého horního rohu. V konzoli se můžeme
setkat s efektem, kdy obraz bliká spolu s tím, jak se na obrazovce přepisují data stavového řádku. V takovém
případě se může hodit volba -really-quiet, která textový výstup zcela umlčí.
Následující volby obrazový výstup vypnou a uloží vše do souboru:
-dumpstream -dumpfile file.flv
Přípona souboru zde nehraje roli: výstup se uloží ve stejném formátu, jako je stream. (Pokud si nejsme jisti
v jakém formátu máme výstup uložený, můžeme použít přehledný program mediainfo, který zobrazí veškeré
informace.) Převod do jiného formátu lze převést poté programem ffmpeg nebo mencoder. Takto nabízí
mplayer snadné přesměrování do souboru. Často ale potřebujeme uložit pořad, který zároveň sledujeme. Stačí
použít -capture místo -dumpstream a nahrávání je možné – během přehrávání – kdykoliv spustit klávesou
c (případně ovládáním přes fifo, jak bude ještě popsáno). (doplněno: nahrávání s pomocí -capture funguje
pouze pro MPEG) Více rustikální způsob, který jsem používal v době, než jsem se prokousal k uvedenému
způsobu s -capture, je rozdvojení výstupu. Jelikož se jedná o způsob, který lze použít také pro jiné programy,
uvádím jej zde také. Zde je zapotřebí vytvořit rouru pomocí mkfifo:
mkfifo /tmp/mpl_dump.fifo
, kterou použijeme jako výstupní soubor přehrávače
terminálu například takto:
mplayer, jenž pak spustíme na pozadí nebo v jiném
mplayer -dumpstream -dumpfile /tmp/mpl_dump.fifo rtmp://fms5.visi
onip.tv/live &
Přes fifo pak budeme čerpat výstup jednak do přehrávače spuštěného pro přehrávání, ale také ještě – pomocí
tee – budeme data ukládat na disk takto:
50
cat /tmp/mpl_dump.fifo | tee -a -i video.flv | mplayer Pomlčka v místě prvního parametru mplayeru je zde použita pro čtení vstupu ze stdin.
Uvedený způsob jsem použil ve skriptu, který přehraje (případně nahraje)
jen o trochu upravený skript z blogu.
stream z televize NOE. Jedná se
Problémové přehrávání streamu
A je to tady. Právě zde budeme nejspíš přinuceni sáhnout po různých dalších parametrech. Nejčastěji asi
použijeme nastavení vyrovnávací paměti:
mplayer - -cache 1000 -cache-min 50
velikost za -cache je v kilobajtech. Za cache-min lze zadat procentuální hodnotu, která musí být dosažena,
aby se přehrávání spustilo. Pomlčkou je také zde přehrávači sděleno, že data přijdou ze stdin; bývá to běžné
například při zachytávání streamu rtmp přes rtmpdump:
rtmpdump -v -r rtmp://fms5.visionip.tv/live -a live -W http://rt.
com/s/swf/player5.4.viral.swf -p http://rt.com/on-air/ -y RT_3 | mpl
ayer - -cache 5000
Tímto způsobem mi dlouho fungoval přehrávač RT 3, o kterém jsem psal zde; nyní je již stream nefunkční. V
případech, kdy server nemá vyšší nároky na ověření, lze přehrávat rtmp stream také rovnou přehrávačem,
ale většinou mít takové štěstí nejspíš nebudeme. (Nevýhodou napojení mplayeru přes rouru za jiný program
spočívá v tom, že jej nelze v konzoli ovládat pomocí běžných klávesových zkratek.)
V případě streamu – u živého vysílání – na nás mohou čekat také další překvapení, neboť data bez úvodní
hlavičky nemusí přehrávač bezpečně poznat. Mnohdy je proto potřeba vše zadat například takto:
-fps 25 -demuxer lavf -lavfdopts format=flv
, kde za -lavfdopts je uveden formát flv, který protokol rtmp nejčastěji přenáší. Může se hodit -demuxer,
nejčastěji bývá lavf. Případně také počet snímků za vteřinu; pokud bychom zadali špatný, bude video rychlejší
nebo naopak pomalejší; bez zadání počtu snímků se může video zadrhávat, jak jsem se nedávno přesvědčil.
Jakmile se nám už daří stream přehrát, ale rozchází se zvuková stopa, může vše vyřešit:
-autosync 3
Správnou hodnotu pro algoritmus je potřeba zjistit metodou pokus-omyl, ale většinou s 1, 2 nebo 3 bude vše v
pořádku (více v manuálu).
Stream pro kanál RT 3, který mi dlouho fungoval, byl nedávno změněn a dnes již rtmp nepoužívá. Místo toho
je přes klasické http přenášen formát MPEG-TS (mpeg-2 rozdělený na krátké úseky – segmenty, jejichž adresa
streamu je uváděna vždy v novém playlistu m3a8, který se stahuje v intervalu přibližně 10 sec.). Pro tento
nový stream jsem použil nastavení formátu:
-lavfdopts format=mpegts
51
Čekání na stream
Může se stát, že v krátkém okamžiku server nedodá potřebná data: výsledkem je pak ukončení mplayeru.
Bohužel mplayer zřejmě nemá volbu, která by umožnila na data počkat. Zkoušel jsem -idle, ale bez výsledku.
Jakmile – v době nejvíce sledovaného zpravodajství – nestačil server dodávat data, mplayer vždy ukončil
přehrávání. Nakonec jsem se tedy rozhodl, ošetřit vše skriptem. Základní princip je snadný: jakmile klesne
hodnota cache pod 20%, pošle se mplaeru příkaz pause, jakmile wget stáhne nový segment – a je tak co
přehrávat –, pošle se znovu play. K tomuto řešení je však zapotřebí pár vychytávek. První jsou options:
-slave -input file=/tmp/mplayerfifo
, jenž umožní ovládat mplayer přes rouru (fifo soubor) /tmp/mplayerfifo. Soubor je potřeba vytvořit pomocí
mkfifo. Pak se nechá přehrávač ovládat zapsáním do suboru:
echo "pause" > /tmp/mplayerfifo
Pro výpis všech příkazů je možné zadat za -input cmdlist. – Dále je potřeba získat ze stavového řádku
hodnotu cache, kterou budeme porovnávat. Jelikož je stavový řádek posílán jako stderr (narozdíl od ostatních
informací), musíme jej tedy převést také na stdout, a pak výstup zapisovat do souboru. (přehrávač mplayer
musíme spustit jako proces na pozadí, abychom mohli ve skriptu dále s hodnotami pracovat):
mplayer ..... 2>&1
| cat > /tmp/rtcom.mplfifoO &
Ze skriptu je pak již zřejmé, jak se dále s výstupem pracuje: v případě poklesu pod 20% se vyšle příkaz pause
a čeká se až do doby, než se stáhne další segment – teprve pak se pošle znovu pause, čímž se přehrávání opět
spustí. Zastavování videa sice není nic příjemného, ale je to pořád lepší, než ukončení přehrávání v důsledku
chyby neúplného paketu. V případě RT se mi tento způsob osvědči, neboť výpadky jsou jen občasné v době
některých zpravodajství; což se dá vydržet.
Segmenty o velikosti přibližně 1 Mbyte stahuje – ve skriptu – wget a přidává je vždy do souboru /tmp/rtcom.ts dump;
MPEG-2 se nechá spojovat jen pouhým pospojováním (přesnější anglický výraz concatenate). Soubor je
předán přehrávači mplayer; princip je trochu složitější – v případě delšího výpadku se použije pro nový
segment nový soubor, aby nepatřičným napojením nedošlo k desynchronizaci obrazu se zvukem (pořípadně
zamrznutí obrazu).
Skript zobrazuje ve stavovém řádku hodnotu cache a počet segmentů, které ještě čekají na stažení; playlist s
novými segmenty se aktualizuje vždy po pěti až deseti vteřinách; nové nezpracované segmenty se pak řadí do
fronty a čekají na stažení programem wget.
Jelikož nejsem vášnivým pozorovatelem pohyblivých obrázků, je tohle zatím vše, co jsem kdy potřeboval;
jistě je celá řada dalších užitečných nastavení, která jsem nezmínil. Časem zde tak pravděpodobně některá
ještě přibudou.
6.3.2014
Konzolový pdf prohlížeč cpdf
terminal; bash; OpenCV; pdf-tools
Pokud může rozumný uživatel v konzoli něco postrádat, pak nejspíš pdf prohlížeč. Většina dokumentů, manuálů
a co já vím co ještě, je totiž v pdfkách; navíc je tu velká skupina ne příliš oblíbených formátů, u nichž je převedení
do pdfka – pod linuxem – jediné řešení jak dokument otevřít. Zkrátka otevírat pdf soubory pod linuxem,
je snad ještě víc potřeba, než pod jiným operačním systémem. ”No dobře,” říkáte si nejspíš: ”Ale proč v
konzoli.” Tak tady rozumnou odpověď nemám ;). Snad jen proto, že prostě mám konzoli raději než terminál
v okně. A když už v konzoli trávím více času než pod x-serverem, pak se mi prostě do oken ani moc nechce a
raději si otevřu pdfko v konzoli.
52
Co máme k dispozici hned
S prohlížečem obrázků fbi se nainstaluje kraťičký skript v bashi fbgs. Ten dokáže pomocí programu ghostscript
převést stránky pdfka na obrázky, které pak fbi načte a zobrazí. Jiná možnost – pokud vím – v konzoli neexistuje.
Převod samozřejmě několik vteřin trvá, ale dá se to jednou za čas vydržet, až na velmi dlouhé dokumenty. Do
seznamu nepraktických věcí kolem tohoto skriptu se nechá zařadit celá řada maličkostí. V zápisku Rychlovky
v bashi jsem psal o drobných vylepšeních, které jsem provedl, aby bylo používání příjemnější. To byly první
pokusy, které přece jen nějaké to mínus ubraly.
Až nedávno jsem se pustil do větších úprav, a výsledkem je nový skript cpdf.sh; u kterého jsem chtěl dosáhnout
toho, aby se chování co nejvíce příblížilo běžnému pdf prohlížeči a dobře se mi s ním pracovalo. Princip z
fbgs zůstává stejný: převod na obrázek pomocí ghostscriptu. (cpdf.sh nahrazuje také i dříve popsaný skript
pdf-reader.sh)
cpdf.sh vs. fbgs
Co jsem tedy všechno vylepšil. Podstatnou maličkostí je přejmenování souborů na formát 003-page-from054.tiff; o tom už jsem ale psal v odkazovaném článku. Velmi užitečnou funkcí je automatická detekce obrázků
v dokumentu a převod těchto obrázků do barevného formátu, zatímco se zbytek převádí jen do 1-bit černobílé;
dokument tím neztratí na vizuální kvalitě a převod je mnohem rychlejší, než kdybychom převáděli všechno do
barevného formátu. Další podstatnou maličkostí je správné zobrazení v prohlížeči fbi: s parametry -a -fitwidth
a -P je to ono správné nastavení, kdy se s obrázkem už nemusí většinou nic dělat a jen se listuje po stránkách
šipkami; samozřejmě také dostatečné rozlišení zvolené u programu ghostscript, aby písmo nebylo kostrbaté,
ale zároveň aby převod netrval dlouho. A konečně velmi důležitá věc, kterou už jsem měl hotovou v předchozích
verzích, a zde jsem ji jen vylepšil: možnost zadávání rozsahu stran, aby se nemusel převádět vždy celý dokument
pokaždé, jakmile si chceme přečíst jen několik stran. – To jsou tedy podstatné maličkosti. – A nyní něco, s čím
jsem možná trošku ujel; díky tomu jsem si ale mohl parádně užít svých oblíbených prográmků sed a awk ;); a
také jsem se dost dozvěděl o možnostech převodu pdf souborů do textového formátu a zopakoval si některé věci
kolem openCV. O co tedy jde?. Je to funkce vyhledávání. Věřte či ne, cpdf.sh dokáže najít výraz (v textové
podobě dokumentu), a pak na stránce řádek zvýraznit. Něco v dokumentu najít se jednou za čas hodí, ne ;).
Hledání v pdf dokumentu
Program pdftohtml dokáže vytvářet jednak klasický html formát, a pak také velmi podrobný html formát
včetně přesné pozice (souřadnic) jednotlivých řádků; z něhož je možné dostat potřebné souřadnice řádku s
hledaným textem. Program není s nastavením takového výstupu ale nejrychlejší, a tak před tím musíme použít
program pdftotext, který v celém dokumentu najde hledaný výraz; program pdftohtml se pak spouští už jen
pro konkrétní stránky. Parser (za pomoci sed a awk) najde vertikální pozice řádků s hledaným výrazem na
konkrétní stránce. Získané informace o stránce a poloze se dále použijí pro zvýraznění kreslícím programem (z
důvodu rychlosti se jen vykreslí obdelník okolo řádku – žádná barva s pseudo-trasparentním pozadím); jako výchozí kreslící program je nastaven convert z balíku ImageMagick, který je ale dost pomalý. Udělal jsem proto
krátký kreslící prográmek používající knihovnu openCV. Ten kromě rychlého vykreslení obdelníků na stránce
provádí také přepočet souřadnic z pdf dokumentu na pozice v převedeném obrázku (má jiné rozlišení než pdf):
přepočet pomocí kalkulátoru bc (jedná se o reálná čísla) v bashi je dost pomalý. Program draw rect se nechá
snadno po instalaci knihoven openCV přeložit; přičemž tento malý pomocník pak vše výrazně urychlí. (Překlad
je možné provést pomocí příkazu uvedeného v hlavičce souboru, nebo vytvořením projektu pomocí cmake.)
Rychlost hledání
Převést celý dokument na text trvá u rozsáhlejších souborů (nad sto stran) několik vteřin. Proto se převedené
textové soubory ukládají do adresáře /tmp/var/cpdfsh. Poku už se v adresáři převedený textový soubor
53
nachází, použije se ten a nic se nepřevádí. Samozřejmě je možné parametrem -r uložený soubor ignorovat,
parametrem -d soubor po ukončení smazat nebo pomocí -dd smazat celý adresář /var/tmp/cpdfsh.
Vykreslení obdelníků okolo řádků u jednoho obrázku (na jedné stránce) trvá přibližně jednu vteřinu; pokud se
použije program draw rec s vykreslováním jen černobílého obdelníku. I to může být dlouhá doba, pokud byl v
dokumentu výraz nalezen na mnoha místech. V tomto případě je zde možnost – po kontrole textového výstupu
nalezených řádek se zvýrazněným hledaným textem – zadat jen konkrétní stránku nebo rozsah, který se převede
na upravené obrázky (se zvýrazněním).
O něco delší doba přípravy jedné zvýrazněné stránky je také dána tím, že se zde převádí stránky na obrázek
jednotlivě; převod programem ghostscrit je pomalejší, než při převodu stran najednou (případně omezený
zadaným rozsahem), jak se používá v běžném prohlížecím módu.
Hledal jsem jak hledat
Cesta k tomuto řešení nebyla přímá, ale vedla ještě přes dvě verze, které jsem nakonec zavrhl. První, velmi
obskurní řešení vůbec nevyžadovalo program na analýzu pdf dokumentu vracejícího přesnou polohou textu.
Pomocí Houghovi transformace plust nalezení objektů a několika morfologických úprav se v obrázku vyhledaly pozice řádků, kde se pak zvýraznil ten potřebný. Řešení však mělo vyšší požadavky na kvalitu uspořádání
textu: text nesměl být rozházený, musel mít alespoň nějakou mezeru mezi řádky a řádky musely mít minimální
délku. Předpokladem bylo také to, že pdftotext vytvoří přesné rozložení řádek jako v dokumentu. Výhodou
řešení byla rychlost, která byla ještě o něco vyšší než u stávající varianty.
Druhým řešením bylo použití programu pdf2pdf.py z projektu PDFMiner. Tento velmi šikovný prográmek
funguje obdobně jako pdftohtml, jen s tím rozdílem, že jeho výstup je ještě podrobnější: obsahuje pozici
každého znaku v textu. Díky tomu je možné lokalizovat nejen rozházený text, ale také vertikální text. (Je
možné pak zvýraznit přesně hledaný text, ne jen řádky.) Jeho nevýhodou je ale rychlost: je asi dvakrát pomalejší
než pdftohtml.
Současná varianta byla až třetím pokusem a je zlatou střední cestou mezi oběma.
Volba -bg
Na závěr jsem se rozhodl ještě přidat skriptu přepínač -bg, který otevře prohlížeč hned, jakmile je převedeno
několik prvních stránek. Tak je možné dokument již prohlížet, zatímco se další stránky převádí na pozadí. Tato
šikovná volba vyžaduje ale patch programu fbi. Jedná se o pár změn, které upraví funkci načítání z playlistu
v souboru fbi.c. Zatímco se běžně načítá playlist jen jednou po spuštění programu fbi, u upravné verze se
načítá nový playlist znovu vždy po načtení další stránky. S touto úpravou se také svezla ještě další úprava:
fbi totiž nemá něco jako toggle zoomu – globálně pro všechny obrázky nastavený zoom. Bylo tedy nutné –
v případě úpravy velikosti – měnit velikost u každé stránky, což moc příjemné není. (Aby patch pro zoom
fungoval správně, musí být použito vedle přepínačů -fitwidth a -P také -noreadahead.) Jedná se jen o několik
upravených řádek, které jsou díky tomu však velmi nečistým řešením, při kterém se – v prvním případě – starý
soubor s playlistem maže, a skript vytváří pak nový; program by neměl být používán pro nic jiného, než pro
tento účel. Upravený a přeložený program je třeba přejmenovat na fbi-patch a nakopírovat do adresáře bin;
zdroják je k dispozici na stránkách projektu fbida. Pak už je možné používat ve skriptu cpdf.sh parametr
-bg. (Prohlížeč čeká na převdení prvních čtyř stran; ne však déle jak 10 vteřin; pak končí s chybou. Pokud by
se tedy jednalo o barevné stránky velkého formátu, kde se nestačí první čtyři stránky převést do této doby,
nebude parametr fungovat; musela by se čekací doba ve skriptu upravit. Běžně se čtyři stránky nepřevádí déle
jak dvě vteřiny.)
54
Parametry skriptu
-bg jako i ostatní přepínače se dávají až na konec za jméno pdf souboru případně až za hledaný text. Chtěl
jsem tím usnadnit změnu chování programu jen přidáním přepínače. Stejně tak se zadává až za název souboru
hledaný výraz nebo rozsah zobrazených stran. Skript otestuje, jedná-li se o číslo nebo rozsah a v takovém případě
jej nepoužije jako hledaný text; tak se lze vyhnout následnému dotazu na zobrazené stránky; to však vylučuje
použít za hledaný výraz jen číslo nebo rozsah ve formátu 4-20.
Skript mi nyní slouží také místo dřívějšího pdf-reade.sh. Pokud je tedy spuštěn pod x-serverem, nesnaží
se převádět pdfko na obrázky, ale otevře dokument v zadaném prohlížeči (nastaven xpdf). Tak může vše
fungovat nezávisle na prostředí při asociaci programu například v programech midnight commander, links,
mutt a jiných.
Závěr
Prohlížet pdf dokumenty v konzoli je nyní téměř stejné jako v běžných xkových prohlížečích. – Pohybovat se
v dokumentu lze pomocí kláves PgUP a PgDOWN, konkrétní stránka se zobrazí zadáním čísla a stisknutím g,
zoom se nechá ovládat pomocí +/-, číslo stránky a celkový počet stran je vidět v levém spodním rohu u názvu
souboru. – Vyhledávání v dokumentu je trochu nezvyklé, ale lepší než vůbec žádné nebo jen hledání v textovém
výstupu, jak bývá většinou doporučeno v různých diskusích.
Měla to být jen drobná vylepšení, a nakonec z toho vznikla takováto hrůza, ale i to se někdy stane ;). . . Přání na
závěr: Ať se konečně pdfka v konzoli prohlížejí tak lehce a snadno jako v xkách ;).
Doplněno 3.1.2014: Krátký skriptík mailcapOffice.sh dokáže v konzoli snadno převést dokumenty ve formátech, které lze otevřít v libreoffice, a zobrazí je pomocí cpdf.sh.
30.1.2014
Sed-árna V.
terminal; sed
Tak si myslím, že dozrál čas, abych editor sed více prostudoval; nejednou mi už totiž dobře posloužil a nejednou
jsem tápal, jak jej vlastně správně použít. Dnes to tedy bude především o tom, jak na ty dlouhé zápisy plné písmenek, ze kterých jde na první pohled hlava kolem. Také jsem narazil na několik chyb a nejasností v předchozích
sed-árnách, ty se dnes pokusím vysvětlit a opravit. A konečně na závěr pár praktických sed zápisů.
Rozbor jedné multi-line operace
Mějme zápis z předchozího dílu, který vytiskne všechny řádky mezi vždy dvojicí řádek karel a jirka.
sed ’/karel/,/jirka/{H;/jirka/{x;//{s/\n/#EOL#/g;s/[^#]*#EOL#//;
s/$/#EOL#---/;p}};d};d’
Takový výraz – navíc v jednom řádku – vypadá na první pohled velmi nepřehledně, proto se jej zde pokusím
převést do čitelnější podoby; pomůžeme si analogií k běžné podmínce IF. Záměnu tedy provedeme takto: operace
které opouštějí právě probíhající cyklus (d, D) můžeme nahradit CONTINUE (proč CONTINUE a ne BREAK
bude zřejmé dále u cyklů, kde je popsáno celé chování editoru); tam co není středník (je operace nalepena hned k
filtru) bude IF ... THEN, přičemž celou skupinu operací, které patří do podmínky (k filtru) vymezují složené
závorky. Tímto postupem získáme následující přehlednější zápis:
55
IF /karel/,/jirka/ THEN {
H
IF /jirka/ THEN {
x
IF / / THEN {
s/\n/#EOL#/g
s/[^#]*#EOL#/ /
s/$/#EOL#---/
p
} FI
} FI
CONTINUE (d)
} FI
CONTINUE (d)
První podmínka (multi-line filtr) má za úkol pouštět jen řádky mezi každou dvojicí karel až jirka. Ostatní
řádky se mažou díky d na konci (vše se děje samozřejmě ve streamu řádek po řádku). Řádky vyhovující první
podmínce se vždy ukládají do hold registru s oddělujícím znakem \n; instrukce H a ostatní běžně používané
instrukce jsou popsány v kapitole 3.6 v manuálu.
Další podmínka obsahuje filtr, který propustí pouze řádek s jirka, tedy vždy poslední řádek v každém vyhovujícím rozsahu karel až jirka, ostatní se maže, cykl se opouští a začíná se nový; řádky už propustit na
výstup dál nepotřebujeme, protože jakmile dosáhneme řádku s jirka, jsou všechny – pro jeden rozsah karel až
jirka – v hold registru. Každý řádek se slovem jirka, který se v textu vyskytuje, tedy slouží jako spoušť pro
vytištění obsahu z hold registru – nejpve se přesune obsah z hold registru do pattern registru pomocí x
(operace oba registry prohodí) a pak už by se mohlo vše vytisknout. Zde je však ještě poslední podmínka: ta
vznikla v předchozím díle mojí chybou. Je totiž zcela zbytečná (projde vždy), jak si ještě ukážeme. (Stejně tak
i substituce mohou být provedeny jednodušeji.) – Každopádně se v podmínce musí obsah z pattern registru
vytisknout pomocí p. Tak se děje pro všechny skupiny v rozsahu karel až nejbližší jirka, dokud není dosaženo
konce souboru (nebo konce přicházejícího přes stdin).
sed vždy každý řádek zbaví ukončovacího znaku
na výstupu sed opět znak konce řádku přidá.
\n před tím, než si jej uloží do pattern registru;
d opustí stávající cyklus a začne další (nic se netiskne), D se chová zcela vyjímečně (dále bude
vysvětleno)
H řádky uvnitř hold registru ”akumuluje” za sebe, a vkládá znak \n vždy před tím než přidá další
řádek z pattern registru; bratříčkem velkého H je malé h, které ”neakumuluje”, ale jen překopíruje
(tudíž přepíše)
znaky ^$ znamenají začátek a konec řádku; $ znamená ale také konec souboru, záleží na způsobu
použití; například: rozsah 10,${. . .}omezuje operace v závorce pro řádky v rozsahu 10 až konec
souboru. Pokud je však znak uveden mezi lomítky /. . ./ (ať už se jedná o filtr nebo substituci),
je to vždy konec řádku, respektive konec obsahu v pattern registru. Pozor při uzavírání výrazu do dvojitých
uvozovek (třeba z důvodu použitých proměnných): v takovém případě je potřeba ukončovací znak $ opatřtit
zpětným lomítkem $, jinak bash uvidí ve znaku proměnnou a pokusí se ji použít.
Zpět k příkladu a zbytečné poslední podmínce, místo které by mohlo být /.*/ nebo také nic (jen složené závorky)
a výsledek by byl stejný. Podmínka projde vždy a je tudíž zbytečná. Také tři substituce jsou zde zbytečné, a
mohou být nahrazeny jen jednou takto:
56
s/[^\n]*\n//p
Pokud bychom ale substituci vůbec nepoužili a místo ní dali jen p, viděli bychom, že se zas tak moc nestane –
jen řádek s jirka bude vytištěn dvakrát. Proč a kde se bere tento řádek navíc?. – Jakmile prohodíme obsahy
registrů (pomocí x), nebude hold registr prázdný, ale překopíruje se do něj obsah z pattern registru, kde se
nachází řádek se vzorem jirka. Mazat registry bez opuštění cyklu není běžná operace: mohlo by to jít pomocí
z, ale je to speciální věc GNU. Raději si tedy pomůžeme jiným způsobem: prostě necháme tento řádek v hold
registru a před vytištěním vždycky tento kousek smažeme. Nepotřebného kousku v pattern registru se zbavíme
tak, že smažeme začátek až po první znak \n včetně; jak to dělá uvedená substituce. Správně tedy měl výraz
vypadat takto:
sed ’/karel/,/jirka/{H;/jirka/{x;s/[^\n]*\n//;p};d};d
Mých několik substitucí dělá vlastně to samé, jen s tím rozdílem, že si znaky \n nejprve nahradím značkou
#EOL#, pak onu část až k první značce odstraním a navíc si ještě na konec vložím pár pomlček. Celý výstup
je pak vždy na jednom řádku s odělovači #EOL#, přičemž odřádkovány jsou celé bloky (odřádkuje se vždy
až za jirkou – jedná se o znak, který sed sám přidá na výstupu). Teprve až dalším sed jsem značky #EOL#
nahradil za normální znak konce řádku. Tento způsob není marný: použil jsem jej z toho důvodu, abych mohl
jednotlivé řádky, pomocí programu tac prohodit, čímž dostaneme prohozené pořadí bloků; v mém případě jsem
prohazoval články tohoto blogu, jak jsem také psal v předchozím díle. Tím vlastně vzniklo ono zesložitění v
podobě třech substitucí tam, kde být vůbec nemusely. A když už jsem se dostal k příkladu s prohozením řádků
tj. poslední z předchozího dílu, tak ten se liší jen přidáním h, jenž zapisuje (nepřidává, ale vždy přepisuje) do
hold registru v době, kdy se ještě registr jako ”akumulátor” nepoužívá. To způsobí, že jakmile se začne hold
registr používat, je v něm už jeden předchozí řádek uložený. Ten pro tento záměr ponecháme – nebudeme jej
mazat z pattern registru. Spolu s prohozením řádků tak mohu krásně a rychle prohodit pořadí článků, které
mají nadpis vždy podtržený pomlčkami a jsou zakončené datumem.
místo karel a jirka, lze samozřejmě používat regulární výrazy, čísla řádků nebo i další speciality,
jako počet řádků od nalezeného prvního výrazu nebo jejich násobek; více v manuálu v kapitole 3.2
na konci; také je možné použít \| pro více výrazů např: /karel\|pepa/,/jirka/ bude akceptován
řádek s karel ale také pepa – ten z nich, který bude dřív ”vyhraje” a vytvoří rozsah řádek až po
jirka
pozor: jak už jsem psal v předchozím díle v dodatku o multi-line filtru: pokud nebude poslední
v dokumentu jirka, pak projdou filtrem řádky od posledního karla až do konce souboru; pokud
třeba provedeme seq 1 20 | sed -n ’/5/,/10/p’ a očekáváme, že se vytisknou jen řádky od čísla
5 až po 10, pak budeme nemile překvapeni, neboť se vytisknou řádky s čísly 5 až 10 a také 15 až
20.
Dlouho mi nebylo jasné, kdy vlastně používat option -n a kdy ne. Již jsem psal ve třetím díle,
že tímto přepínačem se změní chování tak, že se jakoby stream zastaví a pustí se jen tam, kde
použijeme p (nebo P). Domníval jsem se, že nejčastěji tak provádíme tehdy, pokud data ukládáme
do registru a pak je potřebujeme v určitý moment vytisknout najednou. Na uvedeném příkladu je zřejmé, že
přepínač není používán, i přesto že operujeme s registry. Navíc u prvního příkladu v předchozím díle pro změnu
být přepínač musí. Pokud by to někomu vrtalo hlavou – jako mně –, pak je to tedy takhle: pokud nepoužijeme
přepínač, musíme nežádoucí stream odstranit pomocí d; ve skutečnosti tím smažeme pattern registr, vystoupíme z právě probíhajícího cyklu a začneme novej, takže se nedosáhne konce, aby mohlo k jakémukoliv vytištění
dojít. Pokud přepínač použijeme, pak můžeme d vypustit, protože dosažením konce se nic nevytiskne. Obdobně
u zmíněného příkazu z předchozího dílu:
sed
-n ’/karel/,/jirka/p’
můžeme -n vynechat, pokud odtékající stream umažeme (pokud následujou ještě další operace, musíme myslet
na to, že už se neprovedou). Přidáme tedy d za středník:
sed ’/karel/,/jirka/p;d’
57
Jedině tak může vše správně fungovat podle schématu:
IF /karel/,/jirka/ THEN {
p
} FI
CONTINUE (d)
Asi nejsnažší způsob jak vyzkouše přepínač -n, je použít jen jedinou instrukci p. Jakmile -n nepoužijeme,
vytiskne se každý řádek dvakrát: jednou díky p a podruhé díky tomu, že se dosáhne při zpracování instrukcí
konce; p totiž neopouští cyklus!. Zabránit v dosažení konce výrazu můžeme jedině přidáním d (za středník), a
nebo použitím přepínače -n, který funkci ”tiskni při dosažení konce” vypne.
Vzhledem k tomu, že d je vlastně jediná instrukce (nepočítám D), která opouští stávající cyklus, bývá mnohdy
smyslem jejího použití hlavně potřeba vynechat následující operace. Jinou možnost, kterou by bylo například
možné vytvořit něco jako ELSE sed nemá a psát ke každé instrukci filtr, by bylo neefektivní a zbytečně by to
operace zpomalilo.
Cykly
Ve třetím díle jsem uvedl příklad, který odstraní ukončovací znaky na konci řádku.
sed ’:a;N;$!ba;s/\n//g’
Dlouho jsem nevěděl, proč zápis nefunguje správně, až jsem si po dlouhé době všiml, že mi tam chybí znak
$ (konec souboru). Není tomu tak jak jsem se domníval, že když nastane konec souboru opouští se cyklus
automaticky a pokračuje se až na konec, čímž se pak vše vytiskne; podmínka konce souboru tam být musí! To
byl první impuls k tomu, že nějaký zjednodušující přepis musím zavést. – Návěští a: tedy zapíšeme jako DO,
pak jen jedna instrukce N a následuje konec cyklu ba, u kterého nejspíš bude vždy nějaký filtr (zde $! znamená
– není-li konec souboru) – ba zapíšeme pomocí WHILE $! DONE. Za cyklem už následuje středník a běžná
substituce. Přepis pak vypadá následovně:
DO
N
WHILE $! DONE
s/\n//g
Až do konce souboru se postupně řádky přidávají do pattern registru v cyklu. Poté se z
odstraní všechny znaky \n. Výsledkem je spojení všech řádků v jeden. A nyní složitější výraz:
pattern registru
sed ’:a;$!{;N;/\n$/!ba};s/\n//g;s/$/-/’
V cyklu zde máme jinou podmínku \n$ – znamená prázdný řádek (plus negace výrazu). Také za návěštím
není hned operace N, ale je zde filtr (podmínka) $! (není-li konec souboru). Ten už jsme si popsali na začátku. Jen jedna specialita se zde nachází: závorka až za cyklem. Tento fenomén snadno přepíšeme pomocí
ELSE a BREAK:
DO
IF $! THEN {
N
ELSE
BREAK
} FI
WHILE /\n$/! DONE
s/\n//g
58
Narozdíl od předchozího zápisu zde cyklus končí, jakmile se narazí na prázdný řádek \n$ (opravdu prázdný
– takový, který obsahuje jen jeden znak odřádkování). Po odstranění znaků \n se obsah z registru vytiskne.
Tím však program sed nekončí: jen se začne znovu s dalším řádkem; tak to jde až do konce souboru $!, kdy
se musí vystoupit – v tomto případě – z cyklu pomocí BREAK v podmínce. Abychom viděli, kde program
všude vystoupí z cyklu, můžeme přidat ještě jednu substituci s/$/-/ (znak konce $, který je na konci v pattern
registru se nahradí viditelnou pomlčkou). Pro přesné popsání celého procesu, by bylo nutné vždy do schématu
zanést také operace, které sed vykoná bez našeho zásahu. Nad operacemi totiž běží ještě jedna smyčka, kterou
je třeba si představit následovně:
......... sed internal...,..........
WHILE $! DO;
READ (n) .... read line, remove EOL and paste to //pattern// reg. (
only D suppress this line)
....................................
DO
IF 2,8 THEN {
N .... (add \n and read and add next line to //pattern// register)
ELSE
BREAK
} FI
WHILE $! DONE
s/\n//g
..........sed internal .............
PRINT (p) .... add EOL and print content of //pattern// register (
option //-n// suppress this line)
DONE
....................................
Ze schématu je zde konečně zřejmé, proč jsme d na začátku nahradili CONTINUE a ne BREAK.
Hlavní smyčka totiž musí běžet až do posledního řádku souboru. Vystoupit z hlavní smyčky a
ukončit tak program se nechá pomocí q nebo Q; místo nich bychom tedy mohli i BREAK používat,
pokud se nebude jednat o další podsmyčku, jako zde; lépe tedy bude pro výstup z hlavní smyčky
používat jednoznačný EXIT.
Tento jen mírně upravený předchozí zápis způsobí, že se spojí řádky 2 až 9. Pokud totiž není splněna podmínka
rozsahu 2 až 8, vystoupí BREAK vždy z cyklu a vytiskne se jen ten jeden právě načtený řádek. (Jak jsme už
zmínil, sed zbaví řádek – při načtení – zakončovacího znaku \n, a teprve až těsně před vytisknutím jej zase
přidá. Pro uvedenou substituci není v takovém případě – kdy je v pattern registru jen jeden řádek – nic, co by
se zde dalo nahradit.) Celý spustitelný výraz vypadá následovně:
sed ’:a;2,8{;N;$!ba};s/\n//g’
(Místo čísla konkrétních řádků bychom mohli použit také výraz např. jirka, karel.)
Na závěr ukáži, jak spojit řádky jiným způsobem, než použitím cyklu; stejně se nám řádky načítají v hlavní
smyčce, jak jsme si právě ukázali. Můžeme tedy použít jen hold registr:
sed -n ’H;g;$s/\n//gp’
Zde tvoří H;g funkční ”akumulátor”. Každý nový řádek se přidá do hold registru a ten se vždy zkopíruje do
pattern registru. Na konci souboru $ se provede substituce, kde se nahradí všechny přidané znaky ’\n’ a vše se
vytiskne. Tato ukázka je základ pro vytvoření tzv. klouzavého okna (sliding-window), o kterém bude psáno dále.
Hold registr můžeme ale vhodným způsobem naplnit také obráceně (prohodíme řádky).
sed -n ’G;h;x;$s/\n//gp’
59
Jen místo H;g použijeme G;h;x. Prázdný řádek na konci (pokud nespojíme řádky) můžeme odstranit třeba
substitucí s/\n$//. – Nakonec jsem v manuálu narazil na vzhlednější zápis, u kterého není nutná substituce.
sed -n ’1!G;$p;h’
z přepisu by mělo být vše jasné
IF 1! THEN
G
FI
IF $ THEN
p
FI
h
Možností je tedy vždy několik; některé horší, jiné lepší.
G a g fungují stejně jako H a h, jen v opačném směru tj. z hold registru do pattern registru.
Klouzavé okno
Snad se dá klouzavým oknem nahradit anglický výraz sliding window; mně se osobně klouzavé okno líbí,
tak budu tento výraz dále používat. Následující příklad jsem si půjčil z manuálových stránek. (Zápis funguje
s nastavenou volbou -n.). Ukazuje, jak lze vytisknou posledních deset řádek dokumentu; což je u sed trochu
složitější než třeba prvních deset (1,10p).
1! {; H; g; }
1,10 !s/[^\n]*\n//
$p
h
Příklady jsou často psané jako skript na několika řádcích. Odřádkování ve skriptu ale není nic jiného, než
středník u zápisu v jednom řádku. Trochu matoucí může být vykřičník u substituce, který ale patří k filtru 1,10,
tudíž substituce se provede pro všechny řádky kromě 1 až 10.
IF 1! THEN {
H
g
}FI
IF 1,10 ! THEN
s/[^\n]*\n//
FI
IF $ THEN
p
FI
h
Pro snadné pochopení můžeme zatím klidně ignorovat první podmínku, která vynechá první řádek. Pokud H a g
použijeme bez podmínky, bude vytištěno jen místo deseti jedenáct posledních řádek. Vše tedy funguje takto: do
hold registru se vždy přidá obsah z pattern registru (nový řádek), a pak se obsah z hold registru překopíruje
do pattern registru; to má na svědomí H;g. Následující podmínky neprojdou, díky čemuž se provede pouze h
na konci, které zpět překopíruje obsah z pattern registru do hold registru.
60
N a n čtou nový řádek a zapisují jej do pattern registru; N do pattern registru následující řádek
přidává za \n, zatímco malé n jen následující řádek zkopíruje (na začátku cyklu je v pattern
registru vždy nový řádek, který si načetl editor sám viz také uvedené schéma celého procesu)
Až do desátého řádku je tento krok zbytečný, protože se bude kopírovat stejný obsah tam a pak zpět. Od
desátého řádku ale začne fungovat substituce, která umaže vždy začínající část v pattern registru až po první
znak \n (tj. smaže první řádek v pattern registru). Od toho okamžiku má h na konci smysl, protože do hold
registru nakopíruje obsah upravený. O této chvíle s přidáním vždy dalšího řádku do hold registru zároveň
jeden ubude. Jakmile je dosaženo konce, provede se poslední překopírování z hold registru do pattern registru,
poslední substituce a obsah z pattern registru – posledních deset řádek – se vytiskne. Po celou dobu se tedy
udržuje v hold registru jen deset posledních řádek; proto název klouzavé okno. A nyní proč je zde v první
podmínce vynechán první řádek. H totiž nejprve přidá znak \n, a až po té obsah z pattern registru s prvním
řádkem. Zatímco pokud se první řádek zapíše do hold registru pomocí h, nebude na začátku znak \n. Jakmile
pak začne od desátého řádku substituce umazávat tuto část až po znak \n, maže se tím první řádek. Pokud
by ale byl první řádek přidán pomocí H, byl by umazán první znak \n, a teprve až v jedenáctém řádku by se
umazal první řádek v pattern registru.
Substituce zde dělá to samé, co D, jen s tím rozdílem, že D proces opustí tj. nepokračovalo by se na další
podmínky, ale šlo by se opět na začátek s načteným novým řádkem. My ale potřebujeme ještě provést h a na
závěr pak p; takže bez substituce to zde nejde.
instrukce, které ukončí celý cyklus (tedy i program sed) jsou Q a q: q vytiskne před ukončením
obsah v pattern registru, zatímco Q nic netiskne; obě instrukce stojí vždy osamoceně (bez filtru či
substituce); v přepisu bychom je mohli nahradit EXIT
pattern registr je v hlavní smyčce vždy automaticky vymazán a uložen do něj nový načtený řádek
(proto se musí ukládat do hold registru). Pouze instrukce D tento princip porušuje: D umaže část
pattern registru až po první \n, opouští cyklus (stejně jako malé d), přičemž zabrání automatickému vymazání pattern registru a také načtení dalšího řádku (jenž se jinak děje automaticky). Pokud bychom
však použili D uvnitř dalšího cyklu – jak bylo už popsáno – pak D jen umaže první část z pattern registu (až po
první znak \n) a začne další cyklus (dál se nepokračuje – jakoby se provedl příkaz CONTINUE a ne BREAK
– stejně jako u d).
Klouzavé okno je možné udělat i následujícím způsobem – pomocí zmíněného registru D (pozn. zde není použita volba -n).
1h
2,10 {; H; g; }
$q
1,9d
N
D
Tentokrát bude lépe, začít s vysvětlováním od konce. Po tom, co jsem si napsali o funkci D, je už asi zřejmé,
že N;D společně budou vždy do pattern registru ukládat další a mazat nejstarší řádek. Pokud s tím začneme
třeba u desátého řádku a předchozí už budou v registru uloženy, budeme mít po celou dobu v pattern registru
deset řádků, které se na konec vytisknou. K tomuto posunutému začátku je zde předchozí podmínka, která
maže řádky 1 až 9 v pattern registru a také v tomto místě končí (d opouští cyklus). První řádky jsou pouhý
akumulátor, kde se naplní hold a pattern registr až po řádek deset. Jakmile se přestane pattern registr
mazat (od řádku 9), začne se do něj vždy přidávat jeden nový řádek pomocí N a jeden – ten nejstarší – se bude
umazávat pomocí D. Tak to jde až do konce souboru, kde cyklus skončí na $q (q před tím než ukončí vytiskne
obsah z pattern registru).
61
Praktické zápisy:
Již několikrát jsem potřeboval zbavit české znaky diakritiky, nebo převést některé znaky v textu na jiné. Program
tr bohužel nezvládá dobře vícebajtové znaky, a tak mi nezbylo než si pro tuhle věc udělat krátký prográmek,
který ale nebyl extra rychlý. Když jsem narazil v jednom článku na výraz se sed, tak jsem zajásal: stačilo totiž
napsat jeden řádek a mohl jsem svůj starý program v bashi hodit za hlavu:
sed ’y/\n+)( řížéščýáěňťďůúȞҊÚÍŘ ŤŽŠABCDEFGHIJKLMNOPQRSTUVWXYZ\
"/ ____rizescyaentduucznsuirdtzsabcdefghijklmnopqrstuvwxyz_/’
Samozřejmě musí sedět počty znaků, jinak bude sed křičet. (Velká písmena na malá by se nechala převést nejspíš
efektivněji, ale já to mám raději takto pohromadě.) A nyní opravdu velká radost ;):
sed ’s/^\(.*\)pcblog@\(.*\)#\(.*\)$/echo \"\1pcblog-‘grep "@\2 *$
" \/tmp\/pisalek-years.tmp‘#\3\"/e’
Jedná se na první pohled o pouhou substituci, ve které je spuštěn externí program; psal jsem o ní ve třetím
díle, kde byla uvedena jako snadný způsob přečíslování (s inkrementací). Tentokrát jsem namísto programu
pro počítání zavolal grep, ten v jiném souboru najde potřebný výraz, který se má na dané místo dosadit; pak
se provede substituce. Nebýt tohoto vtipného řešení, musel bych použít v bashi dvě smyčky: první, která by
vždy vyhledala vzor v textu a druhou, která by jiný vzor v nalezeném řádku vyhledala v souboru a pak jej
použila pro nahrazení vzoru v textu. To by ale byla v bashi velmi pomalá operace a navíc na několik řádek.
(Doplněno 12.3.2014: S použitím spustitelného příkazu uvnitř sed nebývá o překvapení nouze. Dosud se
mi například nepodařilo u uvedeného výrazu nahradit pcblog proměnnou bashe. Uzavření celého výrazu do
dvojitých uvozovek totiž způsobí, že grep ”@\2 *$” se neprovede správně; konkrétně není předána hodnota
uložená v \2.) – Nakonec se přece podařilo. Řešení jsem nalezl zde v kapitole Passing arguments into a
sed script: proměnnou je možné dát do dvojitých a jednoduchých uvozovek, přičemž celý výraz sed necháme v
jednoduchých uvozovkách. Předchozí výraz pak s proměnnou vypadá následovně:
sed ’s/^\(.*\)’"$BLOG_NAME"’@\(.*\)#\(.*\)$/echo \"\1’"$BLOG_NAME
"’-‘grep "@\2 *$" \/tmp\/pisalek-years.tmp‘#\3\"/e’
modifikátor e u substituce funguje tak, že se celý výraz mezi lomítky nahrazujícího výrazu rozvine
jako příkaz bashe, a poté co se vykoná, vloží se jeho výstup na jeho místo jako výraz, se kterým
se substituce provede ( třeba takto: sed ”s/.*řádek pro datum.*/date/e”); proti veškerému
očekávní se zde musí použít hvězdičky před a za, aby se obsáhl celý pattern register; jen samotný výraz
nahradit nejde a bash hlásí chybu?! ); mezi lomítky už – kromě příkazu – nesmí být nic jiného, tudíž pokud
chceme zachovat část textu a nahradit zbylou část (běžně se dělá pomocí registrů \1,\2,. . případně \& ),
pomůžeme si příkazem echo třeba takto ”s/^\(.*Datum: \).*/echo ’\1 ‘date‘)’)/e” – vloží za Date:
datum; pozor na zvláštní chování p spolu s e; v takovém případě se p chová odlišně před a za e (popsáno v
manuálu v kapitole 3.5)
Doplněno 2.10.2014: Použití e ke spuštění příkazu v shellu není ve skutečnosti tak složité, jak jsem se
doposud domníval. Stačí jen správně z manuálu pochopit princip, který je popsaný jen velmi stručně: sed
nedělá nic jiného, než že se pokusí v shellu spustit obsah v pattern registru. Substitucí si tedy vložíme do
pattern registru potřebné příkazy s parametry a e za substitucí zajistí, že obsah v pattern registru se
provede jako příkaz shellu. Výstup se pak uloží do pattern reg. (původní obsah se přepíše), kde si jej můžem
vyzvednout. Myslet musíme na znaky, které se v shellu rozvinou, a pokud tomu chceme zabránit, musíme text
za příkazem v pattern registru uzavřít do jednoduchých a pak do složených závorek ”’. . .’” . Například pro
dekódování textu mailu v kódování base64, který máme už načtený v pattern reg., přidáme zmíněné uvozovky
(v textu totiž může být například $1 aj.) a pak přidáme na začátek echo a na konec | base64 -d.
sed "s/^/\"’/ ; s/\$/’\"/ ; s/.*/echo \"&\" | base64 -d/e"
62
Dnes, kdy tento díl píši, jsem si usmyslel, že vylepším chování prohlížeče fbi v konzoli pod midnight-commanderem.
Ten totiž otevře v prohlížeči jen obrázek, který je právě vybrán, ale já bych chtěl, aby se nechalo listovat v
prohlížeči dál po obrázcích, které jsou v adresáři. Nechá se to udělat tak, že prohlížeči fbi předám playlist se
všemi seřazenými obrázky v adresáři, přičemž musí být první ten, který je označený (předává se jako parametr).
V praxi to znamená dát všechny řádky na začátku – až po ten, který obsahuje vybraný soubor – na konec. A
zde je už celý zápis:
find . -type f -maxdepth 1 | grep -i "jpg$\|jpeg$\|tiff$\|png$\|p
nm$\|bmp$\|gif$" | sort | sed -n "/${image}/,\${p;\${x;s/[^\n]*\n//
p};d};H" | sed ’s/:$//’
Program find prohledá celý adresář; grep vybere jen soubory s vhodnou příponou; sort je srovná podle jména;
a konečně sed, který funguje následovně:
IF /scan00050.png/,$ THEN {
p
IF $ THEN {
x
s/[^\n]*\n//p
} FI
CONTINUE (d)
} FI
H
Až do řádku se jménem souboru – například scan00050.png – se provádí jen operace H (přidává do hold
registru a nic se netiskne (použitý přepínač -n). Jakmile je podmínka splněna (pro zbylé řádky až do konce),
tisknou se řádky pomocí p a následně se cyklus opouští díky d (aby už nemohla probíhat instrukce H). Podmínka
uvnitř čeká na konec souboru. Jakmile je podmínka splněna, je již vytištěna druhá polovina všech řádků a zbývá
tedy vytisknout první část z hold registru. Jelikož jsme začali s ukládáním řádků pomocí H (a ne h), máme
uložen jeden prázdný řádek, který substitucí odmázneme. Cyklus následně vystoupí na d, a je hotovo. Stačí už
jen tento výstup uložený do souboru předat prohlížeči fbi jako playlist – fbi -l soubor.txt.
p za substitucí tiskne jen tehdy, pokud byla substituce úspěšná (je to stejné jako u filtru); pokud
nám sed se substitucí – k našemu překvapení – vytiskne vše (a navíc nahrazený řádek dvakrát), pak
je to tím, že dosáhne také konce a není použita volba -n; pokud chceme vytisknout jen ten jeden
řádek se substitucí, musíme použít přepínač -n, nebo pomocí d výstup před koncem umazat (zablokovat). (p
za substitucí není vlastně instrukce, ale jen modifikátor – v manuálu se často uvádí jako flag nebo přepínač –
ovlivňující chování výrazu substituce – je popsáno zde v kapitole 3.1.3 {za povšimnutí stojí také modifikátor I
- case-insensitive}; proto u substituce neplatí stejná pravidla spojování instrukcí jako u filtru.) – Tohle bývá
většinou první překvapení začátečníka, který prvně použil substituci v editoru sed spolu s p ;).
Závěr:
Mnohé ukázkové příklady nemají příliš velkou šanci na reálné použití. Často nás totiž napadne jednodušší
způsob bez toho, abychom si lámali hlavu nad složitým výrazem. Přesto se však najdou situace, kdy se nám
může sed hodit; většinou se už nejedná o triviální věci a nebýt sed, museli bychom vytvořit program v perlu
nebo pythonu, pak se ale stává řešení hůře přenositelné a řekl bych, že také pomalejší; často se nechá provést vše v bashi, ovšem pokud nás netrápí rychlost – práce s textem a smyčky v bashi nepatří zrovna mezi
rychlé operace.
Sám už mám několik skriptů, kde mi sed prokázal dobrou službu; nepočítám samozřejmě běžné substituce,
které mám snad v každém desátém řádku. Několik zajímavých praktických ukázek s multi-line operacemi jsem
nedávno našel na zajímavé internetové stránce.
63
sed je skvělý editor, který je navržen tak, aby s minimem udělal maximum. Tím minimem jsou zde dva registry
a několik operací, se kterými se nechá všelijak kouzlit – člověk se až diví, co všechno se nechá vykouzlit, když
se správně poskládá několik písmenek.
Zapomenutý přepínač -e (Doplněno 18.3.1014):
Option -e byl pro mne dlouhou dobu trochu záhadou. Z manuálu je celkem jasné, že se jedná o volbu, za níž
následuje výraz v uvozovkách, přičemž pokud je použit jen jeden výraz pak se -e zadávat nemusí (nejčastější
případ). Nic nám však nebrání v tom použít i několik výrazů například následovně:
seq 10 19 | sed -e ’s/1//g’ -ne ’:a;N;$!ba;s/\n/-/g;p’
A hle, výsledek není stejný jako v případě, kdy použijeme dva sed a rouru takto:
seq 10 19 | sed ’s/1//g’ | sed ’:a;N;$!ba;s/\n/-/g;p’
Až v druhém případě proběhlo vše podle mého očekávání, kdy čísla 10 až 19 jsou zbavena jedničky, a teprve
po té jsou řádky spojeny druhým výrazem. Výsledek pak vypadá následovně: 0–2-3-4-5-6-7-8-9. Namísto
toho u prvního zápisu s -e dostaneme tento výstup: 0-11-12-13-14-15-16-17-18-19. První výraz se zde tedy
provede pouze u prvního řádku s číslem 10; dál už se neprovádí, protože druhý výraz obsahuje smyčku, která čte
až do konce souboru (díky N) a zpracuje tedy všechny řádky bez toho, aby se mohl sed dostat znovu k prvnímu
výrazu. S pomocí -e tedy nelze nahradit rouru mezi dvěma sedy, jak jsem se dlouho domníval; -e nefunguje tak,
že by se nejprve zpracoval kompletně první a pak až druhý výraz; ale i zde jde vše ve streamu řádek po řádku!
Místo -e můžeme výrazy oddělit středníkem (nebo v sed skriptu novým řádkem):
seq 10 19 | sed -n ’s/1//g ; :a;N;$!ba;s/\n/-/g;p’
a výsledek bude stejný jako s -e. Jediné plus, které v použití s -e vidím, je možnost používat na různých místech
-n – nebo i jiné přepínače – pro jednotlivé výrazy.
Na stránce v kapitole 3.1.4 jsem se následně dočetl, že středníkem nelze oddělit všechny instrukce, a proto
se používá -e. Jenže v mém případě lze středníkem oddělovat – jak jsme si právě ukázali – i problémové
instrukce skoku a návěští. Možná tedy může mít -e větší význam u některých verzí programu sed, kde středník nefunguje.
seq 10 19 | sed -ne ’s/1//g’ -e ’:a’ -e ’N;$!ba;s/\n/-/g;p’
(Po čase jsem pochopil, že -e může dlouhé jednořádkové výrazy více zpřehlednit; pokud místo středníku použijeme -e k logickému rozdělení více výrazů. -e má tedy smysl nejen tam kde být musí: například u předchozího
příkladu pro jiné verze sed než GNU.)
Doplněno 18.4.2014 (oprava, upřesnění): n se chová trochu jinak, než čistě analogicky k N, tak jak je
tomu u H, h a G, g. Podle této analogie by totiž n mělo pouze číst nový řádek a zapsat jej do pattern registru
(neboť N čte a přidává obsah do pattern registru za separátor \n). Tak jsem si to také dlouhou dobu myslel.
n se však chová následovně: nejprve vytiskne obsah z pattern registru, a až poté se registr smaže a zapíše do
něj nový řádek (dále se pak pokračuje ve vykonání dalších instrukcí)! Pokud je však použit přepínač -n, tak se
nic tisknout nebude; v tomto případě se n bude chovat již podle očekávání. Přehledně znázorněné je chování n,
(ale i dalších instrukcí) v tabulce zde (na uvedených stránkách autora je také pěkný seriál o editoru sed, který
stojí za přečtení).
Doplněno 9.9.2014: Znovu jsem se přesvědčil o nutnosti mít na paměti, co se stane při značce EOF (konci
souboru). Například sed ’/plain/{N;N;N;N;s/foo/new/}’ má od řádku s plain načíst další čtyři řádky
a v nich provést substituci. Jakmile ale do konce zbývají už jen tři řádky, pak při provádění posledního N se
64
sed ukončí a substituce se u načtených třech řádků neprovede. Proto musíme použít podmínku testující EOF
u každého N takto: sed ’/plain/{$!N;$!N;$!N;$!N;s/foo/new/}’. Obdobně je třeba na konec souboru
myslet i u cyků.
Doplněno 10.9.2014: Dnes jsem narazil na prolém s koncem řádku CRLF. Potřeboval jsem detekovat výskyt
prázdného řádku. Podmínka /^[ \t]*$/ v takovém případě nefunguje, protože, i když bude řádek prázdný,
bude v něm vždy před koncem znak ^M (\r), který se načtením do pattern registru neodstraní (jako vstupní
separátor). Tuto skutečnost je třeba mít na paměti a znak z pattern reg. před tím umazat, pokud potřebujeme
dále testovat konec řádky jako v tomto případě. Pokud znak chceme zachovat, musíme všechny podmínky
a substituce používající konec bufferu nebo separátor (\n) opatřit před tím znakem CR \r*, který se tam
může vyskytovat.
Doplněno 12.9.2014: Pokud má být zpracován soubor jiného kódování, je třeba nastavit na toto kódování
shell, ve kterém je sed spuštěn; jinak sed některé znaky nerozpozná; např. LANG=iso8859-2. – Nejlepším
řešením je nastavit LANG=C.
Ostatní díly:
’ I.díl: Sed-árna I.
’ II.díl: Sed-árna II.
’ III.díl: Sed-árna III.
’ IV.díl: Sed-árna IV.
23.1.2014
Kopírování II
bash; terminal; vim
Se skriptem copycon.sh jsem se – za tu dobu co jej používám – docela sžil. Je to pokrok, když člověk nemusí
přemýšlet nad tím, co kam přesměruje :). Tedy ne že bych to nezvládal, ale to už má člověk nějak v krvi, že musí
furt do něčeho vrtat a něco zlepšovat. A já v tom nejsem jinej. Takže je druhý díl kopírování :).
Hned na začátku musím zmínit, že jsem dal do repozitáře novou verzi copycon.sh, ve které zmizely různé
zbytečnosti a byly odstraněny chybky; také help přibyl. Novou funkcí je načtení obsahu ze souboru. Při troše
štěstí je to verze finální, protože nemám tušení, co bych mohl dále vylepšit.
A nyní k tomu hlavnímu. Tím je můj první skript pro vim editor. V předchozím díle jsem psal o tom, jak se
nechá použít copycon.sh v editoru v konzoli, díky čemuž lze pak kopírovat všemi směry do různých konzolí
včetně spuštěného x-serveru. Časem se ukázalo, že zmiňovaný postup není moc pohodlný; ač je to o něco
lepší než bez copycon.sh. Zejména kopírování textu do příkazového řádku editoru vim, kde se pak spustí
copycon.sh. U krátkého textu je to sranda, ale několik řádek dělá věc značně nepohodlnou a nepřehlednou, v
tom jednom příkazovém řádku. Také je potřeba odstranit zakončovací znaky (znak ^M) a různé znaky opatřit
zpětným lomítkem, aby bylo vůbec možné text v uvozovkách předat skriptu; prostě velmi nepohodlná až otravná
záležitost. A tak jsem se na vyřešení tohoto problému docela těšil; a výsledek je tu – plugin copycon.vim.
Plugin obsahuje dvě funkce: první pro předávání označeného textu skriptu copycon.sh a druhou pro vkládání
výstupu ze skriptu do textu v editoru. S okomentováním jsem si dal tentokrát práci, a tak je snad z krátkého
skriptu vše zřejmé. Cesta k jednoduchosti vedla však přes mírně trnité houštiny. Nejprve jsem byl samozřejmě
trochu zaskočen proměnnou typu lists, která je ve skutečnosti jen polem, nezvyklou prací u proměnných typu
options (u columns), nebo potřebou obnovit obrazovku v případě nestandardní nastavené šířky. Avšak to byly
jen školácké nesnáze začátečníka, se kterými se počítá. Tu největší obtíž mi přichystalo předávání textu programu
65
copycon.sh přes argument (případně stdin). Tím jsem si přidělal mnoho práce, neboť následná expanze znaků
v uvozovkách vyžaduje, aby bylo provedeno před tím několik substitucí speciálních znaků.
Bohužel, nebo možná bohudík, jsem po té, co bylo vše hotové, narazil na článek, ve kterém bylo popsáno, jak
předávat programu xsel soubor namísto řetězce. Rychle jsem tedy upravil skript copycon.sh tak, aby mu bylo
možné předat soubor, v pluginu jsem vymazal substituce, plus pár úprav, aby se text ukládal do souboru, jenž
se pak předá skriptu copycon.sh. Výsledkem je uvedený plugin v již zmíněném odkazu. Pro možné budoucí
využití vkládám také první překonanou verzi jako verzi jedna.
Koukám, že jsem ani nenapsal, jak se kopírování provádí. Je to tak snadné, že jsem na to ani nevzpomněl.
Prostě se text ve visual mode označí a stiskem sy uloží; pak můžeme třeba v jiné konzoli v editoru vložit
obsah pomocí sp. Pokud je spuštěný x-server – používá se clipboard jako místo pro uložení obsahu – je
možné bez problémů vkládat (nebo naopak kopírovat) pomocí Shift+Ins (nebo opačně Ctr+Ins) také do(z)
okenních programů.
Závěr:
Nyní už je kopírování v konzoli opravdu snadné. Jediné co občas dokáže pocuchat nervy jsou speciální znaky
u výrazů předávaných z příkazové řádky. Programy awk, sed, nebo regulární výrazy se jimi doslova hemží.
Při vkládání do uvozovek a předávání programu copycon.sh je potřeba všechna taková místa opatřit zpětným
lomítkem; což u dlouhých výrazů není zrovna zábavné a většinou i na mnohé zapomeneme; pak se divíme, že
nám to ve skriptu nefunguje stejně – to je klasická situace. Tady jsem objevil jen malý zlepšovák: Uložený příkaz
vložíme znovu do řádku vytvořeným aliasem pst. Před tím si ale původní příkaz ještě jednou spustíme tak,
aby se na obrazovku nic netisklo kromě samotného příkazu (asi to bude chtít příkaz umlčet třeba pomocí >
/dev/null). Po vložení zkopírovaného nám tak zůstanou oba hezky nad sebou a tím se nechají dobře porovnat
případně ještě opravit znaky, které jsme přehlédli.
Předchozí díl:
’ Kopírování
16.1.2014
66

Podobné dokumenty

sítě v Linuxu - maturitu.zaridi.to

sítě v Linuxu - maturitu.zaridi.to Nese informace o tom, jaké jsou přístupové práva k danému souboru, kdy došlo k poslední změně či prístupu k souboru, kdo je jeho vlastníkem, počet hardlinku a především informace o tom, kde se nach...

Více

1 na stránku (česky)

1 na stránku (česky) • Setřídí soubory na výstup resp. do souboru (-o file) • Zadání třídícího pole: – beg ... pozice prvního znaku, end ... pozice posledního – tvar: field[.char] ... číslování od 1 • Modifikátory: b (...

Více

premiéra 27.10.2005

premiéra 27.10.2005 cestu k usmíření s Csorbovou rodinou, a zvláště s jejich pěknou dcerou Julinkou. Jak brzy zjišťuje, cesta ke smíru vede pouze přes peníze. Tak se děti vydají na výlet do prehistorického věku, a vyt...

Více

Půl na půl č. 15/2010 - Síť mateřských center

Půl na půl č. 15/2010 - Síť mateřských center ale miluji i tu svoji. ČR je mým druhým domovem, když jsem v Arménii, tak říkám, už chci odletět domů a když jsem tady, tak říkám, že už bych chtěla navštívit domov … Já osobně negativní zkušenosti...

Více

Elektronická verze - Zpravodaj Československého sdružení

Elektronická verze - Zpravodaj Československého sdružení příspěvků. Je to hezká knížka o 330 stránkách, kterou budeme opět prodávat členům za režijní cenu, ne však dříve než na Valném shromáždění CS TUGu v prosinci. Pouze výtisky pro kolektivní členy bud...

Více

číslo = 1 - Zpravodaj Československého sdružení uživatelů TeXu

číslo = 1 - Zpravodaj Československého sdružení uživatelů TeXu Program pdfTEX je rozšíření TEXu, které umožňuje vytvářet dokumenty ve formátu PDF. Aby do takového dokumentu mohly být včleněny hyperlinky a jiné specifické prostředky formátu PDF, je pdfTEX oboha...

Více

Elektronická verze

Elektronická verze Pomocí makra \ifpdf ze stejnojmenného balíku zaručíme, aby makro fungovalo jak při výstupu do PDF, tak i do DVI:

Více

Elektronická verze - Zpravodaj Československého sdružení

Elektronická verze - Zpravodaj Československého sdružení snadnému pochopení a nezkreslenému podání informace nabádá, není důležitý jen jeho obsah, ale i forma. Proto je dobré při tvorbě dokumentu věnovat pozornost jeho formátování. Aby k předání mohlo do...

Více