page header

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:

  1. de algemene vorm van het commando is: find directory criteria actie(s)
  2. meerdere directories achter elkaar zijn toegestaan
  3. we zeggen dat -print de default actie is
  4. 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.c op 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.