Hlavní navigace

Drobnosti ze shellového zápisníku (3)

23. 4. 2002
Doba čtení: 5 minut

Sdílet

Dnes si prozradíme ještě jednu metodu na hromadné akce a toto téma ukončíme přehledem, který nám pomůže vybrat správný typ. Pak se seznámíme s dalšími uvozovkami pro Bash a s postupy, jak se vyhnout omezení koncem řádku. Zakončíme několika metodami porovnávání souborů, vhodnými například pro důkladné ověření právě vytvořeného CD.

Díky vašim ohlasů dnes upřesním některé informace o hromadných akcích.

Použití find a xargs

minulém dílu jsem zapomněl na ukázku základní podoby konstrukce find -exec. Nyní to napravím:

# smazat všechny zálohy ve všech podadresářích
find -type f -name ‚*~‘ -exec rm -vf {} \;

Nesmíme zapomenout vložit vzor jména do uvozovek, jinak by jej expandoval již shell. Naproti tomu dvojzávorku {}, která bude nahrazena jménem souboru, do uvozovek dávat nemusíme. Tato konstrukce přijme jakékoliv jméno souboru. Do jedné konstrukce však nelze zapsat více než jednu akci, ledaže bychom pokaždé volali shell (viz příklad v minulém dílu – ten však již nezpracuje neobvyklá jména souboru, nanejvýš s mezerami).

Xargs je program, který provádí předepsanou akci pro každý soubor ze vstupu. V základní podobě není odolný proti podivným jménům, avšak jeho argument –0 umožňuje přijímat jména souborů oddělená znakem <NULL>. Výborně jej lze kombinovat s příkazem find s argumentem -print0. Podobné funkce lze dosáhnout konstrukcí

find -exec, ale xargs navíc umožňuje vřadit filtr jmen souborů nebo předávat více jmen souborů najednou.

# vypsat všechny soubory, které nemají ve jménu číslici (neodolná verze)
# volat ls s pěti jmény souboru (urychlení)
find -type f | sed ‚/[0–9]/d‘ | xargs -n5 ls -al

# vypsat všechny soubory, které projdou filtrem (odolná verze)
find -type f -print0 | filtr | xargs –0 ls -al

Na druhé konstrukci je poněkud nepraktické, že sed jako filtr nezpracuje jména souborů obsahující znak konce řádku (ač je v dokumentaci zmíněno, že je to možné pomocí konstrukce \n, nepodařilo se mi to použít v praxi) a že znak <NULL> se poměrně špatně píše na klávesnici. Na druhé straně můžeme napsat filtr v jakémkoliv vyšším programovacím jazyku…

Doplnění ke konstrukci for

Konstrukce for i in * ; do akce „$i“ ; done skutečně zpracuje jakékoliv jméno souboru, pokud použijeme uvedené uvozovky.

U starších shellů však existuje poměrně nízký limit celkové délky řetězce (většinou 2048 bajtů). Pro bash-2.05a je toto omezení pro externí příkazy 131072 bajtů (testováno na příkazu cat), interním příkazům (jakými jsou for a echo) lze bez problémů předat i milión argumentů (ověřeno).

Souhrnný přehled metod pro hromadné akce

Tabulka č. 274
Konstrukce rekurzivní odolná změna jména omezení
for i in vzor ne ano shell, ext staré shelly
for i in $(find parametry) ano ne shell, ext staré shelly
find -exec ano ano  – ne
find -printf | sh ano uv! shell, ext ne
find | xargs ano ne ext ne
find -print0 | xargs –0 ano ano ext ne
find -print0 | sed | xargs –0 ano EOL! ano ne
awk příkaz <soubor soubor EOL!, FS! ano ne

Poznámky:
odolná – udává odolnost vůči neobvyklým jménům při správném použití uvozovek: EOL! – neodolá znaku konce řádku, uv! –

neodolá uvozovkám (a zřejmě i ), FS! – neodolá znaku definovanému proměnnou FS, ne – neodolá mezerám ani dalším speciálním znakům
změna jména – udává vhodnost pro přejmenování a převody: shell – shellovými vzory, ext – externím programem. Pomlčka udává, že konstrukce je složitá, nikoliv nemožná.

Jedny uvozovky navíc pro Bash: $‚text

Tyto uvozovky lze použít pouze v nových shellech, jakým je např. Bash. Uvnitř těchto uvozovek dochází pouze k expanzi několika sekvencí: \a, \b, \e, \f, \n, \r,

