Skip to main content

Module 1 · Filesystem & Permissions

Links and File Search

  • Lesson 3/15
  • 30 min
  • Difficulty: Intermediate

This lesson is not available in English yet: you are reading it in Italian.

Objective

Scegliere fra collegamento fisico e simbolico in base al guasto che ciascuno produce, e scrivere una ricerca con find che selezioni l'insieme inteso invece di uno simile

Prerequisites

Prima di leggere: due domande sulle lezioni precedenti

Nella prima lezione hai scoperto che /bin è un collegamento simbolico verso usr/bin. Prima domanda: se qualcuno cancellasse /usr/bin/bash, che cosa resterebbe di /bin/bash? Seconda domanda: quanto spazio su disco occupa un collegamento simbolico a un file da quattro megabyte? Rispondi a entrambe prima di andare avanti.

Le due domande sembrano scollegate e misurano la stessa cosa: se hai capito che un collegamento simbolico non contiene il file, ma solo il suo indirizzo. La differenza fra i due tipi di collegamento che Linux offre si gioca tutta su questo punto, e sbagliarla costa un backup inutilizzabile o uno spazio disco che non torna mai.

Il problema: due nomi per lo stesso file, in due modi diversi

Ti serve che lo stesso contenuto sia raggiungibile da due percorsi. Il caso classico è una configurazione condivisa fra due servizi, oppure la versione attiva di un programma puntata da un nome stabile. Linux offre due meccanismi che sembrano fare la stessa cosa, hanno comandi quasi identici, e si rompono in modi opposti. Chi li confonde scopre la differenza nel momento peggiore: durante un ripristino.

Nella seconda metà della lezione c'è il problema gemello: una volta che i file sono tanti, trovarne uno non è più un gesto ma una ricerca, e il comando che la fa ha una sintassi che non somiglia a nessun altro comando Unix.

Il nome non è il file

Su un filesystem come ext4 il file vero è una struttura chiamata inode, che contiene i permessi, le date, e i puntatori ai blocchi di dati. Il nome che vedi in una cartella è soltanto una voce che associa una stringa a un numero di inode. Quindi un file non ha un nome: ha uno o più nomi, e l'inode conta quanti sono.

Un collegamento fisico, che si crea con ln, è un secondo nome per lo stesso inode. Un collegamento simbolico, che si crea con ln -s, è un file a sé, con un inode proprio, il cui contenuto è il percorso di destinazione scritto come testo. Da questa differenza di struttura discendono tutti i comportamenti, e si vedono in una sola riga di output.

$ echo "originale" > dato.txt
$ ln dato.txt hard.txt
$ ln -s dato.txt soft.txt
$ ls -li dato.txt hard.txt soft.txt
81179682 -rw-rw-r-- 2 federicocalo federicocalo 10 Oct 30 17:43 dato.txt
81179682 -rw-rw-r-- 2 federicocalo federicocalo 10 Oct 30 17:43 hard.txt
81179683 lrwxrwxrwx 1 federicocalo federicocalo  8 Oct 30 17:43 soft.txt -> dato.txt

La prima colonna è il numero di inode, che l'opzione -i aggiunge. dato.txt e hard.txt hanno lo stesso numero: sono lo stesso file. soft.txt ha un numero diverso. La colonna successiva ai permessi è il conteggio dei nomi: 2 per i primi due, 1 per il collegamento simbolico. E la dimensione di soft.txt è 8, che sono esattamente le lettere di dato.txt: il contenuto del collegamento è la stringa del percorso.

Il confronto che decide quale usare

La prova che separa i due meccanismi è cancellare l'originale. Se il collegamento fisico fosse una scorciatoia, morirebbe con l'originale. Non muore, perché non esiste un originale: esistono due nomi pari fra loro, e cancellarne uno abbassa il conteggio da due a uno.

$ rm dato.txt
$ cat hard.txt
originale
$ cat soft.txt
cat: soft.txt: No such file or directory
$ ls -l soft.txt
lrwxrwxrwx 1 federicocalo federicocalo 8 Oct 30 17:43 soft.txt -> dato.txt

Il collegamento fisico ha il contenuto. Il collegamento simbolico esiste ancora come file, lo vedi in ls, ma punta a un nome che non c'è più: è un collegamento rotto, e si trova con find . -xtype l, che sulla mia prova ha risposto ./soft.txt.

Le differenze che contano sono quattro, e vanno sapute prima di scegliere. Un collegamento fisico non può attraversare filesystem diversi, perché il numero di inode ha senso solo dentro un filesystem, mentre il simbolico può puntare dove vuole, anche a un percorso che non esiste ancora. Un collegamento fisico non può puntare a una cartella, il simbolico sì. Il simbolico si rompe se la destinazione si sposta, il fisico no. E solo il simbolico ti dice, guardandolo, dov'è il contenuto.

