De meeste mensen die te maken hadden met de UNIX-opdrachten die in dit hoofdstuk zijn uiteengezet, zullen het met deze titel niet eens zijn. "Krijg nou wat! Je hebt me net laten zien dat de Linux interface zeer standaard is, en nu krijgen we een boel opdrachten, waarvan iedere opdracht op een andere manier werkt. Ik zal al die opties nooit onthouden, en je zegt dat ze grappig zijn?" Ja, je hebt zojuist een voorbeeld van hackers humor gezien. Bekijk het bovendien eens van de zonnige kant: er is geen MS-DOS equivalent van deze opdrachten. Als je ze nodig hebt, moet je ze kopen en je weet nooit hoe de interface ervan zal zijn. Hier zijn ze handig en het zijn goedkoope extra's, dus veel plezier ermee!
De set opdrachten waarover in dit hoofdstuk wordt uitgewijd, zijn find, waarmee de gebruiker de directorystructuur kan doorzoeken op specifieke groepen bestanden; tar, handig voor het aanmaken van een archief dat kan worden meegenomen of slechts bewaard; dd, de low-level kopieerder; en sort, waarmee ... jawel, bestanden worden gesorteerd. Een laatste voorbehoud: deze opdrachten zijn op geen enkele wijze gestandaardiseerd, en ondanks dat een kern algemene opties op alle $*$IX systemen is te vinden, heeft de (GNU) versie, die hieronder wordt uitgelegd en die je op je Linux systeem zult aantreffen, gewoonlijk meer mogelijkheden. Dus als je van plan bent andere op UNIX lijkende besturingssystemen te gebruiken, vergeet dan alsjeblieft niet de man page van het doelsysteem erop na te zien om bekend te geraken met de misschien niet zo kleine verschillen.
Tussen de diverse opdrachten die we tot nu toe hebben bekeken, zijn er een aantal die de gebruiker de directorystructuur recursief af laten dalen om één of andere actie uit te voeren: voorbeelden hiervan zijn ls -R en rm -R. Goed. find is de recursieve opdracht. Mocht je denken "Ok, ik moet zo-en-zo doen op al die soorten bestanden op mijn eigen partitie" dan kun je beter nadenken over het gebruik van find. In bepaalde zin is het slechts een neveneffect dat find bestanden zoekt: het werkelijke doel is evalueren.
De basisstructuur van de opdracht luidt als volgt:
find pad... expressie...
Dit geldt in ieder geval voor de GNU-versie; andere versies staan niet toe dat je meerdere paden opgeeft, en daarnaast is het zeer ongebruikelijk iets dergelijks te doen. De globale uitleg van de opdrachtsyntax is nogal simpel: je geeft aan van waaraf je het zoeken wilt beginnen (het pad gedeelte; bij GNU find kun je dit achterwege laten en dan zal het als standaardwaarde uitgaan van de huidige directory .), en wat voor soort zoekopdracht je uit wilt laten voeren (het expressie gedeelte).
Het standaardgedrag van de opdracht is wat lastig, dus is het waard te vermelden. Stel dat er zich in je homedirectory een directory, genaamd garbage, met daarin een bestand foobar bevindt. Je typt blij find . -name foobar (wat zoals je kunt raden zoekt naar bestanden met de naam foobar), en krijgt ... niets meer dan de prompt weer terug. Het probleem ligt in het feit dat find standaard een stille opdracht is; het retourneert slechts een 0 als de zoekopdracht voltooid is (met of zonder iets te vinden) of een waarde ongelijk aan nul als er een probleem is opgetreden. Dit gebeurt niet met de versie die onder Linux is te vinden, maar het is hoe dan ook nuttig om te onthouden.
Het expressie gedeelte kan zelf worden onderverdeeld in vier verschillende groepen trefwoorden: opties, testen, acties en operatoren. Elk kan een true/false waarde retourneren, gecombineerd met een neveneffect. Het verschil tussen de groepen laten we hieronder zien.
zijn van invloed op de algehele werking van find, in plaats van op de verwerking van een enkel bestand. Een voorbeeld is -follow, waarmee find wordt geïnstrueerd symbolische links te volgen in plaats van gewoon de inode te geven. Er wordt altijd true geretourneerd.
zijn echte testen (-empty controleert bijvoorbeeld of het bestand leeg is, en kan true of false retourneren).
hebben ook een neveneffect, namelijk de naam van het bestand retourneren. Zij kunnen ook true of false retourneren.
retourneren geen waarde (conventioneel kunnen ze worden aangemerkt als true), en worden ze gebruikt om complexe expressies samen te stellen. Een voorbeeld is -or, welke de logische OR neemt van de twee subexpressies aan weerszijden. Wanneer expressies naast elkaar worden geplaatst, wordt -and geïmpliceerd.
Het programma find rekent erop dat de shell de opdrachtregel verwerkt; het betekent dat alle trefwoorden in witruimte moet worden ingesloten en vooral dat een heleboel bijzondere tekens tussen aanhalingstekens moeten worden geplaatst of worden voorafgegaan door een backslash, anders zouden ze door de shell zelf worden verminkt. Elke escape methode (voorafgaande backslash, enkele- of dubbele aanhalingstekens) is OK; in de voorbeelden wordt een eenletterig trefwoord gewoonlijk voorafgegaan door een backslash, omdat het (ten minste naar mijn mening) de eenvoudigste manier is. Maar ik schrijf dan ook deze aantekeningen!
Hier is de lijst met alle opties bekend bij de GNU versie van find. Denk eraan dat ze altijd true retourneren.
-daystart meet de verstreken tijd niet van 24 uur geleden maar van de laatste middernacht. Een echte hacker zal de toepassing van een dergelijke optie waarschijnlijk niet begrijpen, maar een werknemer die van acht tot vijf programmeert zal het waarderen.
-depth verwerkt de inhoud van elke directory voor de directory zelf. Om de waarheid te zeggen, weet ik hier niet veel gebruiken van, afgezien voor een emulatie van de opdracht rm -F (natuurlijk kun je geen directory verwijderen voordat alle bestanden erin ook zijn verwijderd...
-follow eerbiedigt (dat wil zeggen volgt) symbolische links. Het impliceert de optie -noleaf; zie hieronder.
-noleaf zet een optimalisatie uit die aangeeft "Een directory bevat twee subdirectory's minder dan hun hardlink telling". Als de wereld perfect zou zijn, zou naar alle directory's worden verwezen door elk van hun subdirectory's (vanwege de .. optie), door de . in de directory zelf en door zijn "echte" naam van zijn parentdirectory. Dat betekent dat naar elke directory minstens tweemaal moet worden verwezen (eenmaal door zichzelf, eenmaal door zijn parent) en eventuele extra verwijzingen door subdirectory's. In de praktijk echter, kunnen symbolische links en gedistribueerde bestandssystemen[1] dit ontwrichten. Deze optie maakt dat find iets langzamer draait, maar het geeft de verwachte resultaten.
-maxdepth niveaus, -mindepth niveaus, waarbij niveaus een positieve integer is, respectievelijk aangevend dat maximaal of minimaal niveaus niveaus aan directory's zouden moeten worden doorzocht. Een paar voorbeelden: -maxdepth 0 geeft aan dat de opdracht slechts op de argumenten op de opdrachtregel uitgevoerd zouden moeten worden, d.w.z., zonder de directorystructuur af te dalen; -mindepth 1 belet de verwerking van de opdracht voor de argumenten op de opdrachtregel, terwijl wel alle andere bestanden dieper gelegen in de directorystructuur in aanmerking worden genomen.
-version drukt slechts de huidige versie van het programma af.
-xdev, een misleidende naam, instrueert find geen device te kruisen, d.w.z. van bestandssysteem te wisselen. Het is erg handig wanneer je naar iets moet zoeken in het root bestandssysteem; op veel machines is het een nogal kleine partitie, en find / zou anders de gehele structuur doorzoeken!
De eerste twee testen zijn erg simpel te begrijpen: -false retourneert altijd false, terwijl -true altijd true retourneert. Andere testen die de specificatie van een waarde niet nodig hebben zijn -empty, welke true retourneert als het bestand leeg is, en het paar -nouser / -nogroup, welke true retourneert in het geval geen regel in /etc/passwd of /etc/group overeenkomt met de user/group id van de bestandseigenaar. Dit is gebruikelijk op een multiuser systeem; een gebruiker wordt verwijderd, maar bestanden van die gebruiker blijven op de vreemdste plekken van de bestandssystemen achter, en vanwege Murphy's wetten nemen ze heel veel ruimte in.
Natuurlijk is het mogelijk te zoeken naar een specifieke gebruiker of groep. De testen zijn -uid nn en -gid nn. Helaas is het niet mogelijk de naam van de gebruiker direct te geven, maar moet de numerieke id nn worden opgegeven.
Een andere nuttige optie is -type c, welke true retourneert als het bestand van het type c is. De mnemonics voor de mogelijke keuzes zijn hetzelfde als die in ls; dus b geeft een block special file aan; c wanneer het bestand een character special is; d voor directory's; p voor named pipes; l voor symbolische links en s voor sockets. Reguliere bestanden worden aangegeven met f. Een gerelateerde test is -xtype, die vergelijkbaar is met -type behalve in het geval van symbolische links. Als -follow niet werd opgegeven, dan wordt het bestand waarnaar verwezen wordt gecontroleerd, in plaats van de link zelf. Geheel ongerelateerd is de test -fstype type. In dit geval wordt het type bestandssysteem gecontroleerd. Ik denk dat de informatie uit het bestand /etc/mtab wordt gehaald, het bestand waarin gemounte bestandssystemen staan; Ik ben er zeker van dat de types nfs, tmp, msdos en ext2 worden herkend.
Testen als -inum nn en -links nn controleren of het inodenummer van het bestand nn of nn links heeft, terwijl -size nn true is als het bestand nn blokken van 512 bytes toegewezen heeft. (alhoewel niet precies: bij sparse files worden niet toegewezen blokken ook meegeteld). Aangezien het resultaat van ls -s tegenwoordig niet altijd in blokken van 512 bytes wordt gemeten (Linux bijvoorbeeld gebruikt 1k als eenheid), is het mogelijk het teken b aan nn toe te voegen, wat betekent te tellen in bytes, of k, voor het tellen in kilobytes.
Permissiebits worden via de test -perm mode gecontroleerd. Als mode geen voorafgaand teken heeft, dan moeten de permissiebits van het bestand exact overeenkomen. Een voorafgaande - betekent dat alle permissiebits moeten zijn ingesteld, ongeacht de soort permisies; een voorafgaande + voldoet als alle bits zijn ingesteld. Oeps! Ik vergat aan te geven dat de modus wordt aangegeven in de octale notatie of symbolisch, zoals je ze gebruikt in chmod.
De volgende groep met testen is gerelateerd aan de tijd waarin een bestand als laatste werd gebruikt. Dit komt van pas wanneer een gebruiker zijn ruimte heeft opgevuld, zoals gewoonlijk wanneer er veel bestanden zijn die hij sinds tijden niet heeft gebruikt, en hij de betekenis is vergeten. De moeilijkheid is deze op te sporen, en find is de enige hoop. -atime nn is true als het bestand nn dagen geleden voor het laatst werd benaderd, -ctime nn als de bestandsstatus nn dagen geleden voor het laatst werd gewijzigd. Bijvoorbeeld, met een chmod en -mtime nn als het bestand nn dagen geleden werd aangepast. Soms heb je een nauwkeuriger datumstempel nodig; de test -newer bestand is tevreden als het bestand in kwestie later is aangepast dan bestand. Om dit te bereiken moet je dus touch gebruiken met de gewenste datum. GNU find kent nog de testen -anewer en -cnewer die vergelijkbaar functioneren; en de testen -amin, -cmin en -mmin die de tijd in minuten tellen in plaats van in 24-uurs perioden.
Als laatste maar niet als minste de test die ik vaker gebruik. -name patroon is true als de bestandsnaam exact overeenkomt met patroon, wat min of meer hetgeen is wat je zou gebruiken in een standaard ls. Waarom `min of meer'? Omdat je er natuurlijk aan moet denken dat alle parameters door de shell worden verwerkt, en die geliefde metatekens worden geëxtraheerd. Dus een test als -name foo* retourneert niet wat je wilt, dus je zult -name foo\* of -name "foo*" moeten opgeven. Dit is waarschijnlijk de meest voorkomende fout die door onzorgvuldige gebruikers wordt gemaakt, dus schrijf het in GROTE letters op je scherm. Een ander probleem is dat, net als bij ls, voorafgaande punten niet worden herkend. Je kunt hieraan het hoofd bieden door gebruik te maken van de test -path patroon welke zich niet bekommert om punten en slashes bij het vergelijken van het pad van het bestand met patroon.
Ik gaf al eerder aan dat acties werkelijk iets doen. -prune doet echter niets, d.w.z. de directorystructuur afdalen. (tenzij -depth is opgegeven). Gewoonlijk wordt het in combinatie met -fstype gebruikt om te kiezen tussen de diverse bestandssystemen die zouden moeten worden nagegaan. De andere acties kunnen grofweg worden onderverdeeld in twee categorieën;
Acties die iets afdrukken. Hiervan is het meest vanzelfsprekend, en inderdaad, de standaardactie van find, -print, dat gewoon de naam van de bestanden afdrukt overeenkomend met de andere voorwaarden in de opdrachtregel en true retourneert. Een simpele variant op -print is -fprint bestand, dat gebruikt maakt van bestand in plaats van de standaarduitvoer. -ls geeft het huidige bestand in hetzelfde formaat weer als ls -dils; -printf format functioneert min of meer als de C functie printf(), zodat je kunt opgeven hoe de uitvoer moet worden opgemaakt, en -fprintf bestand opmaak doet hetzelfde, maar schrijft het weg naar bestand. Deze actie retourneert ook true.
Acties die iets uitvoeren. Ze worden veel gebruikt, maar de syntax ervan is wat vreemd, dus neem de moeite ze te bekijken. -exec opdracht \; de opdracht wordt uitgevoerd en de actie retourneert true als zijn uiteindelijke status 0 is, dat wil zeggen de reguliere uitvoering ervan. De reden voor de \; is nogal logisch: find weet niet waar de opdracht eindigt, en de truuk om de exec actie aan het eind van de opdracht te plaatsen is niet van toepassing. De beste manier om het einde van de opdracht aan te geven is door het teken te gebruiken dat door de shell zelf wordt gebruikt, dat is `;', maar natuurlijk zou een puntkomma op de opdrachtregel door de shell worden opgeslokt en nooit aan find worden doorgegeven, dus moet er een escape-teken voor worden geplaatst. Het tweede waar je aan moet denken is hoe de naam van het huidige bestand binnen opdracht op te geven, aangezien je waarschijnlijk alle moeite deed de expressie samen te stellen om er iets mee te doen, en niet om slechts date af te drukken. Dit wordt bewerkstelligd met de tekenreeks {}. Sommige oude versies van find vereisen dat het in witruimte moet worden omsloten - niet erg handig als je bijvoorbeeld het gehele pad nodig hebt en niet alleen de bestandsnaam. En moeten er geen backslash-tekens voor of aanhalingstekens omheen worden geplaatst, vraag je je vast af? Verbazingwekkend genoeg hoefde ik dit noch onder tcsh noch onder bash te doen (sh beschouwt { en } niet als speciale tekens, dus levert het geen problemen op). Ik denk dat de shell "weet" dat {} geen zinnige optie is, de reden waarom het niet wordt geëxtraheerd. Gelukkig voor find die het dan onaangeroerd door kan krijgen. -ok opdracht \; functioneert als -exec, met het verschil dat voor elk geselecteerd bestand de gebruiker wordt gevraagd de opdracht te bevestigen. Het wordt uitgevoerd, als het antwoord met y of Y begint, anders niet. De actie retourneert false.
Er zijn een aantal operatoren; hier is een lijst in volgorde van afnemende prioriteit.
dwingt de voorrangsvolgorde af. De haakjes moeten uiteraard worden aangehaald, aangezien ze voor de shell ook van betekenis zijn.
wijzig de werkelijke waarde van de expressie, dat wil zeggen dat als expr true is, het false wordt. Voor het uitroepteken hoeft geen escape-teken te worden geplaatst, omdat het voorafgaat aan witruimte.
allen corresponderen met de logische AND bewerking, wat in het eerste en het meest gebruikelijke geval wordt geïmpliceerd. expr2 wordt niet geëvalueerd, als expr1 de waarde false oplevert.
corresponderen met de logische OR bewerking. expr2 wordt niet geëvalueerd als expr1 de waarde true oplevert.
is het list statement; zowel expr1 als expr2 worden geëvalueerd (natuurlijk met alle neveneffecten!), en de uiteindelijke waarde van de expressie is die van expr2.
Ja, find heeft gewoon teveel opties. Ik weet het. Maar er zijn een heleboel voorgefabriceerde voorbeelden die het waard zijn te onthouden, omdat ze zeer vaak worden gebruikt. Laten we er een aantal van bekijken.
% find . -name foo\* -print |
zoek naar alle bestandsnamen beginnend met foo. Als de string onderdeel uitmaakt van de naam, dan is het waarschijnlijk verstandiger iets te schrijven als "*foo*", in plaats van \*foo\*.
% find /usr/include -xtype f -exec grep foobar \
/dev/null {} \;
|
is de opdracht grep die beginnend vanuit de directory /usr/include recursief wordt uitgevoerd. In dit geval zijn we zowel geïnteresseerd in reguliere bestanden als symbolische links die naar reguliere bestanden verwijzen, vandaar de -xtype test. Vaker is het simpeler om te voorkomen het op te geven, vooral als we er tamelijk zeker van zijn dat geen enkel binair bestand de gewenste string bevat. En waarom de /dev/null in de opdracht? Het is een truuk om grep te dwingen de bestandsnaam te schrijven waar een overeenkomst werd aangetroffen. De opdracht grep wordt in een andere aanroep op elk bestand toegepast en dus neemt het aan dat het niet nodig is de bestandsnaam als uitvoer te geven. Maar nu zijn er twee bestanden, d.w.z. de huidige en /dev/null! Een andere mogelijkheid zou zijn de uitvoer van de opdracht middels een pipe aan xargs door te geven en het de grep laten uitvoeren. Ik probeerde het en vernielde hierbij mijn bestandssysteem volledig (waarop deze notities die ik met de hand probeer te herstellen :-( ).
% find / -atime +1 -fstype ext2 -name core \
-exec rm {} \;
|
is een klassieke taak voor crontab. Het verwijdert alle bestanden met de naam core in bestandssystemen van het type ext2 die niet zijn benaderd in de laatste 24 uur. Het is mogelijk dat iemand het core bestand wil gebruiken om een post mortem dump uit te voeren, maar niemand weet na 24 uur meer wat hij aan het doen was...
% find /home -xdev -size +500k -ls > piggies |
is handig om te zien wie de eigenaar is van de bestanden, door wie het bestandssysteem verstopt raakt. Let op het gebruik van -xdev; aangezien we slechts in een bestandssysteem zijn geïnteresseerd, is het niet nodig andere bestandssystemen af te dalen die onder /home zijn gemount.
Houd in gedachten dat find een tijdrovende opdracht is, aangezien het elke en iedere inode op het systeem moet benaderen om de bewerking uit te voeren. Het is daarom verstandig de hoeveelheid bewerkingen die je nodig hebt in een unieke aanroep van find te combineren, vooral in de `huishoudelijke' taken die gewoonlijk via een crontab taak worden uitgevoerd. Een verhelderend voorbeeld is het volgende: stel dat we bestanden eindigend op de extensie .BAK willen verwijderen en de permissie van alle directory's op 771 in willen stellen en alle bestanden eindigend op .sh op 755. En wellicht mounten we NFS bestandssystemen via een dial-up link, en willen we de bestanden hierop niet controleren. Waarom zou je drie verschillende opdrachten schrijven? De meest effectieve wijze om deze taak te bewerkstelligen is:
% find . \( -fstype nfs -prune \) -o \
\( -type d -a -exec chmod 771 {} \; \) -o \
\( -name "*.BAK" -a -exec /bin/rm {} \; \) -o \
\( -name "*.sh" -a -exec chmod 755 {} \; \)
|
Het ziet er niet uit (en met veel misbruik van backslashes!), maar het beter bekijkend blijkt dat de onderliggende logica nogal recht-toe-recht-aan is. Onthoud dat wat werkelijk wordt uitgevoerd een true/false evaluatie is; de ingesloten opdracht is slechts een neveneffect. Maar dit betekent dat het alleen wordt uitgevoerd als find het exec deel van de expressie moet evalueren, dat is dus alleen als de linkerkant van de subexpressie evalueert tot true. Dus als wordt aangenomen dat het bestand thans een directory is, dan wordt de eerste exec geëvalueerd en wordt de permissie van de inode gewijzigd in 771; anders vergeet het alles en gaat verder naar de volgende subexpressie. Waarschijnlijk is het makkelijker om het in de praktijk te zien dan om het op te schrijven; maar na een tijdje wordt het iets natuurlijks.
| [1] | Gedistribueerde bestandssystemen maken het mogelijk dat bestanden verschijnen als lokaal tot een machine wanneer ze feitelijk elders zijn te lokaliseren. |