Linux tips and tricks/Operazioni su file e directory

Indice del libro

Attraverso la shell di Linux, sia essa Bash, Korn, Csh o altro, è possibile eseguire numerosi comandi che, opportunamente combinati fra loro e con le funzionalità delle shell, consentono di manipolare grandi quantità di file e directory che, tramite interfaccia grafica, non sarebbe possibile gestire in modo altrettanto fine, se non uno alla volta.

Nel seguito si farà riferimento alla shell Bash 3.0, ma gran parte delle operazioni si possono certamente riprodurre, con le dovute modifiche, con le altre shell.

Cercare file e directory: il comando find modifica

Il comando GNU dedicato a questo preciso scopo è find. Le distribuzioni derivate da Debian lo forniscono nel pacchetto findutils. Nella forma più elementare, si può usare così:

$ find PATH

essendo PATH un qualsiasi percorso, assoluto o relativo. Il risultato sarà l'elenco di tutti i file, di qualsiasi tipo, contenuti nell'albero di cui PATH è la radice (questa compresa). Ad esempio:

$ find /var/log | head 
/var/log
/var/log/wtmp
/var/log/btmp
/var/log/lastlog
/var/log/ksymoops
/var/log/news
/var/log/news/news.crit
/var/log/news/news.err
/var/log/news/news.notice
/var/log/news/news.crit.1.gz

Nota - l'output di find non è ordinato alfabeticamente, ma in base all'ordinamento delle directory nel file system. Non esistono opzioni di find per modificare tale ordinamento, per cui occorre ricorrere ad altri comandi, principalmente al comando sort, per ordinare l'output in base alle proprie esigenze.

find ha numerose opzioni, illustrate nel relativo manuale in linea. Esse possono essere distinte in due grandi categorie: test e azioni. I test servono a selezionare i file da elencare, mentre le azioni consentono di eseguire diverse operazioni sui file selezionati in base ai test.

Test per tipo di file modifica

Un test elementare ma molto utile è attuabile con l'opzione -type, che seleziona i file in base al loro tipo, dal punto di vista del file system. Consente quindi di distinguere fra loro file regolari, directory, link simbolici, socket, pipe e quant'altro. Ad esempio, il seguente comando elenca i primi cinque socket trovati in /tmp.

$ find /tmp/ -type s -printf "%y %p\n" | head -5
s /tmp/.X11-unix/X0
s /tmp/.ICE-unix/6161
s /tmp/.gdm_socket
s /tmp/.winbindd/pipe
s /tmp/ssh-rcETKV6161/agent.6161

In questo esempio è stata anche usata l'azione -printf per mostrare, per ciascun file corrispondente al test, il tipo di file ('%y%') oltre al nome del file stesso ('%p'). Non a caso, questa azione usa la tipica sintassi della funzione printf del linguaggio C.

L'uso degli altri valori per il test -type è illustrato sotto:

f
seleziona i file regolari.
d
seleziona le directory.
l
seleziona i link simbolici.
p
seleziona i named pipe.
b, c
seleziona rispettivamente i dispositivi a blocchi (bufferizzati) e a carattere (non bufferizzati).

Test per data e ora modifica

Spesso è utile rintracciare file in base alla data e all'ora di modifica, accesso o altro. Ad esempio, volendo eliminare file non acceduti da molto tempo, e quindi presumibilmente poco importanti, si può usare il seguente comando, che elenca i primi cinque file regolari in /opt/tmp acceduti più di 365 giorni fa, riportandone data di accesso e nome:

$ find /opt/tmp -type f -atime +365 -printf "%a %p\n" | head -5
Fri Jul 22 15:03:35 2005 /opt/tmp/opo-001-C001.dvdrip-info
Fri Jul 22 15:03:35 2005 /opt/tmp/opo-001-C001.mpa
Fri Jul 22 15:04:28 2005 /opt/tmp/opo-001-C001.m1v
Fri Jul 22 15:03:38 2005 /opt/tmp/opo-001-C001-1.mpg
Fri Jul 22 15:03:35 2005 /opt/tmp/opo-001-C002.mpa