L'errore di classificazione plausibile riguarda lo spazio su disco, e si vede con du. Due nomi dello stesso inode occupano i blocchi una volta sola, e du se ne accorge se gli passi entrambi i nomi nella stessa invocazione, ma non se glieli passi separatamente:

$ du -ch reale.bin copia-hard.bin
4,0M	reale.bin
4,0M	total
$ du -h reale.bin
4,0M	reale.bin
$ du -h copia-hard.bin
4,0M	copia-hard.bin

Nella prima invocazione copia-hard.bin non compare affatto e il totale è quattro megabyte, perché du riconosce l'inode già contato. Nelle due invocazioni separate ciascun nome dichiara quattro megabyte, e sommandole a mano otterresti otto. La virgola decimale nell'output dipende dalla lingua del mio sistema: con LC_ALL=C davanti al comando ottieni il punto, e questo conta quando l'output lo legge uno script.

Esempio svolto: trovare i log vecchi senza cancellare quelli nuovi

Il problema: una cartella di log è cresciuta e devi individuare i file più vecchi di una settimana per archiviarli. Il requisito implicito, quello che fa danno se lo ignori, è non toccare i file recenti e non toccare le cartelle.

L'informazione disponibile: find ha un criterio sull'età in giorni, -mtime, e un criterio sul tipo, -type. Il ragionamento sulla sintassi è il passaggio in cui si sbaglia: find non prende opzioni come gli altri comandi, prende un'espressione di condizioni valutate in ordine, e il risultato della valutazione decide se il file entra nell'elenco. Quindi l'ordine delle condizioni non è decorativo.

$ find . -name '*.log'
./log/vecchio.log
./log/app.log
./log/zero.log

$ find . -type f -name '*.log' -mtime +7
./log/vecchio.log

$ find . -empty
./vuota
./log/zero.log

La prima ricerca trova tutti i log. La seconda aggiunge due condizioni e isola l'unico file più vecchio di sette giorni. La terza mostra una cosa che vale la pena notare: -empty trova anche la cartella vuota, perché senza -type f il criterio si applica a qualunque voce.

La decisione che prendo: le virgolette attorno a '*.log' ci stanno sempre. L'alternativa, scriverlo nudo, funziona finché la cartella da cui lanci il comando non contiene file che corrispondono al modello: in quel caso la shell espande l'asterisco prima che find lo veda, e find riceve dei nomi concreti al posto del modello. Il risultato è una ricerca che cerca la cosa sbagliata, senza nessun messaggio di errore.

La verifica: cambia cartella e rilancia lo stesso comando. Se il risultato dipende da dove ti trovi quando non dovrebbe, le virgolette mancano. L'errore che avrei potuto commettere e che ho visto fare è passare direttamente a -delete per fare in fretta: -delete implica -depth e cambia l'ordine di visita, quindi la stessa espressione può selezionare un insieme diverso. Prima si elenca, si guarda l'elenco, e solo poi si cancella.

Il caso negativo: tre comandi che sembrano sinonimi

Qualcuno ti dice che ip sta in /usr/bin. Tu verifichi e trovi due risposte diverse dai due comandi che dovrebbero rispondere alla stessa domanda. Prova a dire quale delle due è utile e perché, prima di leggere il seguito.

$ which ip
/usr/sbin/ip
$ whereis ip
ip: /usr/bin/ip /usr/sbin/ip /usr/share/man/man8/ip.8.gz /usr/share/man/man7/ip.7.gz

Le due risposte non si contraddicono: rispondono a due domande diverse. which dice quale file verrebbe eseguito adesso, con il tuo PATH, e la risposta è /usr/sbin/ip. whereis cerca in posizioni standard e nelle pagine di manuale, e trova tutte le copie esistenti, incluse quelle che il tuo PATH non raggiungerebbe. Se stai diagnosticando «perché viene eseguito il programma sbagliato», la risposta utile è la prima.

Il terzo comando è quello che nessuno usa e che spesso serve: type -a, che è interno alla shell e vede anche ciò che un file non è. Su questo sistema type -a cd risponde che cd è un comando interno di shell, cioè non esiste nessun file da trovare, e per questo which cd non è la domanda giusta. Il messaggio compare nella lingua del sistema, quindi sulla tua macchina potrebbe essere in inglese.

Sul quarto strumento che il programma del modulo nomina, locate, una precisazione misurata: su questa installazione di Ubuntu 26.04 non è presente. Il comando command -v locate non restituisce nulla, e apt-cache policy plocate dichiara Installato: (nessuno) con candidato 1.1.23-1ubuntu3. Quindi locate va installato prima di usarlo, e la sua velocità viene da un indice costruito periodicamente: risponde in fretta e risponde sul passato, mentre find è lento e risponde sul presente.

