Non riesci a smontare una chiavetta ("target is busy"). Il disco è pieno, ma du non trova i file che lo riempiono. Una porta risulta occupata e non sai da quale programma. Tre problemi diversi con la stessa domanda: chi sta usando questo file? lsof risponde a questa domanda, e in Linux "file" vuol dire quasi tutto.
Cos'è
lsof (list open files, "elenca i file aperti") è un programma storico dell'amministrazione dei sistemi Unix. Su Debian e Ubuntu va spesso installato a parte (sudo apt install lsof, vedi l'articolo su apt). Il suo punto di forza deriva da un principio di Unix: "tutto è un file". Per un processo sono file aperti non solo i documenti e i registri, ma anche le cartelle in cui si trova, le librerie caricate, i terminali, le connessioni di rete e le porte in ascolto. lsof li elenca tutti, in una sola tabella.
Cosa fa
Elenca i file aperti dai processi in esecuzione, con il processo e l'utente che li tengono aperti. Si può chiedere "tutto quello che ha aperto questo processo" oppure, al contrario, "chi ha aperto questo file, questa cartella o questa porta". È il complemento di ps (che dice quali processi esistono) e di ss (che dice quali connessioni ci sono): lsof li collega ai file.
Come si usa
$ sudo lsof [opzioni] [file o cartella]Senza sudo si vedono solo i processi dell'utente; per vedere quelli degli altri servono i privilegi di amministratore (vedi l'articolo su sudo). Senza opzioni, lsof elenca tutti i file aperti dal sistema, migliaia di righe: nella pratica si usa sempre con un filtro. Gli esempi usano il server web01 degli articoli precedenti.
Esempio 1: cosa ha aperto un processo
$ sudo lsof -p 925
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
nginx 925 root cwd DIR 254,0 4096 2 /
nginx 925 root txt REG 254,0 1318416 262411 /usr/sbin/nginx
nginx 925 root 2w REG 254,0 8650 262977 /var/log/nginx/error.log
nginx 925 root 6u IPv4 15033 0t0 TCP *:80 (LISTEN)
nginx 925 root 8w REG 254,0 4521338 262978 /var/log/nginx/access.logCon -p si indica il numero di un processo (il PID, che si trova con ps: vedi l'articolo su ps e kill). L'elenco reale è più lungo (qui ne ho mostrato solo alcune righe). Le colonne:
COMMAND,PID,USER: il programma, il suo numero e l'utente con cui gira;FD(file descriptor): come il file è aperto.cwdè la cartella corrente,txtil programma stesso,memle librerie caricate in memoria; un numero come2o8è un file aperto dal programma, seguito dar(lettura),w(scrittura) ou(entrambe). I numeri 0, 1 e 2 sono gli ingressi e le uscite standard (vedi gli articoli suechoetee);TYPE:REGfile normale,DIRcartella,CHRdispositivo (come/dev/null),IPv4eIPv6connessioni di rete,unixcomunicazioni interne tra programmi,FIFOcanali (pipe);DEVICE,SIZE/OFF,NODE: il disco, la dimensione (o la posizione di lettura) e il numero del file nel file system: servono di rado;NAME: il nome del file, o l'indirizzo e la porta per le connessioni.
Esempio 2: chi sta usando questo file o questa cartella?
$ sudo lsof /var/log/nginx/access.log
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
nginx 925 root 8w REG 254,0 4521338 262978 /var/log/nginx/access.log
nginx 931 www-data 8w REG 254,0 4521338 262978 /var/log/nginx/access.log
$ sudo lsof /mnt/usb
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
bash 4102 enzo cwd DIR 8,33 4096 2 /mnt/usb
$ sudo lsof +D /var/www/htmlSe passi il nome di un file, lsof mostra chi lo tiene aperto. Se è un file system montato (come /mnt/usb), mostra chi lo sta usando: è la risposta al problema "target is busy" dell'articolo su mount: qui una shell di enzo ha la cartella come cartella corrente (cwd). Con +D si cerca ricorsivamente in tutta una cartella: utile ma lento, perché esamina tutti i file sottostanti.
Esempio 3: filtrare per utente, programma o processo
$ sudo lsof -u www-data
$ sudo lsof -u ^root
$ sudo lsof -c nginx
$ sudo lsof -a -u enzo -c bash
$ sudo lsof -p 925,931-u filtra per utente (con ^ davanti si escludono: tutti tranne root), -c per il nome del programma (anche una parte iniziale), -p per i numeri di processo (più di uno, separati da virgole). Attenzione: più filtri si sommano (lsof -u enzo -c nginx mostra i file di enzo oppure quelli di nginx). Per richiedere che valgano tutti insieme si aggiunge -a (and), come nel quarto comando: solo i file di enzo aperti da bash.
Esempio 4: le porte e le connessioni di rete
$ sudo lsof -i :80
$ sudo lsof -iTCP -sTCP:LISTEN -P -n
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
sshd 812 root 3u IPv4 14520 0t0 TCP *:22 (LISTEN)
nginx 925 root 6u IPv4 15033 0t0 TCP *:80 (LISTEN)
nginx 925 root 7u IPv4 15034 0t0 TCP *:443 (LISTEN)
mariadbd 1034 mysql 21u IPv4 16410 0t0 TCP 127.0.0.1:3306 (LISTEN)L'opzione -i (internet) seleziona le connessioni di rete. Da sola le mostra tutte; con :80 solo quelle sulla porta 80; con TCP o UDP solo quel protocollo; con @192.168.1.20 solo quelle verso quell'indirizzo. -sTCP:LISTEN limita ai programmi in ascolto, cioè alle porte aperte. Il comando risponde a "quale programma ha aperto questa porta?", ed è utile quando un servizio non parte perché la porta è già occupata. Le opzioni -P e -n evitano di tradurre i numeri in nomi (la porta 80 in http, l'indirizzo in un nome di computer): l'output è più chiaro e il comando molto più veloce. Lo stesso lavoro lo fa ss -tlnp (vedi l'articolo su ss); lsof è utile perché usa lo stesso formato per file e connessioni.
Esempio 5: le connessioni attive, riassunte
$ sudo lsof -nP -i TCP:443 -sTCP:ESTABLISHED | awk 'NR>1 {print $1, $2, $9, $10}'
nginx 931 192.168.1.10:443->203.0.113.45:51822 (ESTABLISHED)
nginx 931 192.168.1.10:443->198.51.100.7:40310 (ESTABLISHED)Le connessioni in corso (ESTABLISHED) mostrano l'indirizzo locale e quello remoto, separati da una freccia. Qui i due indirizzi remoti sono quelli di due visitatori della porta 443 (i numeri sono inventati). Con awk (vedi l'articolo relativo) si tengono solo i campi che servono: NR>1 salta l'intestazione. Utile per scoprire da dove arrivano i visitatori o per riconoscere un indirizzo sospetto.
Esempio 6: il disco è pieno, ma i file non si trovano
$ df -h /var
File system Dim. Usati Dispon. Uso% Montato su
/dev/sda2 226G 219G 1,2G 99% /
$ sudo du -sh /var/log
1,1G /var/log
$ sudo lsof +L1
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NLINK NODE NAME
php-fpm 1120 www-data 5w REG 254,0 3221225472 0 262900 /var/log/app.log (deleted)
$ sudo truncate -s 0 /proc/1120/fd/5Un caso classico: df dice che il disco è al 99%, ma du non trova abbastanza file. La spiegazione è che qualcuno ha cancellato un file con rm mentre un programma lo teneva ancora aperto: il nome scompare, ma lo spazio resta occupato finché il programma non lo chiude. lsof +L1 elenca i file con meno di un collegamento (NLINK a 0), cioè cancellati ma ancora aperti, e li riconosci dalla scritta (deleted) in NAME. Qui un registro da 3 GB, tenuto aperto da php-fpm. Per liberare lo spazio senza interrompere il servizio si svuota il file tramite il suo "percorso nascosto" sotto /proc (/proc/PID/fd/NUMERO, con il PID e il numero di FD letti dalla tabella); oppure si riavvia il servizio (vedi l'articolo su systemctl). Capita spesso quando si cancellano a mano i registri di un servizio acceso, invece di svuotarli o di lasciar fare a logrotate. L'output è illustrativo, ma il meccanismo l'ho provato.
Esempio 7: i numeri dei processi per gli script
$ sudo lsof -t -i :8080
4410
$ sudo lsof -ti :8080 | xargs -r kill
$ if lsof -i :8080 -sTCP:LISTEN -t >/dev/null; then
> echo "porta occupata"
> fiL'opzione -t (terse, "conciso") stampa soltanto i numeri dei processi, uno per riga: è fatta per comporre comandi. Il secondo comando usa xargs (vedi l'articolo su tee e xargs) per terminare con kill (vedi l'articolo su ps e kill) il programma che occupa la porta 8080; l'opzione -r di xargs evita di eseguire kill senza argomenti se non c'è niente. Prima di usarlo, guarda sempre chi è con la forma completa. Il terzo comando usa il codice di uscita di lsof, che è 0 se ha trovato qualcosa e 1 se non ha trovato niente: perfetto per un if.
Esempio 8: ripetere e osservare
$ sudo lsof -r 2 -i :80
$ sudo lsof -p $$
$ sudo lsof -n -w | awk 'NR>1 {print $1}' | sort | uniq -c | sort -nr | head -n 3
272 claude
252 environme
143 rclone-fiCon -r 2 (repeat) lsof ripete la richiesta ogni 2 secondi fino a Ctrl+C: comodo per vedere comparire o sparire una connessione. $$ è il numero della shell in cui stai lavorando: lsof -p $$ mostra i file aperti da lei. L'ultimo comando conta quante righe ha ciascun programma (con awk, sort e uniq: vedi gli articoli relativi) per scoprire chi tiene più file aperti; i nomi e i numeri dell'esempio vengono dal sistema usato per provare il comando, e sul tuo server saranno altri. Con -w si nascondono gli avvisi.
Esempio 9: troppi file aperti
$ ulimit -n
1024
$ grep 'open files' /proc/1034/limits
Max open files 1048576 1048576 files
$ sudo lsof -p 1034 | wc -l
312Ogni programma ha un limite al numero di file che può aprire insieme: ulimit -n mostra quello della tua shell, e /proc/PID/limits quello di un processo in corso. Quando un programma lo supera, compare l'errore Too many open files: un servizio sotto carico smette di accettare connessioni, e il motivo non è evidente. Con lsof -p PID | wc -l (wc: vedi l'articolo relativo) si conta quanti file ha aperti: la riga di intestazione e le librerie contano come file, per cui il numero è approssimato. Se il conteggio sale nel tempo senza scendere (un programma che apre file e non li chiude), il difetto è nel programma; se è semplicemente vicino al limite, si alza il limite con la configurazione del servizio (per systemd con LimitNOFILE).
Esempio 10: uso negli script
#!/bin/bash
PORTA=8080
PID=$(lsof -t -i :"$PORTA" -sTCP:LISTEN | head -n 1)
if [ -n "$PID" ]; then
NOME=$(ps -p "$PID" -o comm=)
echo "La porta $PORTA è già usata da $NOME (PID $PID)" >&2
exit 1
fi
echo "Porta $PORTA libera: avvio il servizio"Prima di avviare un servizio, lo script controlla se la porta è già occupata e, se lo è, dice da chi (con ps -p PID -o comm= si ottiene il nome del programma, senza intestazione), invece di lasciare che il servizio fallisca con un messaggio oscuro. L'ho provato con un server finto sulla porta, e lsof restituisce nessuna riga quando la porta è libera.
Opzioni utili
-p PID: file aperti da un processo;-c nome: da un programma;-u utente: da un utente (^utenteper escluderlo).fileocartella: chi la usa;+D cartella: tutta la cartella, ricorsivamente (lento).-i: connessioni di rete;-i :porta,-iTCP,-i @indirizzo,-sTCP:LISTEN: filtri per porta, protocollo, indirizzo e stato.-a: i filtri valgono tutti insieme (altrimenti basta uno);-t: solo i numeri dei processi;-r N: ripete ogni N secondi.-n,-P: nessuna traduzione di indirizzi e porte (più veloce e più chiaro);-w: nasconde gli avvisi.+L1: file cancellati ma ancora aperti;-d cwd: solo le cartelle correnti;-F: formato per i programmi.fuser -vm: chi usa un file system (più semplice);ss -tlnp: porte in ascolto;ps: informazioni sul processo.
Attenzione a
- Serve
sudoper vedere tutto: da utente normalelsofmostra solo i processi dell'utente, e un "non trovato" può significare solo "non puoi vederlo". Anche consudoalcune righe di processi speciali possono riportare errori di permesso: sono normali, e si nascondono con-w. - Senza filtri è inutilizzabile: sono migliaia di righe, e sono dominate dalle librerie caricate (
mem). Parti sempre da una domanda precisa (un file, una porta, un processo). - I filtri multipli si sommano: è l'errore più comune.
lsof -u enzo -imostra i file dienzoe tutte le connessioni di rete di chiunque. Aggiungi-aper l'intersezione. - Usa
-ne-Pper le connessioni di rete: senza,lsofinterroga il DNS per ogni indirizzo e può metterci minuti, o restare fermo. Con-n -Pè immediato. +Dè lento: esamina tutti i file sotto una cartella e, su alberi grandi, può impiegare molto tempo e carico. Per una sola cartella è preferibile indicarla senza+D.- Non bloccarti sui dischi di rete: se un montaggio di rete (NFS) non risponde,
lsofpuò restare bloccato aspettando. In quel caso,-bevita le chiamate che potrebbero bloccarsi (con meno dettagli) e-wnasconde gli avvisi. - Prima di fermare un processo, verifica chi è:
lsof -ti :porta | xargs killè comodo, ma chiude qualunque programma tenga la porta, anche quello sbagliato. Controlla conlsof -i :portao conpsprima di agire (vedi l'articolo sukill). - Un file cancellato occupa ancora spazio: è la causa tipica di un disco "pieno senza file". Svuotare il file tramite
/proc/PID/fd/Nlibera lo spazio ma il programma continua a scriverci dentro: non è una soluzione al problema, solo un rimedio d'emergenza. La soluzione vera è la rotazione dei registri. - Dentro un container si vedono solo i processi del container (e per vedere quelli dell'ospite servono permessi speciali). Controlla da quale parte stai guardando.
- Non è un monitor in tempo reale: è una fotografia istantanea. Un file aperto per pochi millisecondi può non comparire. Per osservare una situazione che cambia, usa
-r, oppure strumenti dedicati comestrace. - Su altri sistemi cambia:
lsofesiste anche su macOS e FreeBSD, ma alcune opzioni e le colonne possono differire. In uno script portabile, prova prima il comando.
Riepilogo
| Comando | Cosa fa |
|---|---|
sudo lsof -p 925 | File aperti da un processo |
sudo lsof file | Chi tiene aperto un file |
sudo lsof /mnt/usb | Chi sta usando un file system montato |
sudo lsof +D cartella | File aperti in tutta una cartella (lento) |
sudo lsof -u utente | File aperti da un utente |
sudo lsof -c nginx | File aperti da un programma |
sudo lsof -a -u enzo -c bash | Unisce i filtri (tutti insieme) |
sudo lsof -i :80 | Chi usa la porta 80 |
sudo lsof -iTCP -sTCP:LISTEN -P -n | Programmi in ascolto, senza traduzioni |
sudo lsof +L1 | File cancellati ma ancora aperti |
sudo lsof -t -i :8080 | Solo il numero del processo (per gli script) |
sudo lsof -r 2 -i :80 | Ripete ogni 2 secondi |