L'esempio può essere rifinito sostituendo, nell'azione -printf, la sequenza %a con la sequenza %A+, che mostra la data e l'ora in formato numerico, cominciando dall'anno e arrivando ai secondi. Il vantaggio è che un simile output può essere ordinato con il comando sort in modo da vedere i cinque file acceduti per primi, fra quelli selezionati:

$ find /opt/tmp -type f -atime +365 -printf "%A+ %p\n" | sort | head -5
2003-02-24+15:30:16 /opt/tmp/01/opac.sbn.it-access.log.16.gz
2003-02-24+15:30:28 /opt/tmp/01/opac.sbn.it-access.log.17.gz
2003-02-24+15:30:34 /opt/tmp/01/opac.sbn.it-access.log.18.gz
2003-02-24+15:30:35 /opt/tmp/01/opac.sbn.it-access.log.19.gz
2003-02-24+15:30:49 /opt/tmp/01/opac.sbn.it-access.log.20.gz

Come si vede, il risultato è completamente diverso, perché nel primo esempio erano stati scelti, dopo l'esecuzione di find, i cinque file che, secondo l'ordinamento originale nel file system, risultavano primi. Ma nel caso in esame, come in molti altri, questo ordinamento non ha alcuna utilità.

Il valore dell'opzione -atime può anche essere negativo, ad indicare accessi avvenuti meno di n giorni fa, o privo di segno, ad indicare accessi avvenuti esattamente n giorni fa. Frazioni di giorno sono ignorate, quindi -atime +1 significa almeno due giorni fa, perché il primo intero maggiore di 1 è appunto 2.

Nel complesso, le opzioni di questo tipo sono illustrate di seguito:

-atime +n/-n/n
seleziona i file acceduti più di/meno di/esattamente n giorni fa
-ctime +n/-n/n
seleziona i file che hanno cambiato stato più di/meno di/esattamente n giorni fa
-mtime +n/-n/n
seleziona i file modificati più di/meno di/esattamente n giorni fa

Un tipo di test analogo, in cui le unità di tempo sono minuti invece di giorni, si può avere con le opzioni descritte di seguito:

-amin +n/-n/n
seleziona i file acceduti più di/meno di/esattamente n minuti fa
-cmin +n/-n/n
seleziona i file che hanno cambiato stato più di/meno di/esattamente n minuti fa
-mmin +n/-n/n
seleziona i file modificati più di/meno di/esattamente n minuti fa

Esistono poi test che consentono di confrontare la data dei file da cercare con quella di un file prestabilito. Questo uso è più raro: tipicamente si ha quando un processo crea un file vuoto soltanto per segnalare il momento in cui è stato avviato, e poi si ha la necessità di individuare i file creati o modificati prima o dopo quel momento.

I tre test di questo gruppo richiedono come parametro un file. Il loro funzionamento è illustrato di seguito:

-newer file
seleziona i file modificati più recentemente di file
-anewer file
seleziona i file acceduti più recentemente di file
-cnewer file
seleziona i file che hanno cambiato stato più recentemente di file

Test su nomi e percorsi modifica

L'output di un comando find può naturalmente essere filtrato con qualsiasi altro comando, ad esempio grep e awk, per selezionare ulteriormente i file in base al nome. In realtà find stesso può effettuare diversi test simili senza ricorrere ad altri comandi. Alcuni di questi test sono illustrati sotto. La maggior parte prevede una versione case insensitivi, distinta dalla i iniziale:

