Apuntes DAM
Volver al inicio

Monitor de espacio en disco con umbrales

Ejercicio de BashMedioUnos 50 minutos

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

  1. La primera línea es UMBRALES aviso crítico con dos enteros, aviso menor que crítico y crítico como mucho 100 (si no, escribe La 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 de df -P.
  2. Se descartan los dispositivos tmpfs, devtmpfs, overlay, squashfs, udev, none y los que empiezan por /dev/loop. Si no queda ninguno, No hay sistemas de ficheros que vigilar.
  3. Estado según el porcentaje de la columna Capacity: URGENTE si es mayor o igual que el umbral crítico, AVISO si es mayor o igual que el de aviso, y OK si no.
  4. 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 y usado 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.
  5. 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 GiB con 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.

bash
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.

🖥️BashMonitor de espacio en disco con umbralesMedio

Ejemplo

Entrada (lo que se escribe por teclado)
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 esperada
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
⏳
Test oculto #3
⏳
Test oculto #4
0/4 tests pasados · pulsa un test para ver su entrada y su salida esperada

Solución explicada

Ver la solución completa
bash
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.

Dónde se explica