\t, \v, \\, \', \nnn a \xHH. Jsou vhodné pro zápis složitých řetězců, u nichž nepotřebujeme expanzi proměnných. Více nalezneme v dokumentaci.

Překonání hranic řádku

Často potřebujeme v textu vyhledávat či zaměňovat řetězce, které mohou být rozděleny na více řádků. Nové shelly umožňují zadávat řetězec na více řádků. Vypadá sice neobvykle, ale funguje. Program grep sice při tom nefunguje úplně dokonale, ale například na následující dotaz odpoví správně:

# Vypíše soubory, ve kterých se nachází jméno Jan Novák (v prvním pádu)
grep -l ‚Jan
? *Novák‘ xx

Znalci regulárních výrazů by mohli namítnout, že tento výraz přijme i řetězec JanNovák. Je to proto, že komplikovanější regulární výraz se znakem konce řádku již v grepu fungovat nebude.

Naštěstí je dostupná cesta oklikou – pomocí programu tr převedeme všechny znaky konce řádku na jiný znak. V praxi používám znak currency (¤), v české programátorské hantýrce zvaný poeticky prase (dříve též valící se rubl –

v dobách RVHP si vyměnil místo s ideově závadným znakem $ v dolní části ASCII tabulky), který se v textech běžně nevyskytuje:

# test, zda se v souboru nachází jméno Jan Novák (v prvním pádu)
tr ‚\n‘ ¤ <soubor | grep ‚Jan\( *\| *¤ *)Novák‘

Po převodu mohu znak konce řádku vrátit zpět:

# převést jméno Jan Novák na Josef Novotný (v prvním pádu)
tr ‚\n‘ ¤ <novak.txt | sed ‚s/Jan\( *\| *¤ *)Novák/Josef\1No­votný/g‘ | tr ‚¤‘ ‚\n‘ >novotny.txt

Znak ¤ se v X-Window napíše pomocí kombinace <Compose>

x o. Compose je pravé window tlačítko, na starších klávesnicích Přeřaďovač, Alt, pustit Přeřaďovač, pustit Alt.

Porovnávání souborů

Jistě znáte situaci, kdy si často používané zálohy překopírujete z CD na disk, nebo kdy si vypálíte část archivu, a pak chcete z disku smazat soubory, které jsou již uložené na CD. Zde je několik příkladů na tuto akci.

V prvním příkladu jde o prosté porovnání. Lze použít např. jako důkladnou kontrolu vytvořeného CD. Protože budeme prohledávat celý strom, použijeme find (o jeho záporech jsme již psali výše).

# porovnat všechny soubory v aktuálním adresáři a podadresářích se zadaným adresářem (/vol/cdrom)
find -type f -exec cmp {} /vol/cdrom/{} \;

V dalším příkladu se omezíme jen na aktuální adresář, avšak po úspěšné kontrole soubory smažeme.

# porovnat a smazat v aktuálním adresáři soubory, které existují v zadaném adresáři (/vol/cdrom)
for i in * ; do if cmp 2>/dev/null „$i“ „/vol/cdrom/$i“ ; then rm -vf „$i“ ; fi ; done

Pokud jsou zálohy roztroušené na mnoha CD, bude uvedený postup nepraktický. Rozšířenou metodou, jak mít výpis obsahu všech CD vždy po ruce, jsou soubory ls-lR (pojmenované podle příkazu, který je vytváří: ls -lR). Následující program hledá v souborech ls-lR:

# totéž, ovšem soubory hledá v seznamech ls-lR (~/Archiv/CD-ROM/ls-lR) a neporovnává
# nejdřív jen vypsat
for i in * ; do if grep -h " $i$" ~/Archiv/CD-ROM/ls-lR-
; then ls -dal „$i“ ; fi ; done
# je-li to v pořádku, můžeme mazat
for i in * ; do if grep -q " $i$" ~/Archiv/CD-ROM/ls-lR-* ; then rm -vf „$i“ ; fi ; done

Pozor: Postup je potenciálně nebezpečný, pokud dáváte souborům jména totožná se jmény adresářů, číselná jména (zaměnitelná ve výpisu za údaj celkem) nebo jména s mezerami.

ict ve školství 24

Pro orientaci v ls-lR souborech poslouží též triviální program lslrfind (v jazyce C). Ten vypíše nejen jméno nalezeného souboru, ale i adresář, ve kterém soubor leží:

# nalézt řetězec v souborech ls-lR
for i in ~/Archiv/CD-ROM/ls-lR-* ; do echo „CD-ROM $i:“ ; lslrfind <$i řetězec ; done

Autor článku