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
-print de 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.