mei 2009 Archieven
Find voor gevorderden
Geplaatst door hjt op do mei 28 17:29:10 CET 2009 | Permanente link | Categorie: Tips and Tricks | Reacties: 0
Een discussie onder collega's over het find-commando herinnerde me
aan een worsteling die ik een tijdje geleden met dat commando had.
Het is een van de meest krachtige commando's uit het repertoire,
maar -- zoals oude manual pagina's dat letterlijk zeiden --
"the syntax is painful".
find directory criteria actie(s)
Een krachtige faciliteit, maar te moeilijk geoordeeld voor onze cursussen,
is de -prune. Daarmee kun je stukken van de fileboom "snoeien",
d.w.z. overslaan in de zoek-operatie. Om die -prune te begrijpen moet je
weten dat niet alleen de zoek-criteria een booleaanse expressie vormen,
maar dat de acties formeel ook deel van die expressie uitmaken.
Dat laatste is ook een detail dat we in onze cursussen weglaten,
uit angst dat de cursist door de bomen het bos niet meer ziet.
En bij Booleaanse expressies hoort lazy evaluation:
als een uitkomst al vaststaat voordat de laatste gedeelten
van de expressie aan de beurt zijn, dan komen ze niet meer
aan de beurt.
De manual-page zegt:
-prune True; if the file is a directory, do not descend into it.
Dat woordje "True" bevat de crux, maar je leest er zo gauw overheen. Prune is een actie, maar heeft dus ook een booleaanse waarde, zoals alle acties dat hebben. Dat betekent dat in expressies zoals
... -prune -o \( ......... \)
het gedeelte rechts van de -o (or) niet meer hoeft te worden
uitgerekend als door de true van -prune, in combinatie met
wat ervoor staat, de einduitkomst true al vast zou liggen.
Kijk eens naar dit voorbeeld:
find . -name 'myjunk' -a -prune -o \( -name 'pietje.puk' -print \)
Merk op dat ik tussen -name myjunk en -prune de booleaanse
-a (and) expliciet heb opgeschreven. Dat heb ik gedaan om
nadrukkelijk op die 'and' te wijzen. Je mag -a weglaten.
Als we op onze tocht door de fileboom de naam 'myjunk' tegenkomen
dan scoort -name myjunk true. Als het ook nog een directorynaam is,
dan wordt die geskipt. De combinatie -name myjunk -a -prune
is nog steeds true. De -o (or) die daarna komt kan daar niets meer
aan veranderen, en wordt dus helemaal overgeslagen.
Echter, komen we op onze tocht door de fileboom een andere naam
tegen, dan is -name myjunk false, dus -name myjunk -a -prune
is ook false. De -o (or) die daarna komt levert eigenlijk een
compleet nieuwe kans om alsnog een true te bereiken. Dus het is nu
alsof dat hele stuk -name myjunk -a -prune buiten spel wordt
gezet, en het stuk tussen de haken na de -o als een min of meer
zelfstandige find alsnog aan het werk wordt gezet.
Resultaat: we zoeken de naam pietje.puk, maar in/onder de subdirectory myjunk kijken we niet.
Zo'n beslissing om bepaalde (veel) details tijdens een cursus niet te behandelen is onvermijdelijk, maar kan tot gewetensnood leiden. Als docent denk je dan altijd .."maar als iemand eens een commando zus-en-zo opbouwt, dan wordt hij verrast omdat we een detail net iets te eenvoudig hebben uitgelegd...." Of, nog erger: "stel dat dat detail nu net wel in een van de vragen bij een LPIC-examen zit".
Het volgende voorbeeld, weer gebaseerd op de booleaanse waarde
van een actie-component bij find, illustreert dat.
We hanteren in onze lessen de volgende systematiek bij de behandeling:
- de algemene vorm van het commando is:
find directory criteria actie(s) - meerdere directories achter elkaar zijn toegestaan
- we zeggen dat
-printde default actie is - bij de booleaanse operaties met criteria vertellen we
o.a. over:
crit1 -o crit2
Maar kijk nu eens hier:
mkdir subdir; cd subdir # maak lege subdir, ga daar naartoe
> fa; > fb # maak in die (nu huidige) dir twee files
vb. A) find . -name 'fa' -o -name 'fb'
vb. B) find . -name 'fa' -o -name 'fb' -print
vb. C) find . \( -name 'fa' -o -name 'fb' \) -print
Volgens ons les-verhaal zouden A) en B) hetzelfde resultaat moeten leveren, want een default-component mag je meegeven of weglaten zonder verschil te maken. Maar A) en B) geven verschillend resultaat!
De adder onder het gras is alweer dat acties ook meetellen in de
Booleaanse expressie. En omdat twee componenten zonder Booleaanse
operatie ertussen impliciet een -a (and) ertussen krijgen, moet
je B) als volgt lezen:
vb. B) find . -name 'fa' -o -name 'fb' -a -print
en vanwege de Booleaanse precedentieregels betekent dat weer:
vb. B) find . -name 'fa' -o \( -name 'fb' -a -print \)
Dat verklaart waarom A) en B) verschillend resultaat geven. Voorbeelden A) en C) geven wel hetzelfde resultaat.
De moraal van het verhaal: bij een -o kun je het beste maar
altijd haakjes gebruiken. Dus nooit B) schrijven, maar altijd C).
Een ander nuttig criterium is -depth: behandel eerst
files (en subdirs) die in een directory staan, alvorens de
directory zelf te behandelen. Met andere woorden: ga eerst zo snel
mogelijk de diepte in, en schenk pas op de terugweg omhoog aandacht
aan de tussen-niveaus waar je langs kwam. Ik heb zelf de vaste gewoonte
om die -depth altijd mee te geven, want in combinatie
met de commando's tar en cpio kun je later timestamps van de
oorspronkelijke fileboom iets nauwkeuriger reconstrueren.
Maar: de combinatie van -depth en -prune
bijt elkaar! Het besluit om een directory te skippen moet je nemen
voordat je erin duikt, en niet achteraf.
GNU sed -i
Geplaatst door miekg op vr mei 8 16:42:19 CEST 2009 | Permanente link | Categorie: Systeembeheer | Reacties: 0
In een vorig blog
hebben we uitgelegd hoe kleine scriptjes kunnen uitgroeien tot goed
gedocumenteerde (en ietwat grotere) scripts. Het voorbeeld script in dat
verhaal was een wrapper rond git, om versie beheer te
vergemakkelijken. Ook zit er een stukje code die het woord $Hash$
omzet in bijvoorbeeld
`$Hash: fstab f38c788 2009-04-10 19:58:50 +0200 miekg $
Zo kun je makkelijk zien dat een bestand onder versie beheer zit en welke versie het heeft en door wie de laatste edit slag is gepleegd.
De code die dat doet ziet er als volgt uit:
sed -i -e 's/\$[H]ash\$'/\$H''ash:\ $id\ \$/ "$base"
De "gekke" \$[H]ash\$ zorgt ervoor dat we dit script met
zichzelf kunnen bewerken. In andere woorden: deze reguliere
expressie matchet niet zich zelf.
Maar ik wil het hier over iets anders hebben, namelijk de -i vlag
van GNU sed. Met deze vlag kun je in-place editen. Vroeger
(of bij seds die geen -i vlag hebben) ging dat als volgt:
sed -e '<bewerking>' bestand > .bestand.tmp.$$
cp .bestand.tmp.$$ bestand
rm .bestand.tmp.$$
De cp + rmcombo is nodig om sym- en harde links intact te laten.
Gebruik je bijvoorbeeld mv dan gaan links wel stuk.
Een voorbeeld:
$ touch x
$ ln x y
$ ls -li x y
424 -rw-rw-r-- 2 miekg miekg 0 May 8 15:26 x
424 -rw-rw-r-- 2 miekg miekg 0 May 8 15:26 y
Oftewel, y is nu hard gelinkt aan x. Helemaal links staan de
inode nummers, die aangeven dat beide namen naar dezelfde inode
wijzen. Nu dan het kopiƫren en daarna het terug zetten met mv
$ cp y .y.tmp.$$ # kopieer de file
$ mv .y.tmp.$$ y # move het terug
$ ls -li x y
424 -rw-rw-r-- 1 miekg miekg 0 May 8 15:26 x
437 -rw-rw-r-- 1 miekg miekg 0 May 8 15:28 y
En zoals je ziet is de inode van y veranderd naar 437 en is
dus de hard link gebroken. Hadden we cp gebruikt
$ cp .y.tmp.$$ y # kopieer het terug
$ ls -li x y
424 -rw-rw-r-- 1 miekg miekg 0 May 8 15:26 x
424 -rw-rw-r-- 1 miekg miekg 0 May 8 15:28 y
dan was er niks aan de hand geweest. Nu terug naar de -i vlag
van de GNU versie van sed.
Deze vlag lijkt handig omdat je dan niet zelf een tussen bestand (hier
.y.tmp.$$) hoeft aan te maken, maar deze klus door sed laat opknappen. Helaas
gebruikt sed intern een mv om de file terug te verplaatsen.
In de source van sed kun je dit nakijken. Bijvoorbeeld in sed versie 4.1.5 vind je in
lib/utils.cop regel 324
int rd = rename (from, to);De rename()-functie is system call die het mv commando ook gebruikt. Oftewel: sed -i gebruikt intern dus een
mv.
Dus als je GNU sed gebruikt om sym- en/of hard-links te bewerken dan
wordt de link verbroken! Dit hebben we natuurlijk ook
gemeld
aan de ontwikkelaars. De ontwikkelaars vonden dit geen bug, aangezien
dit gedrag gedocumenteerd is. Wij hebben vooralsnog deze documentatie
niet gevonden.
Het goede nieuws is dat in GNU sed versie 4.2 dit verbeterd zal
zijn. Het slechte nieuws is dat deze versie nog niet uit is. En er
zal dan waarschijnlijk nog wat tijd over heen gaat voordat deze versie
in alle Linux distributies zit.