Esercizio 1, con un passaggio mancante

Devi trovare, sotto la tua cartella personale, tutti i file più grandi di dieci megabyte modificati negli ultimi due giorni, e stampare per ciascuno il percorso e la dimensione. Due condizioni sono scritte, la terza e il formato di stampa li scrivi tu.

$ find "$HOME" -type f -size +10M ??????? -printf '???'

Esercizio 2, autonomo: un collegamento che non si rompe

Hai due versioni di un programma in /opt/app-1.2 e /opt/app-1.3, e vuoi un percorso stabile /opt/app-attuale da cui i servizi leggono, che tu possa spostare da una versione all'altra in un gesto e riportare indietro se qualcosa va male. Decidi quale dei due tipi di collegamento usare, costruiscilo in una cartella di prova, e poi provoca il guasto: rinomina la cartella di destinazione e osserva che cosa succede al percorso stabile. Criterio di successo: sai dire quale dei due tipi hai scelto, perché l'altro non andava bene, e che cosa vede un servizio nell'istante in cui la destinazione non c'è.

Soluzioni commentate

Esercizio 1. La condizione mancante è -mtime -2 e il formato è -printf '%p %s\n'. Il ragionamento sul segno è la parte che conta: con -mtime il meno significa «meno di», quindi -2 sono i file modificati negli ultimi due giorni, mentre +2 sono quelli più vecchi di due giorni. Scrivere -mtime 2 senza segno significa «esattamente nel giorno numero due», che è un insieme quasi sempre diverso da quello che intendevi e quasi sempre vuoto.

Sul formato, %p è il percorso e %s la dimensione in byte. Perché non -exec ls -l {} \;: funzionerebbe, ma avvia un processo per ogni file trovato, e su diecimila file la differenza si misura in minuti. -printf stampa dal processo che sta già girando. Perché non -ls: stampa un formato fisso che include dati che non ti servono, e se l'output lo legge uno script le colonne in più sono un problema.

Una cautela sulla portabilità: -printf è un'estensione di GNU findutils, e su questa macchina ho verificato la versione con find --version, che risponde find (GNU findutils) 4.10.0. Su un sistema BSD o in un container minimale -printf può non esserci, e in quel caso la strada portabile è -exec stat -c '%n %s' {} +, dove il più finale raggruppa i file in poche invocazioni invece di una per file.

Esercizio 2. Serve un collegamento simbolico, e le ragioni per cui il fisico non va sono due, entrambe dirimenti. La prima: un collegamento fisico non può puntare a una cartella, quindi il requisito è tecnicamente impossibile. La seconda, che conta anche se lavorassi su un singolo file: un collegamento fisico punta all'inode, non al nome, quindi non lo puoi «ripuntare» altrove. Il gesto che ti serve è ln -sfn /opt/app-1.3 /opt/app-attuale, dove -f sostituisce il collegamento esistente e -n evita che il collegamento venga creato dentro la cartella già puntata, che è l'errore più comune di questo comando.

Sul guasto provocato: nell'istante in cui la destinazione non c'è, il servizio non riceve un collegamento vuoto, riceve No such file or directory sul percorso completo, come hai visto nella prova con cat soft.txt. È un errore buono, perché è immediato e nomina il file. La cosa da cui diffidare è il contrario: se usassi una copia invece di un collegamento, il servizio continuerebbe a leggere la versione vecchia senza nessun segnale, e scopriresti il problema quando un comportamento atteso non si manifesta.

Domande per fissare il concetto

Perché un collegamento fisico non può attraversare due filesystem diversi, se parti dal fatto che è una voce di cartella che punta a un numero di inode? Che cosa succede allo spazio su disco se cancelli un file che ha tre nomi, e in quale momento i blocchi vengono liberati? E se un backup copiasse i collegamenti fisici come file distinti, quanto spazio occuperebbe il ripristino rispetto all'originale?

Che cosa adesso sai fare

Sai distinguere un collegamento fisico da uno simbolico leggendo due colonne di ls -li, il numero di inode e il conteggio dei nomi, e sai prevedere quale dei due sopravvive alla cancellazione della destinazione. Sai costruire un percorso stabile verso una versione, e sai spostarlo con un comando solo. Sai scrivere una ricerca con find mettendo le virgolette dove servono, e sai che il segno davanti a -mtime cambia l'insieme selezionato.

La verifica concreta: cerca i collegamenti rotti sotto la tua cartella personale con find "$HOME" -xtype l. Ognuno di quelli è un pezzo di sistema che punta a qualcosa che non c'è più, e adesso sai leggere che cosa si è spostato.