Tra i vari comandi visti finora, ce ne sono alcuni che permettono all'utente di scendere ricorsivamente nell'albero delle directory per fare qualche azione: gli esempi canonici sono ls -R e rm -R. Bene. find è il comando ricorsivo. Ogni volta che pensate ``Beh, devo fare questo e quest'altro su tutti questi tipi di file nella mia partizione'', dovreste pensare di usare find. In un certo senso il fatto che find trovi file è un effetto secondario: il suo vero compito è valutare.
La struttura di base del comando è la seguente:
find percorso [...] espressione [...]
Questo almeno vale per la versione GNU; altre versioni non permettono di specificare più di un percorso, ed inoltre è molto raro che serva una cosa del genere. La spiegazione della sintassi del comando è piuttosto semplice: dite da dove volete che cominci la ricerca (la parte percorso; con GNU si può omettere e per default verrà presa in considerazione la directory corrente .), e che tipo di ricerca volete fare (la parte espressione).
Il comportamento standard del comando è piuttosto complicato, quindi vale la pena notarlo. Supponiamo che nella vostra home directory ci sia una directory butta, che contiene il file pippo. Digitate find . -name pippo (che come potete indovinare ricerca un file di nome pippo), e ottenete ...niente altro che il prompt. Il problema sta nel fatto che find è per default silenzioso: rende 0 se la ricerca è stata completata (trovando qualcosa o meno) o un valore non-zero se ci sono stati dei problemi. Questo non accade con la versione che trovate su Linux, ma è comunque utile da ricordare.
La parte delle espressioni può essere divisa in quattro gruppi di parole chiave diversi: opzioni, test, azioni ed operatori; ognuno di questi può restituire un valore vero o falso, insieme ad un effetto secondario. Le differenze tra i gruppi vengono mostrate più avanti.
Notate che find si basa sulla shell per leggere la linea di comando, ciò significa che le parole chiave devono essere messe tra spazi, e soprattutto che molti caratteri devono essere trattati in modo da non essere corrotti dalla shell. Si può fare sia con la barra rovesciata che con le virgolette, singole e doppie; negli esempi le parole chiave a carattere singolo verranno quotate con la barra rovesciata, perché (almeno secondo me, ma sono io che scrivo queste note!) è il modo più semplice.
Ecco la lista di tutte le opzioni note alla versione GNU di find. Ricordate che restituiscono sempre il valore vero.
Ciò significa che ciascuna directory deve essere referenziata almeno due volte (una da se stessa, una dalla sua madre) e ogni referenza aggiuntiva proviene da una sottodirectory. In pratica, comunque, i link simbolici ed i filesystem distribuiti possono rompere questa regola. Questa opzione fa girare find leggermente piú lento, ma può dare i risultati attesi.
I primi due test sono molto semplici da capire: -false restituisce solo il valore falso, mentre -true restituisce solo il valore vero. Altri test che non hanno bisogno della specifica di un valore sono -empty, che restituisce il valore vero se il file è vuoto, e la coppia -nouser / -nogroup, che restituiscono il valore vero se nessuna voce di /etc/passwd o /etc/group corrisponde all'id di utente/gruppo del proprietario del file. Questa è una cosa comune che accade nei sistemi multiutente; viene cancellato un utente, ma nei posti più strani del filesystem restano suoi file, e per la legge di Murphy occupano un sacco di posto.
Naturalmente, è possibile ricercare un utente o un gruppo specifico. I test sono -uid nn e -gid nn. Sfortunatamente non è possibile dare direttamente il nome dell'utente, ma bisogna usare l'id numerico, nn.
È possibile usare le forme +nn, che sta per ``un valore strettamente maggiore di nn'' e -nn, che sta per ``un valore strettamente minore di nn''; è piuttosto stupido nel caso degli UID, ma sarà comodo con altri test.
Un'altra opzione utile è -type c, che restituisce il valore vero se il file è di tipo c. Le corrispondenze mnemoniche per le scelte possibile sono le stesse che in ls; quindi si ha b per i file block special, c per quelli character special, d per le directory, p per le pipe con nome, l per i link simbolici, e s per le socket. I file regolari sono indicati con una f. Un test correlato è -xtype, che è simile a -type tranne nel caso dei link simbolici. Se non è stato dato -follow, il file a cui si punta viene controllato al posto del link stesso. Completamente scorrelato è il test -fstype tipo; in questo caso viene controllato il filesystem. Credo di aver preso questa informazione dal file /etc/mtab, quello che indica i filesystem che vengono montati; sono sicuro che i tipi nfs, tmp, msdos ed ext2 vengono riconosciuti.
I test -inum nn e -links nn controllano se il file ha numero di inode nn o nn link, mentre -size nn rende vero se il file ha allocato nn blocchi da 512 byte (beh, non precisamente: per i file sparsi i blocchi non allocati vengono contati lo stesso). Dato che al giorno d'oggi i risultati di ls -s non vengono sempre misurati in parti da 512 byte (Linux per esempio usa unità di 1K), è possibile appendere a nn il carattere b per contare in byte, o k per contare in kilobyte.
I bit di permesso vengono controllati con il test -perm modalità. Se modalità non ha segno, i bit di permesso dei file devono combaciare perfettamente. Un - che precede i bit significa che tutti i bit di permesso devono essere impostati, ma non fa assunzioni sugli altri; un + è soddisfatto se uno qualsiasi dei bit è impostato. Ops! Mi dimenticavo di dire che la modalità è scritta in ottale o simbolicamente, come si fa in chmod.
Il prossimo gruppo di test è correlato all'ora di ultimo utilizzo del file; è comodo quando un utente ha riempito il suo spazio, e come al solito ci sono moltissimi file inutilizzati da anni, e di cui si è dimenticato il significato. Il problema è trovarli, e find è l'unica speranza. -atime nn rende vero se il file è stato utilizzato nn giorni fa - ad esempio, con un comando chmod - e -mtime nn se il file è stato modificato per l'ultima volta nn giorni fa. Talvolta si ha bisogno di un tempo più preciso; il test newer file è soddisfatto se il file considerato è stato modificato dopo file. Quindi, dovete semplicemente usare touch con la data desiderata, ed avete fatto. Il find della GNU aggiunge i test -anewer e -cnewer che si comportano in maniera simile, ed i test -amin, -cmin e -mmin, che contano il tempo in minuti invece che in periodi di 24 ore.
Per ultimo, il test che uso più spesso: -name pattern rende vero se il nome del file corrisponde esattamente a pattern, che è più o meno quello che si usa in un ls standard. Perché `più o meno'? Perché naturalmente dovete ricordare che tutti i parametri sono processati dalla shell, e quei bei metacaratteri vengono espansi. Quindi, un test come -name foo* non renderà quello che volete, e dovreste scrivere o -name foo\* o -name "foo*". Questo è probabilmente uno degli errori più comuni fatti dagli utenti non attenti, quindi scrivetelo a lettere GRANDI sul vostro schermo. Un altro problema è che, come con ls, i punti all'inizio non vengono riconosciuti; per questo potete usare il test -path pattern, che non si preoccupa dei punti e delle barre quando paragona il percorso del file considerato con pattern.
Ho detto che le azioni fanno, appunto, un'azione. Beh, -prune invece non fa qualcosa, cioè non discende all'interno dell'albero delle directory (a meno che non si dia -depth). Di solito si trova insieme a -fstype, per scegliere tra i vari filesystem da controllare.
Le altre azioni possono essere divise in due grandi categorie:
-exec comando \;
il comando è eseguito, e l'azione
restituisce il valore vero se il suo stato finale è 0, cioè se l'esecuzione è
regolare. La ragione per il \;
è piuttosto logica: find non sa
dove finisce il comando, e il trucco di mettere l'azione exec alla fine del
comando non è applicabile. Beh, il modo migliore per segnalare la fine del
comando è di usare il carattere che la shell stessa usa, cioè
`;', ma naturalmente un punto e virgola da solo sarebbe mangiato dalla
shell e non arriverebbe a find, quindi deve essere usato con un comando di
escape. La seconda cosa
da ricordare è come specificare il nome del fie corrente all'interno di
comando, dato che probabilmente vi siete adoperati perché l'espressione faccia
qualcosa, e non solo stampare date; si fa per mezzo della stringa
{}
. Alcune versioni vecchie di find richiedono che debba essere
messa tra spazi bianchi - non molto comodo se ad esempio vi serve il percorso
intero e non solo il nome del file - ma con il find della GNU può essere ovunque
nella stringa che compone comando. E, chiederete di sicuro, non deve essere
usato con un comando di escape o quotato? Meraviglioso: non l'ho mai dovuto fare
né sotto tcsh né sotto bash (sh non considera
{
e
}
come caratteri speciali, quindi non è un problema). La mia idea è che la
shell ``sa'' che {}
non è un'opzione che ha senso, quindi non prova ad
espanderla, fortunatamente per find, che la ottiene integra.
-ok comando \;
si comporta come -exec, con la differenza che
per ogni file scelto viene chiesto all'utente di confermare il comando; se la
risposta inizia per y o Y viene eseguito, altrimenti no, e l'azione
restituisce il valore falso.
Esistono numerosi operatori: eccone una lista, in ordine di precedenza decrescente.
Beh sì, find ha proprio troppe opzioni, lo so, ma ci sono un sacco di modi predefiniti di usarlo che vale la pena ricordare, dato che vengono usati molto spesso. Vediamone alcuni.
% find . -name foo\* -printtrova tutti i nomi di file che cominciano per foo. Se la stringa può trovarsi all'interno del nome, probabilmente è più indicato scrivere "*foo*" invece di \*foo\*.
% find /usr/include -xtype f -exec grep foobar \ /dev/null {} \;è un grep eseguito ricorsivamente a partire dalla directory /usr/include. In questo caso ci interessano sia il file regolare che i link simbolici che puntano a file regolari, da cui il test -xtype. Molte volte è più semplice evitare di specificarlo, specialmente se siamo piuttosto sicuri che non esistono file binary che contengono la stringa desiderata. E perché il /dev/null nel comando? È un trucco per forzare grep a scrivere il nome del file dove trova una corrispondenza. Il comando grep viene applicato a ciascun file in un'invocazione diversa, e quindi non pensa che sia necessario di dare come output il nome del file; ma ora ci sono due file, cioè il file corrente e /dev/null! Un'altra possibilità è fare una pipe del comando verso xargs e fargli fare il grep. L'ho appena provato ed ho distrutto del tutto il mio filesystem (anche queste note che sto cercando di recuperare a mano :-( ).
% find / -atime +1 -fstype ext2 -name core \ -exec rm {} \;è un tipico lavoro da crontab: cancella tutti i file core nei filesystem di tipo ext2 che non hanno avuto accessi nelle ultime 24 ore. È possibile che qualcuno voglia usare il file core per fare un dump post mortem, ma nessuno si può ricordare quello che stava facendo 24 ore prima...
% find /home -xdev -size +500k -ls > piggiesè utile sapere chi ha questi file che intasano il filesystem. Notate l'uso di -xdev: dato che ci interessa un solo filesystem, non è necesario scendere negli altri montati sotto /home.
Tenete a mente che find è un comando che prende moltissimo tempo, dato che deve accedere a tutti gli inode del sistema per operare. È quindi saggio combinare tutte le operazioni che vi servono in un'unica invocazione di find, specialmente nei lavori di 'pulizia' fatti da crontab. Un esempio illuminante è questo: supponiamo di voler cancellare i file che finiscono con .BAK e cambiare le protezioni di tutte le directory a 771 e quelle dei file che finiscono con .sh a 755. E magari stiamo montando dei filesystem via NFS su un collegamento via modem, e non vogliamo controllare quei file. Perché scrivere tre comandi separati? Il modo più efficace di portare a termine il compito è questo:
% 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 {} \; \)
Sembra brutto (e che spreco di barre rovesciate!), ma guardando attentamente rivela che la logica che c'è sotto è molto lineare. Ricordate che quello che fa in realtà è una valutazione vero/falso; il comando compreso è solo un effetto secondario. Ma ciò significa che viene fatto solo se find deve valutare la parte exec dell'espressione, cioè solo se il lato sinistro della sottoespressione rende vero. Quindi, se per esempio il file considerato al momento è una directory viene valutato il primo exec ed i permessi dell'inode vengono cambiati a 771; altrimenti se ne dimentica e passa alla seconda sottoespressione. Probabilmente è più semplice vederlo in pratica che scriverlo, ma dopo un po' diventerà una cosa naturale.