Monitor de espacio en disco con umbrales
Analiza la salida de df -P de un servidor: descarta los sistemas de ficheros virtuales, clasifica cada disco como OK, AVISO o URGENTE según dos umbrales, calcula cuántos GiB hay que liberar y ordena el informe del más lleno al más vacío. Aritmética entera, case, printf y sort por campos.
- while read con varios campos
- case con patrones
- Aritmética entera y redondeos
- printf con anchos
- ${var%patrón}
- sort -t -k y cut
Enunciado
Un disco lleno tumba bases de datos, impide escribir registros y deja servicios caídos. Por eso los administradores programan con cron un script que revisa el espacio cada pocos minutos y avisa antes de que sea tarde.
La información la da df -P (el formato POSIX, pensado para que lo lean los scripts): una línea por sistema de ficheros con el dispositivo, los bloques de 1 KiB, los usados, los disponibles, el porcentaje y el punto de montaje. Muchas líneas son sistemas de ficheros virtuales (tmpfs, devtmpfs, overlay de Docker, squashfs y /dev/loop de los snaps) que no ocupan disco y no hay que vigilar.
Bash solo sabe hacer cuentas con enteros, así que los GiB con un decimal y los redondeos hay que construirlos a mano: es un buen ejercicio de aritmética entera.
Qué tiene que hacer el programa
- La primera línea es
UMBRALES aviso críticocon dos enteros, aviso menor que crítico y crítico como mucho 100 (si no, escribeLa primera línea debe ser UMBRALES aviso crítico (aviso < crítico <= 100)y termina). La segunda es la cabecera de df, que se ignora; después, las líneas dedf -P. - Se descartan los dispositivos
tmpfs,devtmpfs,overlay,squashfs,udev,noney los que empiezan por/dev/loop. Si no queda ninguno,No hay sistemas de ficheros que vigilar. - Estado según el porcentaje de la columna Capacity:
URGENTEsi es mayor o igual que el umbral crítico,AVISOsi es mayor o igual que el de aviso, yOKsi no. - Una línea por sistema de ficheros, del porcentaje mayor al menor (a igualdad, por punto de montaje): el estado en 8 caracteres a la izquierda, un espacio, el porcentaje en 3 caracteres a la derecha seguido de
%, dos espacios, el punto de montaje en 12 caracteres a la izquierda, un espacio yusado U de T(usado y usado + disponible). Si el estado no es OK, se añade· libera N GiB para bajar del A %: los GiB enteros (redondeando hacia arriba) que hay que borrar para que el uso baje hasta el umbral de aviso. - Los tamaños se escriben en GiB (1 GiB = 1.048.576 KiB) con un decimal redondeado y coma:
49,7 GiB. Al final,Libre en total: X GiBcon la suma de lo disponible en los sistemas vigilados.
Entrada
Línea 1: UMBRALES aviso crítico. Después, la salida completa de df -P (con su cabecera).
Ejemplos de ejecución
Tu programa debe escribir exactamente esta salida para estas entradas. Las pruebas del editor incluyen estos ejemplos y otros casos ocultos.
Un servidor con dos discos llenándose
Entrada
UMBRALES 80 90 Filesystem 1024-blocks Used Available Capacity Mounted on udev 4006228 0 4006228 0% /dev tmpfs 804792 1764 803028 1% /run /dev/sda2 61255492 52113408 5998944 90% / tmpfs 4023956 0 4023956 0% /dev/shm /dev/sda1 523248 6220 517028 2% /boot/efi /dev/sdb1 976284628 801228314 175056314 83% /datos /dev/loop0 65536 65536 0 100% /snap/core20/2318 overlay 61255492 52113408 5998944 90% /var/lib/docker/overlay2/x/merged
Salida por consola
URGENTE 90% / usado 49,7 GiB de 55,4 GiB · libera 6 GiB para bajar del 80 % AVISO 83% /datos usado 764,1 GiB de 931,1 GiB · libera 20 GiB para bajar del 80 % OK 2% /boot/efi usado 0,0 GiB de 0,5 GiB Libre en total: 173,2 GiB
Umbrales no válidos
Entrada
UMBRALES 95 90 Filesystem 1024-blocks Used Available Capacity Mounted on
Salida por consola
La primera línea debe ser UMBRALES aviso crítico (aviso < crítico <= 100)
Guía paso a paso
Intenta resolverlo por tu cuenta y abre un paso solo cuando te atasques: cada uno te acerca a la solución sin dártela entera.
1. Leer df por columnas
while read -r dispositivo bloques usado libre capacidad montaje reparte cada línea en sus columnas. Lee antes la cabecera con un read -r _ suelto para saltarla.
2. Descartar con case
Un case con varios patrones separados por | y un continue descarta los sistemas virtuales de un golpe; los /dev/loop* se comprueban con [[ $dispositivo == /dev/loop* ]].
3. El porcentaje sin el %
${capacidad%\%} quita el % del final (el % de la expansión significa «quitar por el final» y el segundo, escapado, es el carácter que se quita).
4. Decimales con enteros
Para un decimal, trabaja en décimas: multiplica por 10, suma la mitad del divisor para redondear y divide. Después, la parte entera es / 10 y el decimal % 10. Para redondear hacia arriba se suma el divisor menos 1.
gib() {
local decimas=$(( ($1 * 10 + 524288) / 1048576 ))
echo "$((decimas / 10)),$((decimas % 10)) GiB"
}5. Ordenar las líneas ya formateadas
Guarda cada línea con delante el porcentaje y el montaje separados por |, ordénalas con sort -t'|' -k1,1nr -k2,2 y quita esos campos auxiliares con cut -d'|' -f3-.
Resuélvelo aquí
El editor trae el esqueleto del programa. Pulsa «Ejecutar» para comprobarlo con los ejemplos y con 2 casos ocultos que buscan los errores típicos.
Ejemplo
UMBRALES 80 90 Filesystem 1024-blocks Used Available Capacity Mounted on udev 4006228 0 4006228 0% /dev tmpfs 804792 1764 803028 1% /run /dev/sda2 61255492 52113408 5998944 90% / tmpfs 4023956 0 4023956 0% /dev/shm /dev/sda1 523248 6220 517028 2% /boot/efi /dev/sdb1 976284628 801228314 175056314 83% /datos /dev/loop0 65536 65536 0 100% /snap/core20/2318 overlay 61255492 52113408 5998944 90% /var/lib/docker/overlay2/x/merged
URGENTE 90% / usado 49,7 GiB de 55,4 GiB · libera 6 GiB para bajar del 80 % AVISO 83% /datos usado 764,1 GiB de 931,1 GiB · libera 20 GiB para bajar del 80 % OK 2% /boot/efi usado 0,0 GiB de 0,5 GiB Libre en total: 173,2 GiB
Solución explicada
Ver la solución completa
1#!/bin/bash
2# Lee la salida de «df -P» y avisa de los sistemas de ficheros que se llenan
3export LC_ALL=C
4
5read -r palabra aviso critico
6if [ "$palabra" != "UMBRALES" ] || ! [[ $aviso =~ ^[0-9]+$ && $critico =~ ^[0-9]+$ ]] || [ "$aviso" -ge "$critico" ] || [ "$critico" -gt 100 ]; then
7 echo "La primera línea debe ser UMBRALES aviso crítico (aviso < crítico <= 100)"
8 exit 0
9fi
10
11# KiB → «12,3 GiB» con un decimal, redondeado
12gib() {
13 local decimas=$(( ($1 * 10 + 524288) / 1048576 ))
14 echo "$((decimas / 10)),$((decimas % 10)) GiB"
15}
16
17filas=()
18libre_total=0
19read -r _ # cabecera de df
20while read -r dispositivo bloques usado libre capacidad montaje; do
21 [ -z "$dispositivo" ] && continue
22 # Sistemas de ficheros virtuales: no ocupan disco
23 case $dispositivo in
24 tmpfs|devtmpfs|overlay|squashfs|udev|none) continue ;;
25 esac
26 [[ $dispositivo == /dev/loop* ]] && continue
27 porcentaje=${capacidad%\%}
28 libre_total=$((libre_total + libre))
29 if [ "$porcentaje" -ge "$critico" ]; then estado="URGENTE"
30 elif [ "$porcentaje" -ge "$aviso" ]; then estado="AVISO"
31 else estado="OK"; fi
32 linea=$(printf "%-8s %3d%% %-12s usado %s de %s" "$estado" "$porcentaje" "$montaje" "$(gib "$usado")" "$(gib $((usado + libre)))")
33 if [ "$estado" != "OK" ]; then
34 # Lo que hay que liberar para bajar del umbral de aviso: usado - aviso% del total, redondeado hacia arriba
35 sobra=$(( usado * 100 - aviso * (usado + libre) ))
36 liberar=$(( (sobra + 100 * 1048576 - 1) / (100 * 1048576) ))
37 linea+=" · libera $liberar GiB para bajar del $aviso %"
38 fi
39 filas+=("$porcentaje|$montaje|$linea")
40done
41
42if [ ${#filas[@]} -eq 0 ]; then
43 echo "No hay sistemas de ficheros que vigilar"
44 exit 0
45fi
46# Del más lleno al más vacío; a igual porcentaje, por punto de montaje
47printf "%s\n" "${filas[@]}" | sort -t'|' -k1,1nr -k2,2 | cut -d'|' -f3-
48echo "Libre en total: $(gib "$libre_total")"df -P existe precisamente para los scripts: garantiza una línea por sistema de ficheros y columnas fijas, aunque el nombre del dispositivo sea largo. Leerlo con read y nombres de variable deja el script legible.
Los umbrales separan la decisión (qué estado tiene cada disco) de la presentación. En la versión real, el estado URGENTE dispararía un correo o un mensaje, y el script se programaría en cron cada 5 minutos.
Toda la aritmética es entera: el redondeo al más cercano se consigue sumando la mitad del divisor y el redondeo hacia arriba sumando el divisor menos uno. Es la misma técnica que en cualquier lenguaje sin decimales (o con dinero en céntimos).
Guardar las líneas con una clave de ordenación delante y quitarla después (el patrón «decorar, ordenar, quitar») permite ordenar por un dato numérico sin perder el formato de la salida.
Para ir más allá
- Comprueba también los inodos con
df -Pi: un disco puede llenarse de ficheros pequeños con espacio libre. - Lista los 5 directorios que más ocupan del sistema de ficheros más lleno con
du -x --max-depth=2 | sort -n. - Guarda un histórico diario y estima en cuántos días se llenará cada disco al ritmo actual.