-name pattern
è il test più semplice, che seleziona i file che, oltre ad eventuali altri test, hanno un nome (privato delle eventuali directory del percorso) che corrisponde a pattern; questa stringa è riconosciuta dalla shell, e quindi può contenere i caratteri jolly normalmente usati per selezionare file direttamente a livello di shell, come '?' e '*'; di norma il pattern deve essere racchiuso fra doppi apici (").
-wholename pattern
come -name, ma agisce sull'intero nome del file, directory comprese; in versioni molto vecchie si poteva usare il sinonimo -path, che però in seguito è stato deprecato.
-regex pattern
come -wholename, ma il pattern è un'espressione regolare; si tratta quindi di un test molto fine, che consente di selezionare file in base a condizioni molto precise.

Test su utenti, gruppi e permessi modifica

Altro gruppo di test fondamentali è quello relativo ai permessi di accesso ai file. Trovare file sparsi appartenenti ad un dato utente o gruppo, oppure individuare file di rilevanti in termini di sicurezza, ma con permessi troppo blandi, sono tipiche attività che find consente di svolgere facilmente.

Ecco i principali test di questo gruppo:

-user uname
seleziona i file appartenenti all'utente uname; sono ammessi sia nomi alfabetici che gli uid numerici; questi ultimi sono utili per cercare file rimasti in giro dopo la rimozione di un utente.
-group gname
come -user, ma per i gruppi.
-perm mode
seleziona i file che hanno esattamente i permessi indicati da mode, e nessun altro; è ammessa sia la notazione ottale (ad esempio, 0644), sia quella simbolica (ad esempio, u=rw); una versione più pratica è la seguente.
-perm -mode
simile a -perm, ma seleziona qualunque file che abbia almeno tutti i permessi indicati, e non soltanto quelli; è sicuramente più pratica della versione standard; in sostanza è come fare un AND fra test distinti.
-perm /mode
altra versione debole del test sui permessi: seleziona i file che abbiano almeno uno qualunque dei permessi indicati; in sostanza è una specie di OR fra test distinti.
-nouser, -nogroup
seleziona i file che non appartengono ad un utente (o a un gruppo) fra quelli noti al sistema; normalmente simili file sono sospetti o quanto meno inutili (in effetti non dovrebbero esistere).

Test composti: operatori logici modifica

I test discussi, e tutti gli altri descritti nel manuale di find, possono essere combinati fra loro con gli usuali operatori logici AND, OR e NOT, come segue:

test1 -a test2
entrambi i test sono eseguiti, e un file è selezionato solo se entrambi hanno risultato vero; equivale a test1 test2.
test1 -o test2
viene eseguito test1, e solo se esso è falso viene eseguito test2; un file è selezionato solo se supera uno dei due test.
! test
un file è selezionato solo se test, eseguito su di esso, risulta falso .

Azioni di visualizzazione modifica

Per ciascuno dei file selezionati in base ai test, find può eseguire un cospicuo numero di azioni, alcune anche piuttosto complesse. Ogni azione ha un valore vero o falso, che può essere usato per verificarne l'esito. Alcune azioni hanno risultato sempre vero o sempre falso.

Cominciamo dalle azioni più scontate, cioè quelle che servono semplicemente a mostrare i file selezionati.

-print
praticamente equivale ad invocare find senza azioni: il risultato è identico.
-printf format
mostra i file selezionati secondo il formato format. Il formato è descritto da una stringa che si ispira alla nota funzione C standard omonima. Più avanti vengono illustrate le direttive % più utili e comunemente usate.
-fprint file
come -printf, ma l'output viene inviato a file.
-fprintf file format
come -fprint, ma in più si può specificare un formato, come in -printf.
-print0
come -print, ma ogni file viene mostrato con un carattere null (\0) alla fine, come nelle stringhe in C. Questo rende più facile separare i risultati se devono essere interpretati successivamente da qualche programma. Un esempio è il potente comando xargs, che esegue un comando qualsiasi su tutti i file che riceve in input, e che ha un'opzione -0 proprio per separare i file in input in base al carattere null.
-fprint0 file
come -print0, ma il risultato è inviato a file.
-ls
equivale a mostrare tutti i file selezionati col comando ls -dils.
-fls file
come -ls, ma l'output è inviato a file.

Azioni di rimozione modifica

Esiste un'unica opzione esplicitamente dedicata alla rimozione dei file selezionati:

-delete
cancella ognuno dei file selezionati, con valore vero se ci riesce, altrimenti con un messaggio di errore.

Azioni avanzate modifica

Le azioni qui descritte consentono in pratica di eseguire qualsiasi comando, più o meno complesso, su ogni file selezionato in base ai test. Inutile dire che possono avere effetti devastanti se non sono utilizzate con molta prudenza.

-exec comando {} ;
esegue comando per ogni file selezionato. Il nome del file va indicato esplicitamente con la notazione {}'. Il punto e virgola finale è necessario ma, quando il comando è eseguito da una shell, va scritto come \; per evitare che la shell lo interpreti. Da notare che comando è eseguito nella directory in cui è invocato find.
-exec comando {} +
esegue comando una sola volta, passandogli come argomento l'intero elenco di file trovati. Se comando accetta già di per sé un numero arbitrario di argomenti, questa sintassi può avere risultati del tutto equivalenti alla precedente, ma in generale c'è una notevole differenza.
-execdir comando {} ;, -exec comando {} +
come le due azioni precedenti, ma comando è eseguito nella directory contenente il file selezionato, per ciascuno dei file selezionati. La versione + non esegue il comando una sola volta, ma tante volte quante sono le sottodirectory incontrate, e per ciascuna di esse esegue comando passandogli come argomenti tutti i file selezionati in quella directory.

Alcuni tipici impieghi di find modifica

Vediamo ora alcuni impieghi di find, più o meno complessi. L'idea di fondo è sempre quella di effettuare una data operazione su parecchi file contemporaneamente, e di usare find per selezionare i file giusti con notevole precisione.

Semplice backup incrementale modifica

Non è certo questo il luogo in cui parlare delle tecniche di backup più evolute, ma in molte situazioni è ampiamente sufficiente raccogliere dei file, organizzarli insieme ed eventualmente comprimerli, e infine salvare il risultato su nastro o più semplicemente su qualche tipo di supporto ottico, soluzione casalinga ma efficace.

Vediamo in dettaglio cosa si vuole ottenere:

  1. si voglio archiviare alcuni file e directory, non il contenuto di una singola directory;
  2. il backup deve avvenire periodicamente, e si vogliono salvare solo i file modificati dopo l'ultimo backup, realizzando un semplice backup incrementale;
  3. il risultato dev'essere un file ZIP.

Il comando seguente risolve il problema:

$ find $(cat include.find) -newer last.find | zip backup.zip -@ ; touch last.find

Per prima cosa, ogni backup, al termine delle operazioni, aggiorna la data del file last.find (comando touch finale). In questo modo, il test -newer last.find seleziona proprio i file modificati dall'ultimo backup.

Il file include.find contiene l'elenco dei file e delle directory da archiviare, uno per riga. La sintassi $(...) significa che gli argomenti di find saranno l'output del comando cat, cioè proprio i file e le directory volute.

Il comando zip preleva infine l'output di find (-@) e archivia tutti i file trovati da find nel file backup.zip.

Rimozione di file inutilizzati modifica

Se un file non è acceduto da due anni, è altamente improbabile che serva ancora a qualcosa. O almeno è improbabile che il suo proprietario ricordi a cosa serviva.

Il comando seguente crea un file contenente tanti comandi rm -rf quanti sono i file nella home dell'utente che non sono stati neanche aperti da due anni:

$ find ~ -atime +730 -exec echo "rm -rf {}" \; > elimina.find

Il file elimina.find potrà poi essere esaminato con un editor di testo alla ricerca di qualche file importante, e infine eseguito in una shell per fare piazza pulita.

Rimozione di file sospetti modifica

Eventuali file privi di utente o di gruppo non dovrebbero esistere in un sistema sano. Cercarli è facile, con un comando simile al precedente, test a parte:

$ find ~ -nogroup -or -nouser -exec echo "rm -rf {}" \; > elimina.find

Anche in questo caso, è prudente esaminare il risultato, prima di passare alla cancellazione effettiva. Da notare l'uso della congiunzione -or per selezionare file privi di utente o gruppo, o di entrambi.