Apuntes DAM
Volver al inicio

Ejercicios resueltos de Sistemas Informáticos

Los 49 ejercicios de Sistemas Informáticos de la web, tema a tema: cada uno con su enunciado, los datos que necesitas (código de partida, ejemplos de entrada y salida o la base de datos) y, al final, la solución explicada.

Descargar el PDF

Componentes Físicos

1. ¿Por qué mi disco de 1 TB tiene 931 GB?

Medio · Componentes Físicos · apuntesdam.com/subject/sistemas/topic/componentes-fisicos

Los fabricantes miden en unidades decimales (1 GB = 10⁹ bytes) y muchos sistemas operativos en binarias (1 GiB = 2³⁰ bytes, aunque Windows lo muestre como «GB»). Cada línea de la entrada es una cantidad y una unidad (B, KB, MB, GB, TB, KiB, MiB, GiB o TiB). Muestra «cantidad unidad = N bytes = X GB = Y GiB», con los bytes como número entero redondeado y X e Y con 2 decimales. Si la unidad no existe, «línea: unidad desconocida».

Código de partida (python)
import sys DECIMAL = {"B": 1, "KB": 10**3, "MB": 10**6, "GB": 10**9, "TB": 10**12}BINARIO = {"KiB": 2**10, "MiB": 2**20, "GiB": 2**30, "TiB": 2**40} # TODO: convierte cada tamaño a bytes, a GB y a GiB

Ejemplo: Discos y memoria

Entrada

1 TB
512 GB
16 GiB
4.7 GB

Salida esperada

1 TB = 1000000000000 bytes = 1000.00 GB = 931.32 GiB
512 GB = 512000000000 bytes = 512.00 GB = 476.84 GiB
16 GiB = 17179869184 bytes = 17.18 GB = 16.00 GiB
4.7 GB = 4700000000 bytes = 4.70 GB = 4.38 GiB

Ejemplo: Unidades pequeñas

Entrada

1536 KiB
700 MB
1 B

Salida esperada

1536 KiB = 1572864 bytes = 0.00 GB = 0.00 GiB
700 MB = 700000000 bytes = 0.70 GB = 0.65 GiB
1 B = 1 bytes = 0.00 GB = 0.00 GiB

2. Dimensionar la fuente de alimentación

Difícil · Componentes Físicos · apuntesdam.com/subject/sistemas/topic/componentes-fisicos

Las primeras líneas son los componentes con su consumo máximo, «nombre vatios» (el nombre puede tener espacios). Después vienen cuatro líneas de configuración «clave=valor»: certificacion (bronze, gold o platinum, con eficiencias 0.85, 0.90 y 0.92), uso (porcentaje medio de carga respecto al máximo), horas (de uso al día) y precio (€ por kWh). Muestra: «Consumo de los componentes: N W»; «Con un margen del 30 %: N W» (sin decimales); «Fuente recomendada: N W» (la primera del catálogo que llegue a ese valor, o «ninguna del catálogo es suficiente»); «Consumo real de la pared: X W» (consumo × uso / 100 / eficiencia, 1 decimal); y «Coste anual: X € (Y kWh)» con 365 días al año (2 y 1 decimales).

Código de partida (python)
import sys CATALOGO = [450, 550, 650, 750, 850, 1000, 1200]EFICIENCIA = {"bronze": 0.85, "gold": 0.90, "platinum": 0.92} lineas = [l.strip() for l in sys.stdin.read().splitlines() if l.strip()] # TODO: calcula el consumo, elige la fuente y estima el coste anual

Ejemplo: Equipo de desarrollo

Entrada

Procesador Ryzen 7 105
Gráfica RTX 4060 115
Placa base 50
RAM 2 módulos 10
SSD NVMe 8
Ventiladores 12
certificacion=gold
uso=40
horas=8
precio=0.18

Salida esperada

Consumo de los componentes: 300 W
Con un margen del 30 %: 390 W
Fuente recomendada: 450 W
Consumo real de la pared: 133.3 W
Coste anual: 70.08 € (389.3 kWh)

Ejemplo: Estación de trabajo

Entrada

CPU 253
GPU RTX 4090 450
Resto 120
certificacion=platinum
uso=70
horas=10
precio=0.2

Salida esperada

Consumo de los componentes: 823 W
Con un margen del 30 %: 1070 W
Fuente recomendada: 1200 W
Consumo real de la pared: 626.2 W
Coste anual: 457.12 € (2285.6 kWh)

3. Ancho de banda y cuellos de botella

Muy difícil · Componentes Físicos · apuntesdam.com/subject/sistemas/topic/componentes-fisicos

Cada línea describe un componente. «ram DDR5-6000 2» es memoria del tipo indicado (el número son millones de transferencias por segundo, MT/s) en 2 canales: cada canal transfiere 8 bytes, así que el ancho de banda en GB/s es MT/s × 8 × canales / 1000. «ssd 7000 pcie4 x4» es un SSD que puede leer a 7000 MB/s conectado a una ranura PCIe de 4.ª generación con 4 líneas; cada línea transporta 0.985, 1.969 o 3.938 GB/s en las generaciones 3, 4 y 5. Para la memoria muestra «Memoria DDR5-6000 en 2 canales: 96.0 GB/s» («canal» en singular si es 1). Para el SSD, «SSD de V MB/s en PCIe G.0 xN (E GB/s): sin cuello de botella» o, si el enlace es más lento que el SSD, «…: limitado a E MB/s (D MB/s desaprovechados)», con el enlace en MB/s sin decimales (× 1000) y en GB/s con 1 decimal.

Código de partida (python)
import sys PCIE_GBS_POR_LINEA = {"3": 0.985, "4": 1.969, "5": 3.938} # TODO: calcula el ancho de banda y detecta cuellos de botella

Ejemplo: Equipo actual

Entrada

ram DDR5-6000 2
ssd 7000 pcie4 x4
ssd 3500 pcie3 x4

Salida esperada

Memoria DDR5-6000 en 2 canales: 96.0 GB/s
SSD de 7000 MB/s en PCIe 4.0 x4 (7.9 GB/s): sin cuello de botella
SSD de 3500 MB/s en PCIe 3.0 x4 (3.9 GB/s): sin cuello de botella

Ejemplo: SSD en ranura lenta

Entrada

ssd 7400 pcie3 x4
ssd 12000 pcie5 x4
ram DDR4-3200 1

Salida esperada

SSD de 7400 MB/s en PCIe 3.0 x4 (3.9 GB/s): limitado a 3940 MB/s (3460 MB/s desaprovechados)
SSD de 12000 MB/s en PCIe 5.0 x4 (15.8 GB/s): sin cuello de botella
Memoria DDR4-3200 en 1 canal: 25.6 GB/s

Sistemas Operativos

4. Planificador FCFS y SJF

Medio · Sistemas Operativos · apuntesdam.com/subject/sistemas/topic/sistemas-operativos

La primera línea es el algoritmo: FCFS (el primero que llega, se ejecuta primero) o SJF (de los que ya han llegado, el de ráfaga más corta; a igualdad, el que llegó antes y después por nombre). Ambos son no expropiativos: un proceso se ejecuta hasta terminar. Cada línea siguiente es un proceso «nombre llegada ráfaga». Muestra el diagrama de Gantt en una línea, con los tramos «nombre inicio-fin» separados por « | » (si la CPU queda libre esperando a que llegue alguien, un tramo «ocioso inicio-fin»); después, para cada proceso en el orden de la entrada, «nombre: espera E, retorno R» (espera = inicio − llegada; retorno = fin − llegada); y por último la espera media y el retorno medio con 2 decimales.

Código de partida (python)
import sys lineas = [l.split() for l in sys.stdin.read().splitlines() if l.strip()]algoritmo = lineas[0][0]procesos = [(nombre, int(llegada), int(rafaga)) for nombre, llegada, rafaga in lineas[1:]] # TODO: planifica con FCFS o SJF y muestra el diagrama y los tiempos medios

Ejemplo: FCFS del ejemplo

Entrada

FCFS
P1 0 5
P2 1 3
P3 2 1
P4 3 2

Salida esperada

P1 0-5 | P2 5-8 | P3 8-9 | P4 9-11
P1: espera 0, retorno 5
P2: espera 4, retorno 7
P3: espera 6, retorno 7
P4: espera 6, retorno 8
Espera media: 4.00
Retorno medio: 6.75

Ejemplo: SJF del ejemplo

Entrada

SJF
P1 0 5
P2 1 3
P3 2 1
P4 3 2

Salida esperada

P1 0-5 | P3 5-6 | P4 6-8 | P2 8-11
P1: espera 0, retorno 5
P2: espera 7, retorno 10
P3: espera 3, retorno 4
P4: espera 3, retorno 5
Espera media: 3.25
Retorno medio: 6.00

Ejemplo: CPU ociosa

Entrada

FCFS
A 2 3
B 10 2
C 11 1

Salida esperada

ocioso 0-2 | A 2-5 | ocioso 5-10 | B 10-12 | C 12-13
A: espera 0, retorno 3
B: espera 0, retorno 2
C: espera 1, retorno 2
Espera media: 0.33
Retorno medio: 2.33

5. Round Robin con quantum

Difícil · Sistemas Operativos · apuntesdam.com/subject/sistemas/topic/sistemas-operativos

La primera línea es «quantum Q»; cada línea siguiente, un proceso «nombre llegada ráfaga». Simula Round Robin: el proceso al frente de la cola se ejecuta como mucho Q unidades; si no termina, vuelve al final de la cola. Los procesos que llegan durante ese tramo (o justo cuando termina) entran en la cola antes que el proceso expulsado. Si la cola está vacía, la CPU queda ociosa hasta la siguiente llegada. Muestra el diagrama de Gantt («nombre inicio-fin» separados por « | », con tramos «ocioso inicio-fin» si hace falta), para cada proceso en el orden de la entrada «nombre: espera E, retorno R» (retorno = fin − llegada; espera = retorno − ráfaga), la espera y el retorno medios con 2 decimales y «Cambios de contexto: N» (veces que en el diagrama un tramo es de un proceso distinto al anterior).

Código de partida (python)
import sysfrom collections import deque lineas = [l.split() for l in sys.stdin.read().splitlines() if l.strip()]quantum = int(lineas[0][1])procesos = [(nombre, int(llegada), int(rafaga)) for nombre, llegada, rafaga in lineas[1:]] # TODO: simula Round Robin y muestra el diagrama y los tiempos

Ejemplo: Ejemplo con quantum 2

Entrada

quantum 2
P1 0 5
P2 1 3
P3 2 1
P4 3 2

Salida esperada

P1 0-2 | P2 2-4 | P3 4-5 | P1 5-7 | P4 7-9 | P2 9-10 | P1 10-11
P1: espera 6, retorno 11
P2: espera 6, retorno 9
P3: espera 2, retorno 3
P4: espera 4, retorno 6
Espera media: 4.50
Retorno medio: 7.25
Cambios de contexto: 6

Ejemplo: Quantum grande

Entrada

quantum 10
P1 0 5
P2 1 3
P3 2 1
P4 3 2

Salida esperada

P1 0-5 | P2 5-8 | P3 8-9 | P4 9-11
P1: espera 0, retorno 5
P2: espera 4, retorno 7
P3: espera 6, retorno 7
P4: espera 6, retorno 8
Espera media: 4.00
Retorno medio: 6.75
Cambios de contexto: 3

Ejemplo: CPU ociosa

Entrada

quantum 3
A 0 4
B 10 5
C 12 2

Salida esperada

A 0-3 | A 3-4 | ocioso 4-10 | B 10-13 | C 13-15 | B 15-17
A: espera 0, retorno 4
B: espera 2, retorno 7
C: espera 1, retorno 3
Espera media: 1.00
Retorno medio: 4.67
Cambios de contexto: 4

6. Reemplazo de páginas: FIFO, LRU y óptimo

Muy difícil · Sistemas Operativos · apuntesdam.com/subject/sistemas/topic/sistemas-operativos

Cuando la memoria está llena y se necesita una página que no está cargada (fallo de página), el sistema operativo elige una víctima. La primera línea es «marcos N» (páginas que caben en memoria, inicialmente vacía) y la segunda, la cadena de referencias separadas por espacios. Cuenta los fallos con tres algoritmos: FIFO (sale la que lleva más tiempo cargada), LRU (sale la que hace más tiempo que no se usa) y óptimo (sale la que tardará más en volver a usarse; si varias no vuelven a usarse, la que lleva más tiempo cargada). Muestra «FIFO: N fallos (P %)», «LRU: …» y «Óptimo: …», con el porcentaje sobre el total de referencias con 1 decimal. Prueba la cadena 1 2 3 4 1 2 5 1 2 3 4 5 con 3 y con 4 marcos: verás la anomalía de Bélády.

Código de partida (python)
import sys lineas = [l.split() for l in sys.stdin.read().splitlines() if l.strip()]marcos = int(lineas[0][1])referencias = [int(x) for x in lineas[1]] # TODO: cuenta los fallos de página con FIFO, LRU y el algoritmo óptimo

Ejemplo: Cadena clásica con 3 marcos

Entrada

marcos 3
7 0 1 2 0 3 0 4 2 3 0 3 2 1 2 0 1 7 0 1

Salida esperada

FIFO: 15 fallos (75.0 %)
LRU: 12 fallos (60.0 %)
Óptimo: 9 fallos (45.0 %)

Ejemplo: Anomalía de Bélády con 3

Entrada

marcos 3
1 2 3 4 1 2 5 1 2 3 4 5

Salida esperada

FIFO: 9 fallos (75.0 %)
LRU: 10 fallos (83.3 %)
Óptimo: 7 fallos (58.3 %)

Ejemplo: Anomalía de Bélády con 4

Entrada

marcos 4
1 2 3 4 1 2 5 1 2 3 4 5

Salida esperada

FIFO: 10 fallos (83.3 %)
LRU: 8 fallos (66.7 %)
Óptimo: 6 fallos (50.0 %)

Instalación de Sistemas Operativos

7. Un plan de particiones alineado

Medio · Instalación de Sistemas Operativos · apuntesdam.com/subject/sistemas/topic/instalacion-sistemas-operativos

La primera línea es «disco N GiB» (N entero). Cada línea siguiente es una partición «nombre tamaño», con el tamaño en MiB («512MiB»), en GiB («50GiB», puede tener decimales) o «resto» (todo lo que quede). En un disco GPT, el primer MiB queda reservado (tabla y alineación) y también el último (copia de la tabla). Coloca las particiones una detrás de otra empezando en el MiB 1 y muestra «nombre: inicio A MiB, fin B MiB, tamaño T GiB» (T con 2 decimales). Si una partición no cabe, muestra «ERROR: no cabe nombre (faltan N MiB)» y para. Si todas caben, termina con «Sin asignar: X GiB».

Código de partida (python)
import sys lineas = [l.split() for l in sys.stdin.read().splitlines() if l.strip()]disco_mib = int(lineas[0][1].replace("GiB", "")) * 1024 # TODO: coloca las particiones alineadas a 1 MiB y muestra dónde empieza y acaba cada una

Ejemplo: Ubuntu en 500 GiB

Entrada

disco 500GiB
efi 1GiB
raiz 50GiB
swap 8GiB
home resto

Salida esperada

efi: inicio 1 MiB, fin 1025 MiB, tamaño 1.00 GiB
raiz: inicio 1025 MiB, fin 52225 MiB, tamaño 50.00 GiB
swap: inicio 52225 MiB, fin 60417 MiB, tamaño 8.00 GiB
home: inicio 60417 MiB, fin 511999 MiB, tamaño 441.00 GiB
Sin asignar: 0.00 GiB

Ejemplo: Windows con hueco libre

Entrada

disco 256GiB
esp 260MiB
msr 16MiB
windows 180GiB
recuperacion 1GiB

Salida esperada

esp: inicio 1 MiB, fin 261 MiB, tamaño 0.25 GiB
msr: inicio 261 MiB, fin 277 MiB, tamaño 0.02 GiB
windows: inicio 277 MiB, fin 184597 MiB, tamaño 180.00 GiB
recuperacion: inicio 184597 MiB, fin 185621 MiB, tamaño 1.00 GiB
Sin asignar: 74.73 GiB

8. ¿Arrancará este esquema de particiones?

Difícil · Instalación de Sistemas Operativos · apuntesdam.com/subject/sistemas/topic/instalacion-sistemas-operativos

Las tres primeras líneas son «firmware uefi|bios», «tabla gpt|mbr» y «disco N» (GiB). Cada línea siguiente es una partición «nombre clase sistema tamaño punto» (clase: primaria, extendida o logica; sistema: fat32, ntfs, ext4, swap…; tamaño en GiB; punto de montaje o «-»). Comprueba estas reglas en este orden y muestra un mensaje por cada una que falle, o «Esquema válido» si ninguna falla: (1) «UEFI necesita una tabla GPT (o activar el modo CSM)»; con MBR: (2) «MBR solo puede usar 2048 GiB de los N GiB del disco» si el disco es mayor, (3) «MBR admite como máximo 4 particiones primarias o extendidas», (4) «Solo puede haber una partición extendida», (5) «Las particiones lógicas necesitan una partición extendida»; con GPT: (6) «GPT no tiene particiones extendidas ni lógicas»; (7) con UEFI, «Falta la partición EFI (FAT32 montada en /boot/efi)»; (8) «Falta la partición raíz (/)»; (9) «Las particiones ocupan T GiB y el disco tiene N GiB» (la extendida no cuenta, porque contiene a las lógicas). Los números se muestran sin decimales innecesarios.

Código de partida (python)
import sys lineas = [l.split() for l in sys.stdin.read().splitlines() if l.strip()]config = {l[0]: l[1] for l in lineas[:3]}          # firmware, tabla, discoparticiones = lineas[3:]                            # nombre clase sistema tamaño_GiB punto_de_montaje # TODO: comprueba las reglas en orden y muestra los problemas o «Esquema válido»

Ejemplo: Esquema correcto

Entrada

firmware uefi
tabla gpt
disco 500
efi primaria fat32 1 /boot/efi
raiz primaria ext4 50 /
home primaria ext4 400 /home

Salida esperada

Esquema válido

Ejemplo: UEFI con MBR y sin EFI

Entrada

firmware uefi
tabla mbr
disco 3000
sistema primaria ntfs 200 C:
datos primaria ntfs 2500 D:

Salida esperada

UEFI necesita una tabla GPT (o activar el modo CSM)
MBR solo puede usar 2048 GiB de los 3000 GiB del disco
Falta la partición EFI (FAT32 montada en /boot/efi)
Falta la partición raíz (/)

Ejemplo: MBR clásico con lógicas

Entrada

firmware bios
tabla mbr
disco 1000
win primaria ntfs 300 C:
recup primaria ntfs 1 -
ext extendida - 699 -
raiz logica ext4 100 /
swap logica swap 8 -
home logica ext4 591 /home

Salida esperada

Esquema válido

9. Leer la tabla de particiones de un MBR

Muy difícil · Instalación de Sistemas Operativos · apuntesdam.com/subject/sistemas/topic/instalacion-sistemas-operativos

El sector 0 de un disco MBR termina con la tabla de particiones: 4 entradas de 16 bytes (desde el byte 446) y la firma 55 AA. La entrada recibe esos 66 bytes en hexadecimal. En cada entrada: el byte 0 es 0x80 si la partición es la activa (arrancable); el byte 4, el tipo; los bytes 8-11, el sector inicial (LBA); y los bytes 12-15, el número de sectores, ambos enteros de 4 bytes en little endian (el byte menos significativo primero). Las entradas con tipo 00 están vacías. Para cada partición muestra «Partición N: [activa, ]tipo XX (nombre), sector inicial S, T sectores (G GiB)» con sectores de 512 bytes y G con 2 decimales (tipo en hexadecimal con 2 cifras en mayúsculas; nombre «desconocido» si no está en la tabla). Si el tipo es EE, añade la línea «El disco usa GPT: esta entrada solo protege el disco de herramientas antiguas». Al final, «Firma: correcta (55 AA)» o «Firma: incorrecta (XX XX)».

Código de partida (python)
import sys datos = bytes.fromhex("".join(sys.stdin.read().split()))   # las 4 entradas (64 bytes) y la firma (2 bytes)TIPOS = {0x07: "NTFS/exFAT", 0x0B: "FAT32", 0x0C: "FAT32 LBA", 0x83: "Linux", 0x82: "Linux swap",         0x05: "Extendida", 0x0F: "Extendida LBA", 0xEE: "GPT de protección", 0xEF: "Sistema EFI"} # TODO: interpreta cada entrada de 16 bytes y la firma del sector

Ejemplo: Windows y Linux en arranque dual

Entrada

80 00 00 00 07 00 00 00 00 08 00 00 00 00 40 06
00 00 00 00 83 00 00 00 00 08 40 06 00 00 80 0C
00 00 00 00 82 00 00 00 00 08 C0 12 00 00 00 01
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
55 AA

Salida esperada

Partición 1: activa, tipo 07 (NTFS/exFAT), sector inicial 2048, 104857600 sectores (50.00 GiB)
Partición 2: tipo 83 (Linux), sector inicial 104859648, 209715200 sectores (100.00 GiB)
Partición 3: tipo 82 (Linux swap), sector inicial 314574848, 16777216 sectores (8.00 GiB)
Firma: correcta (55 AA)

Ejemplo: Disco GPT

Entrada

00 00 00 00 EE 00 00 00 01 00 00 00 FF FF FF FF
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
55 AA

Salida esperada

Partición 1: tipo EE (GPT de protección), sector inicial 1, 4294967295 sectores (2048.00 GiB)
El disco usa GPT: esta entrada solo protege el disco de herramientas antiguas
Firma: correcta (55 AA)

Sistemas de Ficheros y Copias de Seguridad

10. Capacidad y tolerancia de un RAID

Medio · Sistemas de Ficheros y Copias de Seguridad · apuntesdam.com/subject/sistemas/topic/sistemas-ficheros-almacenamiento

Cada línea es «nivel discos tamaño_GB», con el nivel RAID0, RAID1, RAID5, RAID6 o RAID10. Calcula la capacidad útil (RAID0: suma de todos; RAID1: un disco, porque todos son copias; RAID5: n − 1 discos; RAID6: n − 2; RAID10: la mitad), la eficiencia (útil / total, en % sin decimales) y los fallos de disco que soporta sin perder datos (0, n − 1, 1, 2 y en RAID10 «1 fallo por pareja (hasta n/2)»). Muestra «RAID5 con 4 × 2000 GB: 6000 GB útiles (75 %), tolera 1 fallo» (y «fallos» en plural salvo cuando es exactamente 1). Si faltan discos (mínimos: 2, 2, 3, 4 y 4; RAID10 además un número par), muestra «RAID10 con 5 discos: no válido (necesita al menos 4 discos y un número par)», y para un nivel que no existe, «nivel: nivel desconocido».

Código de partida (python)
import sys MINIMO = {"RAID0": 2, "RAID1": 2, "RAID5": 3, "RAID6": 4, "RAID10": 4} # TODO: calcula la capacidad útil, la eficiencia y los fallos que soporta cada configuración

Ejemplo: Niveles habituales

Entrada

RAID0 2 1000
RAID1 2 4000
RAID5 4 2000
RAID6 6 8000
RAID10 4 1000

Salida esperada

RAID0 con 2 × 1000 GB: 2000 GB útiles (100 %), tolera 0 fallos
RAID1 con 2 × 4000 GB: 4000 GB útiles (50 %), tolera 1 fallo
RAID5 con 4 × 2000 GB: 6000 GB útiles (75 %), tolera 1 fallo
RAID6 con 6 × 8000 GB: 32000 GB útiles (67 %), tolera 2 fallos
RAID10 con 4 × 1000 GB: 2000 GB útiles (50 %), tolera 1 fallo por pareja (hasta 2)

Ejemplo: Configuraciones no válidas

Entrada

RAID5 2 1000
RAID10 5 2000
RAID7 4 1000

Salida esperada

RAID5 con 2 discos: no válido (necesita al menos 3 discos)
RAID10 con 5 discos: no válido (necesita al menos 4 discos y un número par)
RAID7: nivel desconocido

11. Simulador de estrategias de copia

Difícil · Sistemas de Ficheros y Copias de Seguridad · apuntesdam.com/subject/sistemas/topic/sistemas-ficheros-almacenamiento

La primera línea es «datos N» (GB que se copian), la segunda «cambio P» (porcentaje de los datos que se modifica cada día, siempre ficheros distintos) y la tercera «plan …» con un tipo de copia por día: C (completa: copia todo), I (incremental: lo que ha cambiado desde la copia anterior, P % de los datos) o D (diferencial: todo lo que ha cambiado desde la última completa, P % por cada día transcurrido desde ella, como máximo el 100 %). Muestra «Día N (T): X GB» (1 decimal) por cada día, «Total ocupado: X GB» y «Para restaurar el último día: día A, día B…» con las copias necesarias: la última completa, la última diferencial posterior (si la hay) y las incrementales posteriores a esa diferencial (o a la completa si no hay diferencial). Una I o una D antes de cualquier completa muestran «Día N: ERROR, una copia T necesita una completa anterior» y no ocupan nada.

Código de partida (python)
import sys lineas = [l.split() for l in sys.stdin.read().splitlines() if l.strip()]datos = float(lineas[0][1])cambio = float(lineas[1][1])plan = lineas[2][1:]          # C = completa, I = incremental, D = diferencial, un día cada una # TODO: tamaño de cada copia, total ocupado y copias necesarias para restaurar el último día

Ejemplo: Semana con incrementales

Entrada

datos 200
cambio 5
plan C I I I I I I

Salida esperada

Día 1 (C): 200.0 GB
Día 2 (I): 10.0 GB
Día 3 (I): 10.0 GB
Día 4 (I): 10.0 GB
Día 5 (I): 10.0 GB
Día 6 (I): 10.0 GB
Día 7 (I): 10.0 GB
Total ocupado: 260.0 GB
Para restaurar el último día: día 1, día 2, día 3, día 4, día 5, día 6, día 7

Ejemplo: Semana con diferenciales

Entrada

datos 200
cambio 5
plan C D D D D D D

Salida esperada

Día 1 (C): 200.0 GB
Día 2 (D): 10.0 GB
Día 3 (D): 20.0 GB
Día 4 (D): 30.0 GB
Día 5 (D): 40.0 GB
Día 6 (D): 50.0 GB
Día 7 (D): 60.0 GB
Total ocupado: 410.0 GB
Para restaurar el último día: día 1, día 7

Ejemplo: Mixta con dos completas

Entrada

datos 500
cambio 10
plan C I I D I C I D I

Salida esperada

Día 1 (C): 500.0 GB
Día 2 (I): 50.0 GB
Día 3 (I): 50.0 GB
Día 4 (D): 150.0 GB
Día 5 (I): 50.0 GB
Día 6 (C): 500.0 GB
Día 7 (I): 50.0 GB
Día 8 (D): 100.0 GB
Día 9 (I): 50.0 GB
Total ocupado: 1500.0 GB
Para restaurar el último día: día 6, día 8, día 9

12. ¿Es buena esta copia de seguridad?

Muy difícil · Sistemas de Ficheros y Copias de Seguridad · apuntesdam.com/subject/sistemas/topic/sistemas-ficheros-almacenamiento

Para comprobar una copia se compara un manifiesto del original con otro de la copia: cada línea es «ruta tamaño hash» (como lo que genera sha256sum, abreviado). La entrada tiene los dos manifiestos separados por una línea «---». Informa, en este orden: los ficheros MOVIDOS (faltan en una ruta pero aparecen en otra de la copia con el mismo hash) como «MOVIDO ruta -> nueva_ruta»; los que FALTAN; los DISTINTOS (misma ruta, distinto hash) con «(X bytes en el original, Y en la copia)»; y los que SOBRAN; cada grupo ordenado alfabéticamente. Termina con «Correctos: N de M · Datos del original: X MiB» (2 decimales) y «Copia VÁLIDA» si no falta ni difiere nada (los movidos y sobrantes no la invalidan) o «Copia NO VÁLIDA».

Código de partida (python)
import sys texto = sys.stdin.read().split("---")original = [l.split() for l in texto[0].splitlines() if l.strip()]   # ruta tamaño hashcopia = [l.split() for l in texto[1].splitlines() if l.strip()] # TODO: compara los dos manifiestos e informa de las diferencias

Ejemplo: Copia perfecta

Entrada

docs/contrato.pdf 482133 9f2a
docs/nominas/enero.xlsx 22100 77b1
docs/nominas/febrero.xlsx 22340 01ce
fotos/logo.png 150000 aa10
bd/clientes.sql 5242880 c3d4
---
docs/contrato.pdf 482133 9f2a
docs/nominas/enero.xlsx 22100 77b1
docs/nominas/febrero.xlsx 22340 01ce
fotos/logo.png 150000 aa10
bd/clientes.sql 5242880 c3d4

Salida esperada

Correctos: 5 de 5 · Datos del original: 5.65 MiB
Copia VÁLIDA

Ejemplo: Con errores

Entrada

docs/contrato.pdf 482133 9f2a
docs/nominas/enero.xlsx 22100 77b1
docs/nominas/febrero.xlsx 22340 01ce
fotos/logo.png 150000 aa10
bd/clientes.sql 5242880 c3d4
---
docs/contrato.pdf 482133 9f2a
docs/nominas/enero.xlsx 22100 0000
fotos/logo.png 150000 aa10
bd/clientes.sql 5242880 c3d4
tmp/cache.bin 10 ffff

Salida esperada

FALTA docs/nominas/febrero.xlsx
DISTINTO docs/nominas/enero.xlsx (22100 bytes en el original, 22100 en la copia)
SOBRA tmp/cache.bin
Correctos: 3 de 5 · Datos del original: 5.65 MiB
Copia NO VÁLIDA

Administración de Sistemas Operativos

13. Saludo con validación

Fácil · Administración de Sistemas Operativos · apuntesdam.com/subject/sistemas/topic/administracion-sistemas-operativos

Lee un nombre por teclado. Si está vacío, muestra «Error: nombre vacío»; si no, muestra «Hola, NOMBRE» y en otra línea «Tu nombre tiene N letras».

Código de partida (bash)
#!/bin/bashread -r nombre # TODO: valida y saluda

Ejemplo: Nombre normal

Entrada

Ana

Salida esperada

Hola, Ana
Tu nombre tiene 3 letras

Ejemplo: Otro nombre

Entrada

Rodrigo

Salida esperada

Hola, Rodrigo
Tu nombre tiene 7 letras

14. Tabla de multiplicar con bucle

Fácil · Administración de Sistemas Operativos · apuntesdam.com/subject/sistemas/topic/administracion-sistemas-operativos

Lee un número. Si no es un entero positivo, muestra «Error: número no válido». Si lo es, muestra su tabla del 1 al 10 con el formato «N x i = resultado».

Código de partida (bash)
#!/bin/bashread -r n # TODO

Ejemplo: Tabla del 7

Entrada

7

Salida esperada

7 x 1 = 7
7 x 2 = 14
7 x 3 = 21
7 x 4 = 28
7 x 5 = 35
7 x 6 = 42
7 x 7 = 49
7 x 8 = 56
7 x 9 = 63
7 x 10 = 70

Ejemplo: Texto

Entrada

hola

Salida esperada

Error: número no válido

15. Encuentra el fallo: el saludo que no se guarda

Fácil · Administración de Sistemas Operativos · apuntesdam.com/subject/sistemas/topic/administracion-sistemas-operativos

El script lee un nombre y debería mostrar «Hola, NOMBRE». En su lugar muestra un error y una línea vacía. Corrígelo.

Código de partida (bash)
#!/bin/bashread -r nombresaludo = "Hola, $nombre"echo "$saludo"

Ejemplo: Ana

Entrada

Ana

Salida esperada

Hola, Ana

Ejemplo: Luis

Entrada

Luis

Salida esperada

Hola, Luis

16. Analizar un fichero de log

Medio · Administración de Sistemas Operativos · apuntesdam.com/subject/sistemas/topic/administracion-sistemas-operativos

Por la entrada llega un log con líneas «FECHA HORA NIVEL IP mensaje». Muestra: • «Errores: N» (líneas con nivel ERROR) • «Avisos: N» (nivel WARN) • «IP con más errores: IP (N)» con la IP que más aparece en las líneas ERROR.

Código de partida (bash)
#!/bin/bashlog=$(cat) # TODO

Ejemplo: Log de ejemplo

Entrada

2025-09-30 10:00:01 INFO 10.0.0.5 Inicio
2025-09-30 10:00:02 ERROR 10.0.0.7 Login fallido
2025-09-30 10:00:05 WARN 10.0.0.5 Disco al 85%
2025-09-30 10:01:00 ERROR 10.0.0.7 Login fallido
2025-09-30 10:02:00 ERROR 10.0.0.9 Timeout

Salida esperada

Errores: 3
Avisos: 1
IP con más errores: 10.0.0.7 (2)

17. Usuarios del sistema desde /etc/passwd

Medio · Administración de Sistemas Operativos · apuntesdam.com/subject/sistemas/topic/administracion-sistemas-operativos

Por la entrada llega el contenido de un /etc/passwd (campos separados por «:» — usuario:x:UID:GID:comentario:home:shell). Muestra, ordenados por UID, los usuarios «reales» (UID ≥ 1000 y distinto de 65534) con el formato «usuario (UID) -> home [shell]», y al final «Total: N».

Código de partida (bash)
#!/bin/bash# La entrada estándar es el contenido de /etc/passwd # TODO

Ejemplo: passwd de ejemplo

Entrada

root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
lucia:x:1001:1001:Lucía:/home/lucia:/bin/zsh
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
ana:x:1000:1000:Ana:/home/ana:/bin/bash

Salida esperada

ana (1000) -> /home/ana [/bin/bash]
lucia (1001) -> /home/lucia [/bin/zsh]
Total: 2

18. Crear ficheros y asignar permisos

Medio · Administración de Sistemas Operativos · apuntesdam.com/subject/sistemas/topic/administracion-sistemas-operativos

Por la entrada llega una lista de nombres de fichero (uno por línea). En un directorio temporal (mktemp -d), crea cada fichero y asígnale permisos: 750 si termina en .sh, 600 si termina en .key y 644 en cualquier otro caso. Después muestra, ordenados por nombre, sus permisos en octal y su nombre con stat -c '%a %n'.

Código de partida (bash)
#!/bin/bashdir=$(mktemp -d)cd "$dir" || exit 1 # TODO: crea los ficheros de la entrada y asigna los permisos

Ejemplo: Tres tipos

Entrada

copia.sh
notas.txt
servidor.key

Salida esperada

750 copia.sh
644 notas.txt
600 servidor.key

19. Encuentra el fallo: la alerta que salta siempre

Medio · Administración de Sistemas Operativos · apuntesdam.com/subject/sistemas/topic/administracion-sistemas-operativos

El script lee el porcentaje de uso del disco y debería mostrar «ALERTA: disco al N %» si pasa de 80, u «OK» si no. Con un uso del 9 % también da la alerta. Corrígelo.

Código de partida (bash)
#!/bin/bashread -r usoif [ $uso > 80 ]; then  echo "ALERTA: disco al $uso %"else  echo "OK"fi

Ejemplo: Disco casi lleno

Entrada

95

Salida esperada

ALERTA: disco al 95 %

Ejemplo: Disco con espacio

Entrada

9

Salida esperada

OK

Ejemplo: Justo en el límite

Entrada

80

Salida esperada

OK

20. Encuentra el fallo: el mayor que no lo es

Medio · Administración de Sistemas Operativos · apuntesdam.com/subject/sistemas/topic/administracion-sistemas-operativos

El script lee dos números en la misma línea y muestra el mayor. Con «7 3» funciona, pero con «10 9» dice que el mayor es 9. Corrígelo.

Código de partida (bash)
#!/bin/bashread -r a bif [[ $a > $b ]]; then  echo "El mayor es $a"else  echo "El mayor es $b"fi

Ejemplo: Un dígito

Entrada

7 3

Salida esperada

El mayor es 7

Ejemplo: Dos cifras contra una

Entrada

10 9

Salida esperada

El mayor es 10

Ejemplo: El segundo es mayor

Entrada

4 25

Salida esperada

El mayor es 25

21. Encuentra el fallo: el nombre con espacio

Medio · Administración de Sistemas Operativos · apuntesdam.com/subject/sistemas/topic/administracion-sistemas-operativos

El script da la bienvenida a Ana López y a cualquier otra persona le dice «No te conozco». Cuando escribe ella su nombre completo, también le dice que no la conoce y aparece un error. Corrígelo.

Código de partida (bash)
#!/bin/bashread -r nombreif [ $nombre = "Ana López" ]; then  echo "Bienvenida, Ana"else  echo "No te conozco"fi

Ejemplo: Nombre con espacio

Entrada

Ana López

Salida esperada

Bienvenida, Ana

Ejemplo: Otra persona

Entrada

Luis

Salida esperada

No te conozco

22. Alerta de espacio en disco

Difícil · Administración de Sistemas Operativos · apuntesdam.com/subject/sistemas/topic/administracion-sistemas-operativos

Por la entrada llega la salida de «df -h» (con su cabecera). Muestra «ALERTA: PUNTO_DE_MONTAJE al N%» para cada sistema de ficheros con un uso igual o superior al umbral que se pasa en la primera línea de la entrada, y «Todo correcto» si no hay ninguno.

Código de partida (bash)
#!/bin/bashread -r umbral # TODO

Ejemplo: Umbral 80

Entrada

80
S.ficheros Tamaño Usados Disp Uso% Montado en
/dev/sda1 50G 45G 5G 90% /
/dev/sdb1 200G 100G 100G 50% /datos
/dev/sdc1 20G 16G 4G 80% /var

Salida esperada

ALERTA: / al 90%
ALERTA: /var al 80%

23. ¿Quién intenta entrar por SSH?

Difícil · Administración de Sistemas Operativos · apuntesdam.com/subject/sistemas/topic/administracion-sistemas-operativos

La entrada es un fragmento de /var/log/auth.log. Las líneas de contraseñas fallidas tienen la forma «… sshd[PID]: Failed password for USUARIO from IP port N ssh2» o «… Failed password for invalid user USUARIO from IP port N ssh2». Cuenta los intentos fallidos por IP y muéstralos de más a menos intentos (a igualdad, por IP en orden alfabético) con el formato «IP: N intentos», añadiendo « → BLOQUEAR» si son 5 o más. Termina con «Usuarios inexistentes probados: N» (usuarios distintos que aparecen tras «invalid user»). Las demás líneas se ignoran.

Código de partida (bash)
#!/bin/bash# Lee el log por la entrada estándarlog=$(cat) # TODO: intentos fallidos por IP y usuarios inexistentes probados

Ejemplo: Ataque desde una IP

Entrada

Oct  1 10:00:01 srv sshd[901]: Failed password for root from 203.0.113.9 port 50122 ssh2
Oct  1 10:00:03 srv sshd[901]: Failed password for root from 203.0.113.9 port 50122 ssh2
Oct  1 10:00:05 srv sshd[905]: Failed password for invalid user admin from 203.0.113.9 port 50200 ssh2
Oct  1 10:00:07 srv sshd[905]: Failed password for invalid user test from 203.0.113.9 port 50200 ssh2
Oct  1 10:00:09 srv sshd[905]: Failed password for invalid user admin from 203.0.113.9 port 50201 ssh2
Oct  1 10:02:00 srv sshd[950]: Accepted publickey for ana from 192.168.1.20 port 41000 ssh2
Oct  1 10:05:00 srv sshd[960]: Failed password for ana from 192.168.1.20 port 41100 ssh2

Salida esperada

203.0.113.9: 5 intentos → BLOQUEAR
192.168.1.20: 1 intentos
Usuarios inexistentes probados: 2

Ejemplo: Varias IP empatadas

Entrada

a sshd[1]: Failed password for root from 10.0.0.9 port 1 ssh2
b sshd[1]: Failed password for root from 10.0.0.10 port 1 ssh2
c sshd[1]: Failed password for invalid user pi from 10.0.0.9 port 2 ssh2
d sshd[1]: Failed password for invalid user pi from 10.0.0.10 port 2 ssh2
e sshd[1]: Connection closed by 10.0.0.11 port 3

Salida esperada

10.0.0.10: 2 intentos
10.0.0.9: 2 intentos
Usuarios inexistentes probados: 1

24. Encuentra el fallo: el total que se queda en cero

Difícil · Administración de Sistemas Operativos · apuntesdam.com/subject/sistemas/topic/administracion-sistemas-operativos

El script suma los números que recibe por la entrada, uno por línea, y muestra «Total: N». Siempre muestra «Total: 0». Corrígelo.

Código de partida (bash)
#!/bin/bashtotal=0cat | while read -r n; do  total=$((total + n))doneecho "Total: $total"

Ejemplo: Tres números

Entrada

3
5
2

Salida esperada

Total: 10

Ejemplo: Con un negativo

Entrada

100
-40

Salida esperada

Total: 60

25. Encuentra el fallo: las 08:09 que rompen la cuenta

Difícil · Administración de Sistemas Operativos · apuntesdam.com/subject/sistemas/topic/administracion-sistemas-operativos

El script lee una hora con el formato HH:MM y muestra los minutos que han pasado desde medianoche. Con 10:30 funciona (630), pero con 08:09 da un error. Corrígelo.

Código de partida (bash)
#!/bin/bashread -r horah=${hora%%:*}m=${hora##*:}echo $(( h * 60 + m ))

Ejemplo: Media mañana

Entrada

10:30

Salida esperada

630

Ejemplo: Con ceros delante

Entrada

08:09

Salida esperada

489

Ejemplo: Última hora

Entrada

23:59

Salida esperada

1439

26. Permisos: de letras a octal y al revés

Muy difícil · Administración de Sistemas Operativos · apuntesdam.com/subject/sistemas/topic/administracion-sistemas-operativos

Cada línea es un permiso en notación simbólica de 9 caracteres (rwxr-x---) o en octal de 3 o 4 cifras (750, 4755). Convierte cada uno a la otra notación: «rwxr-x--- = 750» y «0644 = rw-r--r--». Ten en cuenta los bits especiales: setuid (4), setgid (2) y sticky (1), que en la notación simbólica cambian la x del propietario, del grupo o de otros por s o t (o por S o T si ese permiso de ejecución no está). Una entrada simbólica con bits especiales da 4 cifras (rwsr-xr-x = 4755); sin ellos, 3. Una entrada octal se muestra siempre con 4 cifras a la izquierda del «=». Si la línea no es válida, «línea: no válido».

Código de partida (bash)
#!/bin/bash # TODO: convierte cada línea de simbólico a octal o de octal a simbólico

Ejemplo: Permisos habituales

Entrada

rwxr-x---
rw-r--r--
644
750

Salida esperada

rwxr-x--- = 750
rw-r--r-- = 644
0644 = rw-r--r--
0750 = rwxr-x---

Ejemplo: Bits especiales

Entrada

rwsr-xr-x
rwxrwxrwt
rwxrws---
4755
1777
2770

Salida esperada

rwsr-xr-x = 4755
rwxrwxrwt = 1777
rwxrws--- = 2770
4755 = rwsr-xr-x
1777 = rwxrwxrwt
2770 = rwxrws---

Redes Informáticas

27. ¿Llega este cable?

Medio · Redes Informáticas · apuntesdam.com/subject/sistemas/topic/redes-informaticas

Cada línea describe un tramo de cable: «medio metros velocidad», con el medio cat5e, cat6, cat6a, cat8 (par trenzado), om3, om4 (fibra multimodo) u os2 (fibra monomodo) y la velocidad en Gbit/s con una G (1G, 10G…). El diccionario MAXIMO indica la distancia máxima de cada medio a cada velocidad que admite. Muestra «medio, M m a VG: SÍ (máximo X m)» si el tramo es válido; «…: NO (máximo X m; sirve a, b…)» si es demasiado largo, con los medios que sí llegarían en orden alfabético (o «ninguno»); «…: NO (no admite VG; velocidades inferiores válidas: …)» si el medio no admite esa velocidad (o «ninguna»); y «medio: medio desconocido» si no existe.

Código de partida (python)
import sys # Distancia máxima en metros de cada medio para cada velocidad (Gbit/s)MAXIMO = {    "cat5e": {1: 100},    "cat6": {1: 100, 10: 55},    "cat6a": {1: 100, 10: 100},    "cat8": {10: 30, 25: 30, 40: 30},    "om3": {1: 550, 10: 300, 40: 100},    "om4": {1: 550, 10: 400, 40: 150},    "os2": {1: 10000, 10: 10000, 40: 10000},} # TODO: decide si cada tramo de cable es válido

Ejemplo: Oficina

Entrada

cat6 70 1G
cat6 70 10G
cat6a 90 10G
om4 350 10G

Salida esperada

cat6, 70 m a 1G: SÍ (máximo 100 m)
cat6, 70 m a 10G: NO (máximo 55 m; sirve cat6a, om3, om4, os2)
cat6a, 90 m a 10G: SÍ (máximo 100 m)
om4, 350 m a 10G: SÍ (máximo 400 m)

Ejemplo: Velocidades no admitidas

Entrada

cat5e 20 10G
cat8 10 1G
coaxial 50 1G

Salida esperada

cat5e, 20 m a 10G: NO (no admite 10G; velocidades inferiores válidas: 1G)
cat8, 10 m a 1G: NO (no admite 1G; velocidades inferiores válidas: ninguna)
coaxial: medio desconocido

28. La tabla MAC de un switch

Difícil · Redes Informáticas · apuntesdam.com/subject/sistemas/topic/redes-informaticas

Un switch aprende en qué puerto está cada equipo mirando la MAC de origen de las tramas que recibe. La primera línea es «puertos N»; cada línea siguiente, una trama recibida: «puerto MAC_origen MAC_destino». Para cada trama: si la MAC de origen no estaba en la tabla (o estaba en otro puerto, porque el equipo se ha movido), muestra «Aprende MAC en el puerto P». Después, con dos espacios delante: si el destino es FF:FF:FF:FF:FF:FF, «Difusión: inunda por a, b…» (todos los puertos menos el de entrada); si el destino no está en la tabla, «MAC desconocido: inunda por …»; si está en el mismo puerto de entrada, «MAC está en el mismo puerto: descarta la trama»; y si no, «Reenvía a MAC por el puerto P». Al final muestra «Tabla MAC:» y cada entrada « MAC → puerto P» ordenada por MAC.

Código de partida (python)
import sys lineas = [l.split() for l in sys.stdin.read().splitlines() if l.strip()]puertos = int(lineas[0][1])BROADCAST = "FF:FF:FF:FF:FF:FF" # TODO: simula cómo aprende y reenvía las tramas un switch

Ejemplo: Arranque de una red

Entrada

puertos 4
1 AA:AA:AA:00:00:01 FF:FF:FF:FF:FF:FF
2 BB:BB:BB:00:00:02 AA:AA:AA:00:00:01
1 AA:AA:AA:00:00:01 BB:BB:BB:00:00:02
3 CC:CC:CC:00:00:03 DD:DD:DD:00:00:04

Salida esperada

Aprende AA:AA:AA:00:00:01 en el puerto 1
  Difusión: inunda por 2, 3, 4
Aprende BB:BB:BB:00:00:02 en el puerto 2
  Reenvía a AA:AA:AA:00:00:01 por el puerto 1
  Reenvía a BB:BB:BB:00:00:02 por el puerto 2
Aprende CC:CC:CC:00:00:03 en el puerto 3
  DD:DD:DD:00:00:04 desconocido: inunda por 1, 2, 4
Tabla MAC:
  AA:AA:AA:00:00:01 → puerto 1
  BB:BB:BB:00:00:02 → puerto 2
  CC:CC:CC:00:00:03 → puerto 3

29. Puntos únicos de fallo de una red

Muy difícil · Redes Informáticas · apuntesdam.com/subject/sistemas/topic/redes-informaticas

La primera línea es «raiz NOMBRE»: el equipo por el que se sale a Internet (normalmente el router). Cada línea siguiente es un enlace «enlace A B» entre dos equipos (los enlaces funcionan en los dos sentidos). Para cada equipo con dos o más enlaces que no sea la raíz, en orden alfabético, calcula qué equipos dejarían de poder llegar a la raíz si ese equipo fallara: muestra «Si falla X: sin red a, b…» (en orden alfabético) o «Si falla X: nadie pierde la red (hay caminos alternativos)». Al final, «Puntos únicos de fallo: N» con los equipos cuyo fallo deja a alguien sin red.

Código de partida (python)
import sys lineas = [l.split() for l in sys.stdin.read().splitlines() if l.strip()]raiz = lineas[0][1]                       # equipo desde el que se sale a Internetenlaces = [(a, b) for _, a, b in lineas[1:]] # TODO: para cada equipo de interconexión, ¿quién se queda sin red si falla?

Ejemplo: Estrella extendida

Entrada

raiz router
enlace router sw-core
enlace sw-core sw-planta1
enlace sw-core sw-planta2
enlace sw-planta1 pc1
enlace sw-planta1 pc2
enlace sw-planta2 pc3
enlace sw-planta2 impresora

Salida esperada

Si falla sw-core: sin red impresora, pc1, pc2, pc3, sw-planta1, sw-planta2
Si falla sw-planta1: sin red pc1, pc2
Si falla sw-planta2: sin red impresora, pc3
Puntos únicos de fallo: 3

Ejemplo: Con enlace redundante

Entrada

raiz router
enlace router sw-a
enlace router sw-b
enlace sw-a sw-b
enlace sw-a pc1
enlace sw-b pc2
enlace sw-b ap
enlace ap portatil

Salida esperada

Si falla ap: sin red portatil
Si falla sw-a: sin red pc1
Si falla sw-b: sin red ap, pc2, portatil
Puntos únicos de fallo: 3

Configuración de Redes TCP/IP

30. ¿Pueden hablar sin router?

Medio · Configuración de Redes TCP/IP · apuntesdam.com/subject/sistemas/topic/configuracion-redes

Dos equipos solo se comunican directamente si están en la misma red, es decir, si su IP AND la máscara da el mismo resultado. Cada línea tiene «IP/prefijo IP2»: el primer equipo con su prefijo y la IP del segundo (que usa la misma máscara). Muestra «IP y IP2: misma red RED/prefijo, comunicación directa» o «IP y IP2: redes distintas (RED1 y RED2), hace falta un router». Las funciones a_entero y a_texto convierten entre la notación con puntos y un entero de 32 bits.

Código de partida (python)
import sys def a_entero(ip):    a, b, c, d = (int(x) for x in ip.split("."))    return (a << 24) | (b << 16) | (c << 8) | d def a_texto(n):    return ".".join(str((n >> s) & 255) for s in (24, 16, 8, 0)) # TODO: ¿están los dos equipos en la misma red?

Ejemplo: Redes /24 y /26

Entrada

192.168.1.10/24 192.168.1.200
192.168.1.10/26 192.168.1.70
192.168.1.65/26 192.168.1.126

Salida esperada

192.168.1.10 y 192.168.1.200: misma red 192.168.1.0/24, comunicación directa
192.168.1.10 y 192.168.1.70: redes distintas (192.168.1.0 y 192.168.1.64), hace falta un router
192.168.1.65 y 192.168.1.126: misma red 192.168.1.64/26, comunicación directa

Ejemplo: Prefijos que cortan otros octetos

Entrada

172.16.37.10/20 172.16.40.1
172.16.37.10/20 172.16.48.1
10.1.2.3/8 10.200.0.1

Salida esperada

172.16.37.10 y 172.16.40.1: misma red 172.16.32.0/20, comunicación directa
172.16.37.10 y 172.16.48.1: redes distintas (172.16.32.0 y 172.16.48.0), hace falta un router
10.1.2.3 y 10.200.0.1: misma red 10.0.0.0/8, comunicación directa

31. Abreviar y expandir direcciones IPv6

Difícil · Configuración de Redes TCP/IP · apuntesdam.com/subject/sistemas/topic/configuracion-redes

Cada línea es una dirección IPv6. Si está completa (8 grupos sin «::»), abréviala: quita los ceros a la izquierda de cada grupo (un grupo todo a ceros queda como 0) y sustituye por «::» la serie más larga de dos o más grupos seguidos a cero (si hay dos igual de largas, la primera; solo se usa «::» una vez). Si está abreviada, expándela a 8 grupos de 4 cifras. Usa siempre minúsculas. Muestra «entrada → resultado».

Código de partida (python)
import sys # TODO: abrevia las direcciones IPv6 completas y expande las abreviadas

Ejemplo: Abreviar

Entrada

2001:0db8:0000:0000:0000:ff00:0042:8329
fe80:0000:0000:0000:0202:b3ff:fe1e:8329
2001:0DB8:0000:0001:0000:0000:0000:0001

Salida esperada

2001:0db8:0000:0000:0000:ff00:0042:8329 → 2001:db8::ff00:42:8329
fe80:0000:0000:0000:0202:b3ff:fe1e:8329 → fe80::202:b3ff:fe1e:8329
2001:0DB8:0000:0001:0000:0000:0000:0001 → 2001:db8:0:1::1

Ejemplo: Expandir

Entrada

2001:db8::ff00:42:8329
::1
fe80::

Salida esperada

2001:db8::ff00:42:8329 → 2001:0db8:0000:0000:0000:ff00:0042:8329
::1 → 0000:0000:0000:0000:0000:0000:0000:0001
fe80:: → fe80:0000:0000:0000:0000:0000:0000:0000

32. Calculadora VLSM

Muy difícil · Configuración de Redes TCP/IP · apuntesdam.com/subject/sistemas/topic/configuracion-redes

La primera línea es «red A.B.C.D/p», la red que hay que repartir. Cada línea siguiente es «nombre hosts»: un departamento y los equipos que necesita. Con VLSM, asigna las subredes de la que más hosts necesita a la que menos (a igualdad, por nombre), cada una del tamaño mínimo que alcance (2^bits − 2 ≥ hosts, con al menos 2 bits) y una detrás de otra desde el principio de la red. Para cada una muestra «nombre (hosts): RED/prefijo, hosts PRIMERO - ÚLTIMO, difusión DIF, sobran N»; si ya no cabe, «nombre: no hay espacio para N hosts» (y se sigue con las demás). Termina con «Libre: N direcciones desde IP» o «Libre: 0 direcciones».

Código de partida (python)
import sys def a_entero(ip):    a, b, c, d = (int(x) for x in ip.split("."))    return (a << 24) | (b << 16) | (c << 8) | d def a_texto(n):    return ".".join(str((n >> s) & 255) for s in (24, 16, 8, 0)) lineas = [l.split() for l in sys.stdin.read().splitlines() if l.strip()]base_ip, base_prefijo = lineas[0][1].split("/")departamentos = [(nombre, int(hosts)) for nombre, hosts in lineas[1:]] # TODO: reparte la red con VLSM, de la subred que más hosts necesita a la que menos

Ejemplo: Ejemplo del tema

Entrada

red 192.168.50.0/24
Desarrollo 100
Administracion 50
Direccion 10
Enlace 2

Salida esperada

Desarrollo (100): 192.168.50.0/25, hosts 192.168.50.1 - 192.168.50.126, difusión 192.168.50.127, sobran 26
Administracion (50): 192.168.50.128/26, hosts 192.168.50.129 - 192.168.50.190, difusión 192.168.50.191, sobran 12
Direccion (10): 192.168.50.192/28, hosts 192.168.50.193 - 192.168.50.206, difusión 192.168.50.207, sobran 4
Enlace (2): 192.168.50.208/30, hosts 192.168.50.209 - 192.168.50.210, difusión 192.168.50.211, sobran 0
Libre: 44 direcciones desde 192.168.50.212

Ejemplo: Instituto en una /22

Entrada

red 10.20.0.0/22
Aulas 400
Profesorado 120
Wifi 200
Servidores 20
Impresoras 12

Salida esperada

Aulas (400): 10.20.0.0/23, hosts 10.20.0.1 - 10.20.1.254, difusión 10.20.1.255, sobran 110
Wifi (200): 10.20.2.0/24, hosts 10.20.2.1 - 10.20.2.254, difusión 10.20.2.255, sobran 54
Profesorado (120): 10.20.3.0/25, hosts 10.20.3.1 - 10.20.3.126, difusión 10.20.3.127, sobran 6
Servidores (20): 10.20.3.128/27, hosts 10.20.3.129 - 10.20.3.158, difusión 10.20.3.159, sobran 10
Impresoras (12): 10.20.3.160/28, hosts 10.20.3.161 - 10.20.3.174, difusión 10.20.3.175, sobran 2
Libre: 80 direcciones desde 10.20.3.176

Servicios de Red, Recursos Compartidos y Seguridad

33. Permisos efectivos de una carpeta compartida

Medio · Servicios de Red, Recursos Compartidos y Seguridad · apuntesdam.com/subject/sistemas/topic/servicios-red-seguridad

En Windows, a una carpeta compartida por la red se le aplican dos capas de permisos: los del recurso compartido y los NTFS. Dentro de cada capa, un usuario suma los permisos de todos sus grupos (se queda con el mayor), una denegación explícita gana a cualquier permiso, y el permiso efectivo es el más restrictivo de las dos capas. Las líneas pueden ser «compartido GRUPO nivel», «ntfs GRUPO nivel», «denegar GRUPO» (denegación NTFS total) o «usuario NOMBRE grupo1,grupo2…». Los niveles, de menor a mayor, son ninguno, lectura, modificar y control. Para cada usuario, en el orden de la entrada, muestra «nombre: compartido X, NTFS Y → efectivo Z».

Código de partida (python)
import sys NIVELES = ["ninguno", "lectura", "modificar", "control"] lineas = [l.split() for l in sys.stdin.read().splitlines() if l.strip()] # TODO: calcula el permiso efectivo de cada usuario sobre la carpeta compartida

Ejemplo: Departamentos

Entrada

compartido Todos modificar
ntfs Ventas modificar
ntfs Direccion control
ntfs Todos lectura
usuario ana Ventas,Todos
usuario luis Todos
usuario marta Direccion,Todos

Salida esperada

ana: compartido modificar, NTFS modificar → efectivo modificar
luis: compartido modificar, NTFS lectura → efectivo lectura
marta: compartido modificar, NTFS control → efectivo modificar

Ejemplo: Denegación y compartido restrictivo

Entrada

compartido Todos lectura
compartido Admins control
ntfs Admins control
ntfs Todos modificar
denegar Becarios
usuario pedro Todos,Becarios
usuario eva Todos
usuario root Admins

Salida esperada

pedro: compartido lectura, NTFS ninguno → efectivo ninguno
eva: compartido lectura, NTFS modificar → efectivo lectura
root: compartido control, NTFS control → efectivo control

34. Un cortafuegos de primera coincidencia

Difícil · Servicios de Red, Recursos Compartidos y Seguridad · apuntesdam.com/subject/sistemas/topic/servicios-red-seguridad

Un cortafuegos revisa sus reglas en orden y aplica la primera que coincide; si ninguna coincide, aplica la política por defecto. La entrada tiene dos partes separadas por «---». La primera empieza con «politica allow|deny» y sigue con reglas «allow|deny protocolo puerto from red» (protocolo tcp, udp o any; puerto un número o any; red en CIDR, una IP sola o any). La segunda son paquetes entrantes «protocolo ip_origen puerto_destino». Para cada paquete muestra «proto ip → puerto: ALLOW (regla N)», «…: DENY (regla N)» o «…: ACCIÓN (política por defecto)».

Código de partida (python)
import sys def a_entero(ip):    a, b, c, d = (int(x) for x in ip.split("."))    return (a << 24) | (b << 16) | (c << 8) | d texto = sys.stdin.read().split("---")reglas = [l.split() for l in texto[0].splitlines() if l.strip()]      # la primera línea es «politica allow|deny»paquetes = [l.split() for l in texto[1].splitlines() if l.strip()] # TODO: aplica las reglas en orden a cada paquete (gana la primera que coincide)

Ejemplo: Servidor web

Entrada

politica deny
allow tcp 22 from 192.168.1.0/24
allow tcp 443 from any
allow tcp 80 from any
deny tcp 3306 from any
allow udp 53 from 10.0.0.0/8
---
tcp 192.168.1.50 22
tcp 203.0.113.7 22
tcp 203.0.113.7 443
tcp 192.168.1.50 3306
udp 10.20.30.40 53
udp 8.8.8.8 53

Salida esperada

tcp 192.168.1.50 → 22: ALLOW (regla 1)
tcp 203.0.113.7 → 22: DENY (política por defecto)
tcp 203.0.113.7 → 443: ALLOW (regla 2)
tcp 192.168.1.50 → 3306: DENY (regla 4)
udp 10.20.30.40 → 53: ALLOW (regla 5)
udp 8.8.8.8 → 53: DENY (política por defecto)

35. ¿Cuánto aguantaría esta contraseña?

Muy difícil · Servicios de Red, Recursos Compartidos y Seguridad · apuntesdam.com/subject/sistemas/topic/servicios-red-seguridad

Cada línea es una contraseña. Si en minúsculas está en el DICCIONARIO, o lo está tras quitar los dígitos, puntos y signos de exclamación del final (Barcelona2024!), muestra «clave: muy débil (está en los diccionarios de contraseñas)». Si no, calcula su entropía: longitud × log2(juego), donde el juego suma 26 si hay minúsculas, 26 si hay mayúsculas, 10 si hay dígitos y 33 si hay otros símbolos; si tiene muy pocos caracteres distintos (no más de un tercio de su longitud), divide la entropía entre 2. Clasifícala como débil (< 40 bits), media (< 60), fuerte (< 80) o muy fuerte, y estima el tiempo medio de un ataque de fuerza bruta a 10¹⁰ intentos por segundo: 2^entropía / 2 / 10¹⁰ segundos, expresado en años, días, horas o minutos (la mayor unidad con valor ≥ 1; con 1 decimal si es menor que 100, y si no, redondeado y con punto de miles, como «857.012 años»), «más de un millón de …» si llega al millón, «menos de 1 minuto» o «instantáneo» (< 1 s). Muestra «clave: nivel (X bits, TIEMPO por fuerza bruta)», con los bits con 1 decimal.

Código de partida (python)
import sysimport math DICCIONARIO = {"password", "123456", "qwerty", "admin", "iloveyou", "dragon", "monkey", "letmein",               "futbol", "barcelona", "madrid", "contraseña", "hola", "princesa", "superman"} # TODO: estima la fortaleza de cada contraseña

Ejemplo: Varias contraseñas

Entrada

password
Barcelona2024!
Gato
T3ruel!
aaaaaaaaaaaa
caballo-bateria-grapa-correcto

Salida esperada

password: muy débil (está en los diccionarios de contraseñas)
Barcelona2024!: muy débil (está en los diccionarios de contraseñas)
Gato: débil (22.8 bits, instantáneo por fuerza bruta)
T3ruel!: media (46.0 bits, 58.2 minutos por fuerza bruta)
aaaaaaaaaaaa: débil (28.2 bits, instantáneo por fuerza bruta)
caballo-bateria-grapa-correcto: muy fuerte (176.5 bits, más de un millón de años por fuerza bruta)

Ejemplo: Largas y aleatorias

Entrada

Xk9#mP2$vL8q
z7Q!r4T@w1E%y6U^

Salida esperada

Xk9#mP2$vL8q: fuerte (78.8 bits, 856.735 años por fuerza bruta)
z7Q!r4T@w1E%y6U^: muy fuerte (105.1 bits, más de un millón de años por fuerza bruta)

Documentación y Aplicaciones Ofimáticas

36. Informe del inventario

Medio · Documentación y Aplicaciones Ofimáticas · apuntesdam.com/subject/sistemas/topic/documentacion-ofimatica

La primera línea es «hoy=AAAA-MM-DD» y el resto un CSV separado por punto y coma con cabecera «codigo;tipo;aula;precio;fin_garantia». Agrupa los equipos por aula (en orden alfabético) y muestra «AULA: N equipos, X €» (2 decimales; «1 equipo» en singular), añadiendo «, sin garantía: código1, código2» con los códigos ordenados si alguno tiene la garantía vencida (fecha de fin anterior a hoy). Termina con «TOTAL: N equipos, X €, M sin garantía (P %)» con el porcentaje sin decimales. Es lo que harías con una tabla dinámica en la hoja de cálculo.

Código de partida (python)
import sysimport csvfrom datetime import date lineas = sys.stdin.read().splitlines()hoy = date.fromisoformat(lineas[0].split("=")[1])filas = list(csv.DictReader(lineas[1:], delimiter=";"))   # codigo;tipo;aula;precio;fin_garantia # TODO: informe por aula con número de equipos, valor y garantías caducadas

Ejemplo: Instituto

Entrada

hoy=2026-10-01
codigo;tipo;aula;precio;fin_garantia
PC-201;sobremesa;2.01;780;2027-05-01
PC-202;sobremesa;2.01;780;2026-03-15
IMP-01;impresora;2.01;320.5;2025-11-30
PC-110;sobremesa;1.10;650;2028-01-10
PRY-01;proyector;1.10;899.99;2026-12-31
SRV-01;servidor;CPD;3400;2026-09-30

Salida esperada

1.10: 2 equipos, 1549.99 €
2.01: 3 equipos, 1880.50 €, sin garantía: IMP-01, PC-202
CPD: 1 equipo, 3400.00 €, sin garantía: SRV-01
TOTAL: 6 equipos, 6830.49 €, 3 sin garantía (50 %)

37. Un índice automático para la documentación

Difícil · Documentación y Aplicaciones Ofimáticas · apuntesdam.com/subject/sistemas/topic/documentacion-ofimatica

La entrada es un documento en Markdown. Genera su índice con los títulos de nivel 2 (##) y 3 (###), numerados (1., 1.1., 1.2., 2.…; la numeración de nivel 3 se reinicia en cada nivel 2) y con un enlace a su ancla: «1. [Requisitos previos](#requisitos-previos)» para el nivel 2 y, con tres espacios delante, « 1.1. [Hardware](#hardware)» para el nivel 3. El ancla se obtiene del título en minúsculas, sin tildes, eliminando lo que no sea letra, número, espacio o guion, y cambiando los espacios por guiones; si un ancla se repite, la segunda vez se le añade «-1», la tercera «-2»… Ignora el título de nivel 1 y cualquier línea dentro de un bloque de código (entre líneas que empiezan por ```).

Código de partida (python)
import sysimport reimport unicodedata lineas = sys.stdin.read().splitlines() # TODO: genera el índice numerado de los títulos (## y ###) con su ancla

Ejemplo: Manual

Entrada

# Manual de instalación del servidor

## Requisitos previos
Texto.
### Hardware
### Software
## Instalación
```bash
## esto es un comentario dentro de un bloque de código, no un título
```
### Configuración de la red
### Configuración de la red
## Copias de seguridad: ¿cómo y cuándo?

Salida esperada

1. [Requisitos previos](#requisitos-previos)
   1.1. [Hardware](#hardware)
   1.2. [Software](#software)
2. [Instalación](#instalacion)
   2.1. [Configuración de la red](#configuracion-de-la-red)
   2.2. [Configuración de la red](#configuracion-de-la-red-1)
3. [Copias de seguridad: ¿cómo y cuándo?](#copias-de-seguridad-como-y-cuando)

38. Un conversor de Markdown a HTML

Muy difícil · Documentación y Aplicaciones Ofimáticas · apuntesdam.com/subject/sistemas/topic/documentacion-ofimatica

Convierte un documento Markdown a HTML con estas reglas. Bloques: los títulos (# a ######) pasan a <h1>…<h6>; las líneas que empiezan por «- » o «* » forman una lista <ul> y las que empiezan por «1. », «2. »… una <ol>, con cada elemento en un <li>; las líneas seguidas de texto normal forman un párrafo <p> (unidas con un espacio); una línea en blanco cierra el párrafo o la lista; y lo que hay entre dos líneas que empiezan por `` es un bloque <pre><code>…</code></pre> en el que no se interpreta nada. En línea, dentro de títulos, párrafos y elementos de lista: código` → <code>, negrita → <strong>, *cursiva* → <em> y [texto](url) → <a href="url">texto</a>. Antes de nada, escapa los caracteres <, > y & (html.escape con quote=False), también dentro de los bloques de código. Muestra cada bloque en su propia línea, y las etiquetas de apertura y cierre de las listas en líneas aparte.

Código de partida (python)
import sysimport reimport html lineas = sys.stdin.read().splitlines() # TODO: convierte el Markdown en HTML

Ejemplo: Procedimiento

Entrada

# Alta de un usuario

Este procedimiento lo sigue el **técnico de guardia** cuando llega una
petición *aprobada*.

## Pasos

1. Comprueba la petición en el [gestor de incidencias](https://tickets.empresa.es).
2. Crea la cuenta con `New-ADUser`.
3. Añádela a sus grupos.

Notas:
- Nunca uses contraseñas <123456>.
- Avisa a **RR. HH.** al terminar.

```powershell
New-ADUser -Name "Ana Ruiz" -Enabled $true
if ($x -lt 5) { "<ok>" }
```

Salida esperada

<h1>Alta de un usuario</h1>
<p>Este procedimiento lo sigue el <strong>técnico de guardia</strong> cuando llega una petición <em>aprobada</em>.</p>
<h2>Pasos</h2>
<ol>
<li>Comprueba la petición en el <a href="https://tickets.empresa.es">gestor de incidencias</a>.</li>
<li>Crea la cuenta con <code>New-ADUser</code>.</li>
<li>Añádela a sus grupos.</li>
</ol>
<p>Notas:</p>
<ul>
<li>Nunca uses contraseñas &lt;123456&gt;.</li>
<li>Avisa a <strong>RR. HH.</strong> al terminar.</li>
</ul>
<pre><code>New-ADUser -Name "Ana Ruiz" -Enabled $true
if ($x -lt 5) { "&lt;ok&gt;" }</code></pre>

Ejercicios de Sistemas Informáticos

39. Contar ficheros por extensión

Medio · Ejercicios de Sistemas Informáticos · apuntesdam.com/subject/sistemas/topic/ejercicios-sistemas

Por la entrada llega una lista de nombres de fichero (uno por línea). Muestra cuántos hay de cada extensión, en minúsculas y ordenadas alfabéticamente, con el formato «ext: N». Los ficheros sin extensión cuentan como «(sin extensión)» y se muestran al final.

Código de partida (bash)
#!/bin/bash# Entrada: un nombre de fichero por línea # TODO

Ejemplo: Varias extensiones

Entrada

informe.PDF
foto.jpg
notas.txt
FOTO2.JPG
Makefile
script.sh
factura.pdf

Salida esperada

jpg: 2
pdf: 2
sh: 1
txt: 1
(sin extensión): 1

40. Validar direcciones IPv4

Difícil · Ejercicios de Sistemas Informáticos · apuntesdam.com/subject/sistemas/topic/ejercicios-sistemas

Por la entrada llega una dirección por línea. Para cada una muestra «IP válida» o «IP no válida». Es válida si tiene cuatro números separados por puntos, cada uno entre 0 y 255 y sin ceros a la izquierda (salvo el propio 0).

Código de partida (bash)
#!/bin/bashwhile read -r ip || [ -n "$ip" ]; do  # TODO: comprueba la IP  echo "IP válida"done

Ejemplo: Casos variados

Entrada

192.168.1.10
256.1.1.1
10.0.0
0.0.0.0
192.168.01.1
hola

Salida esperada

IP válida
IP no válida
IP no válida
IP válida
IP no válida
IP no válida

41. Red y broadcast con aritmética de bits

Difícil · Ejercicios de Sistemas Informáticos · apuntesdam.com/subject/sistemas/topic/ejercicios-sistemas

Por la entrada llega una dirección con prefijo, por ejemplo 192.168.1.130/26. Calcula con bash (sin herramientas externas) y muestra: Red: 192.168.1.128 Broadcast: 192.168.1.191 Hosts: 62

Código de partida (bash)
#!/bin/bashread -r entradaip=${entrada%/*}prefijo=${entrada#*/} # TODO: calcula red, broadcast y hosts

Ejemplo: /26

Entrada

192.168.1.130/26

Salida esperada

Red: 192.168.1.128
Broadcast: 192.168.1.191
Hosts: 62

Ejemplo: /20

Entrada

172.16.37.10/20

Salida esperada

Red: 172.16.32.0
Broadcast: 172.16.47.255
Hosts: 4094

Ejercicios largos

42. Planificador de procesos: FCFS, SJF, SRTF y Round Robin

Muy difícil · Python · 100 minutos · apuntesdam.com/ejercicios/python/planificador-de-procesos

Un ordenador ejecuta muchos más procesos que núcleos tiene, así que el planificador del sistema operativo decide en cada momento cuál usa la CPU. La decisión cambia mucho el tiempo que espera cada programa: es uno de los temas que más cae en los exámenes de Sistemas, siempre con el mismo tipo de ejercicio: dibujar el diagrama de Gantt y calcular tiempos.

En este ejercicio vas a escribir ese planificador. Los procesos llegan en distintos instantes con una ráfaga de CPU (lo que necesitan ejecutar), y el programa simula cuatro algoritmos clásicos: FCFS (por orden de llegada), SJF (el de ráfaga más corta entre los que esperan, sin interrumpirlo), SRTF (el de menor tiempo restante, interrumpiendo si llega uno más corto) y Round Robin (turnos de un quantum fijo).

El resultado de cada proceso se mide con dos tiempos: el de retorno (desde que llega hasta que termina) y el de espera (el retorno menos lo que de verdad ha usado la CPU). En la web tienes también el simulador visual de planificación para comprobar tus resultados.

Requisitos

  • La primera línea es el algoritmo: FCFS, SJF, SRTF o RR q (con q, el quantum, un entero mayor que 0). Si no es válida, escribe Algoritmo no válido y nada más. Cada línea siguiente es un proceso: nombre llegada ráfaga, enteros con la ráfaga mayor que 0. Una línea con otro formato o un nombre repetido escribe Aviso: línea N ignorada. Si no queda ningún proceso, No hay procesos.

  • Desempates: FCFS y Round Robin atienden por orden de llegada y, a igual llegada, por el orden de la entrada. SJF elige la menor ráfaga y SRTF el menor tiempo restante; si empatan, el que llegó antes y luego el de la entrada. En Round Robin, cuando a un proceso se le acaba el quantum, los procesos que han llegado hasta ese instante entran en la cola antes que él.

  • SRTF reevalúa cada vez que llega un proceso: si el recién llegado necesita menos que lo que le queda al que está en la CPU, lo expulsa. Si nadie está listo, la CPU queda ociosa hasta la siguiente llegada.

  • Escribe Algoritmo: … con su nombre completo (el de la tabla; en Round Robin, Round Robin con quantum q) y Diagrama: | P1 0-3 | P2 3-7 | … |: cada tramo con el proceso, el inicio y el fin, -- para la CPU ociosa, y los tramos seguidos del mismo proceso unidos en uno solo.

  • Una tabla con la cabecera Proceso, Llegada, Ráfaga, Fin, Retorno, Espera y una fila por proceso en el orden de la entrada: el nombre en 8 caracteres a la izquierda y los números alineados a la derecha en 8, 8, 6, 9 y 8 caracteres.

  • Para terminar, Media de retorno: X · Media de espera: Y y Uso de la CPU: Z % (tiempo ocupado entre el instante final), con dos decimales y coma decimal.

Formato de la entrada

  • Línea 1: FCFS, SJF, SRTF o RR q. Resto: nombre llegada ráfaga.

Nombre de cada algoritmo en la salida
EntradaAlgoritmo
FCFSFCFS (primero en llegar, primero en ser servido)
SJFSJF (el más corto primero, sin expulsión)
SRTFSRTF (menor tiempo restante, con expulsión)
RR qRound Robin con quantum q

Ejemplo: Round Robin con quantum 2

Entrada

RR 2
P1 0 5
P2 1 3
P3 2 8
P4 3 6

Salida esperada

Algoritmo: Round Robin con quantum 2
Diagrama: | P1 0-2 | P2 2-4 | P3 4-6 | P1 6-8 | P4 8-10 | P2 10-11 | P3 11-13 | P1 13-14 | P4 14-16 | P3 16-18 | P4 18-20 | P3 20-22 |
Proceso  Llegada  Ráfaga   Fin  Retorno  Espera
P1             0       5    14       14       9
P2             1       3    11       10       7
P3             2       8    22       20      12
P4             3       6    20       17      11
Media de retorno: 15,25 · Media de espera: 9,75
Uso de la CPU: 100,00 %

Ejemplo: SRTF con CPU ociosa

Entrada

SRTF
A 0 7
B 2 4
C 4 1
D 5 4
E 30 2
F x 3
A 1 1

Salida esperada

Aviso: línea 7 ignorada
Aviso: línea 8 ignorada
Algoritmo: SRTF (menor tiempo restante, con expulsión)
Diagrama: | A 0-2 | B 2-4 | C 4-5 | B 5-7 | D 7-11 | A 11-16 | -- 16-30 | E 30-32 |
Proceso  Llegada  Ráfaga   Fin  Retorno  Espera
A              0       7    16       16       9
B              2       4     7        5       1
C              4       1     5        1       0
D              5       4    11        6       2
E             30       2    32        2       0
Media de retorno: 6,00 · Media de espera: 2,40
Uso de la CPU: 56,25 %
Código de partida (python)
import sys NOMBRES = {"FCFS": "FCFS (primero en llegar, primero en ser servido)", "SJF": "SJF (el más corto primero, sin expulsión)",           "SRTF": "SRTF (menor tiempo restante, con expulsión)", "RR": "Round Robin"}  def coma(x):    return f"{x:.2f}".replace(".", ",")  def simular(algoritmo, quantum, procesos):    """Devuelve el diagrama de Gantt como lista de [nombre, inicio, fin] y el instante en que termina cada proceso."""    # TODO    return [], {}  def main():    lineas = sys.stdin.read().split("\n")    # TODO: lee el algoritmo y los procesos, simula y escribe el diagrama, la tabla y las medias  main()

43. Plan de direccionamiento con subredes VLSM

Difícil · Python · 70 minutos · apuntesdam.com/ejercicios/python/subredes-vlsm

Una empresa tiene asignada la red 192.168.10.0/24 y quiere dividirla entre sus departamentos. Con subredes del mismo tamaño se desperdician direcciones (el enlace entre dos routers no necesita 62 direcciones), así que se usa VLSM: cada subred recibe la máscara más ajustada a los equipos que va a tener.

El método que se enseña (y el que sigue este programa) es asignar primero las subredes más grandes: así cada bloque empieza en una dirección múltiplo de su tamaño y las subredes nunca se solapan. De cada subred hay que dar la dirección de red, la máscara, la primera y la última dirección que se pueden asignar a equipos y la de difusión.

Hacerlo a mano es lento y es fácil equivocarse con la última dirección o la de difusión. Una dirección IP no es más que un número de 32 bits escrito en cuatro bytes: con ese número, las cuentas son sumas y operaciones de bits.

Requisitos

  • La primera línea es la red, IP/prefijo, con el prefijo entre 8 y 30. Si no tiene ese formato o la IP no es válida, escribe Red no válida: texto. Si la IP no es la dirección de red de ese prefijo (tiene bits de host a 1), escribe IP no es una dirección de red (sería RED/prefijo). En los dos casos no se escribe nada más.

  • Cada línea siguiente es una subred: un nombre (puede tener espacios) y el número de hosts que necesita, un entero mayor que 0. Una línea con otro formato escribe Aviso: línea N ignorada.

  • Las subredes se asignan de mayor a menor número de hosts (si empatan, por orden de la entrada), una detrás de otra desde el principio de la red. Cada una recibe el prefijo más largo que tenga sitio para sus hosts más la dirección de red y la de difusión, y como mucho /30 (un enlace de 2 hosts es un /30).

  • Para cada subred: Nombre (H hosts) → RED/p, y debajo, con dos espacios delante, máscara M · hosts útiles U y primera A · última B · difusión D. Si no cabe en lo que queda de la red, Nombre (H hosts) → no cabe: necesita un /p y quedan N direcciones y se sigue con la siguiente.

  • Antes de las subredes, Red: RED/prefijo (T direcciones). Al final, Sin asignar: N de T direcciones, desde IP con la primera dirección libre, o Sin asignar: 0 de T direcciones si no queda ninguna.

Formato de la entrada

  • Línea 1: la red, IP/prefijo. Resto: una subred por línea, nombre hosts.

Tamaño de cada prefijo
PrefijoMáscaraDireccionesHosts útiles
/30255.255.255.25242
/29255.255.255.24886
/28255.255.255.2401614
/27255.255.255.2243230
/26255.255.255.1926462
/25255.255.255.128128126
/24255.255.255.0256254

Ejemplo: Una oficina con cinco subredes

Entrada

192.168.10.0/24
Ventas 50
Administración 20
Almacén 10
Enlace router 2
Dirección 20

Salida esperada

Red: 192.168.10.0/24 (256 direcciones)
Ventas (50 hosts) → 192.168.10.0/26
  máscara 255.255.255.192 · hosts útiles 62
  primera 192.168.10.1 · última 192.168.10.62 · difusión 192.168.10.63
Administración (20 hosts) → 192.168.10.64/27
  máscara 255.255.255.224 · hosts útiles 30
  primera 192.168.10.65 · última 192.168.10.94 · difusión 192.168.10.95
Dirección (20 hosts) → 192.168.10.96/27
  máscara 255.255.255.224 · hosts útiles 30
  primera 192.168.10.97 · última 192.168.10.126 · difusión 192.168.10.127
Almacén (10 hosts) → 192.168.10.128/28
  máscara 255.255.255.240 · hosts útiles 14
  primera 192.168.10.129 · última 192.168.10.142 · difusión 192.168.10.143
Enlace router (2 hosts) → 192.168.10.144/30
  máscara 255.255.255.252 · hosts útiles 2
  primera 192.168.10.145 · última 192.168.10.146 · difusión 192.168.10.147
Sin asignar: 108 de 256 direcciones, desde 192.168.10.148

Ejemplo: Una IP que no es de red

Entrada

10.0.0.5/24
A 10

Salida esperada

10.0.0.5 no es una dirección de red (sería 10.0.0.0/24)
Código de partida (python)
import sys  def a_numero(ip):    """«192.168.10.0» → entero de 32 bits; None si no es una IPv4."""    # TODO    return None  def a_ip(n):    # TODO    return ""  def main():    lineas = sys.stdin.read().split("\n")    # TODO: valida la red, lee las subredes y asígnalas con VLSM  main()

44. Detección de ataques de fuerza bruta en el registro de SSH

Difícil · Python · 70 minutos · apuntesdam.com/ejercicios/python/deteccion-de-ataques-ssh

Cualquier servidor con SSH abierto a Internet recibe cientos de intentos de entrada al día: programas que prueban contraseñas para root, admin o test desde máquinas comprometidas. Cada intento queda en el registro de autenticación (/var/log/auth.log en Debian y Ubuntu) con una línea como Oct 3 10:15:01 web1 sshd[2231]: Failed password for root from 203.0.113.5 port 52314 ssh2.

Herramientas como fail2ban leen ese registro y bloquean en el cortafuegos las IP que fallan demasiadas veces en poco tiempo. Vas a escribir su lógica: con la configuración típica, 5 fallos dentro de una ventana de 10 minutos bloquean la IP.

Lo más peligroso no son los fallos, sino un acceso correcto desde una IP que antes ha fallado: puede ser alguien que ha acertado la contraseña. Esos casos se marcan como alertas para revisarlos.

Requisitos

  • Cada línea empieza con la fecha de syslog en sus primeros 15 caracteres (Oct 3 10:15:01, mes en inglés y sin año: se toma 2026). Las líneas vacías o con una fecha que no se puede leer se ignoran, igual que las que no son ni un fallo ni un acceso correcto.

  • Un fallo contiene Failed password for USUARIO from IP port (o for invalid user USUARIO, si el usuario no existe en el sistema). Un acceso correcto contiene Accepted password for USUARIO from IP port o Accepted publickey for ….

  • Una IP se bloquea en el instante de un fallo si, contando ese fallo, tiene al menos 5 fallos en los últimos 10 minutos (con una diferencia de 10 minutos exactos todavía cuenta). Cada IP se bloquea una sola vez, en el primer instante en que se cumple.

  • Un acceso correcto desde una IP que ya ha tenido algún fallo (en cualquier momento anterior) genera la alerta USUARIO entró desde IP a las HH:MM:SS tras N fallos desde esa IP («fallo» si es 1).

  • Informe: Intentos fallidos: F · Accesos correctos: A; Bloquear: con una línea IP a las HH:MM:SS (5 fallos desde las HH:MM:SS) por IP, en el orden en que se bloquearon, indicando el primer fallo de la ventana (o Bloquear: ninguna IP); Usuarios inexistentes probados: a, b en orden alfabético (o ninguno); si ha habido fallos, Usuarios más atacados: root (6), admin (3), … con los tres con más fallos (a igualdad, alfabético); y Alertas: con las alertas en orden (o Alertas: ninguna).

Formato de la entrada

  • Las líneas del registro de autenticación de SSH.

Ejemplo: Una mañana en el servidor web

Entrada

Oct  3 10:15:01 web1 sshd[2231]: Failed password for root from 203.0.113.5 port 52314 ssh2
Oct  3 10:15:04 web1 sshd[2231]: Failed password for invalid user admin from 203.0.113.5 port 52320 ssh2
Oct  3 10:15:09 web1 sshd[2232]: Failed password for root from 203.0.113.5 port 52331 ssh2
Oct  3 10:16:10 web1 sshd[2240]: Accepted password for ana from 198.51.100.7 port 50022 ssh2
Oct  3 10:16:12 web1 sshd[2241]: Failed password for invalid user test from 203.0.113.5 port 52340 ssh2
Oct  3 10:16:30 web1 sshd[2242]: Accepted publickey for luis from 192.0.2.10 port 40122 ssh2
Oct  3 10:17:02 web1 sshd[2243]: Failed password for root from 203.0.113.5 port 52351 ssh2
Oct  3 10:20:45 web1 sshd[2250]: Failed password for ana from 198.51.100.99 port 50100 ssh2
Oct  3 10:21:00 web1 sshd[2251]: Accepted password for ana from 198.51.100.99 port 50101 ssh2
Oct  3 10:22:10 web1 CRON[2260]: pam_unix(cron:session): session opened for user root

Salida esperada

Intentos fallidos: 6 · Accesos correctos: 3
Bloquear:
  203.0.113.5 a las 10:17:02 (5 fallos desde las 10:15:01)
Usuarios inexistentes probados: admin, test
Usuarios más atacados: root (3), admin (1), ana (1)
Alertas:
  ana entró desde 198.51.100.99 a las 10:21:00 tras 1 fallo desde esa IP

Ejemplo: La ventana de 10 minutos

Entrada

Oct  3 22:00:00 srv sshd[1]: Failed password for root from 10.0.0.9 port 1 ssh2
Oct  3 22:03:00 srv sshd[1]: Failed password for root from 10.0.0.9 port 1 ssh2
Oct  3 22:06:00 srv sshd[1]: Failed password for root from 10.0.0.9 port 1 ssh2
Oct  3 22:09:00 srv sshd[1]: Failed password for root from 10.0.0.9 port 1 ssh2
Oct  3 22:12:00 srv sshd[1]: Failed password for root from 10.0.0.9 port 1 ssh2
Oct  3 22:13:00 srv sshd[1]: Failed password for root from 10.0.0.9 port 1 ssh2
línea basura

Salida esperada

Intentos fallidos: 6 · Accesos correctos: 0
Bloquear:
  10.0.0.9 a las 22:13:00 (5 fallos desde las 22:03:00)
Usuarios inexistentes probados: ninguno
Usuarios más atacados: root (6)
Alertas: ninguna
Código de partida (python)
import reimport sysfrom datetime import datetime, timedelta FALLO = re.compile(r"Failed password for (invalid user )?(\S+) from (\S+) port")EXITO = re.compile(r"Accepted (password|publickey) for (\S+) from (\S+) port")VENTANA = timedelta(minutes=10)MAX_FALLOS = 5  def main():    # TODO: recorre el registro, detecta los ataques y escribe el informe    pass  main()

45. Informe de un registro de accesos web

Difícil · Bash · 75 minutos · apuntesdam.com/ejercicios/bash/informe-registro-accesos-web

Los servidores web como Apache y Nginx anotan cada petición en un registro de accesos (access.log). Cada línea dice quién pidió qué, cuándo y cómo respondió el servidor, en un formato estándar llamado Common Log Format.

El administrador de una web pequeña quiere un resumen diario sin instalar herramientas de análisis: un script de Bash que lea el registro por la entrada estándar (./informe.sh < access.log) y escriba las cifras importantes.

Un registro real puede tener líneas cortadas (por ejemplo, si el disco se llenó a mitad de una escritura). El script no debe contarlas como peticiones, pero sí decir cuántas había.

Requisitos

  • Cada línea tiene la forma IP - - [fecha zona] "MÉTODO ruta PROTOCOLO" código bytes. Separada por espacios, la fecha ocupa dos campos y la petición tres; el código es el 9.º campo y los bytes el 10.º. Los bytes valen - cuando la respuesta no tiene cuerpo (por ejemplo, una redirección): cuentan como 0.

  • Las líneas vacías se ignoran. Una línea no es válida si no llega al campo de los bytes o si el código no es un número de tres cifras.

  • Si no hay ninguna petición válida, escribe solo No hay peticiones válidas.

  • Escribe Peticiones: N, Visitantes distintos: N (el número de IP distintas), Respuestas: 2xx A · 3xx B · 4xx C · 5xx D (cuántas respuestas hay de cada familia, según la primera cifra del código) y Datos enviados: N KB (la suma de los bytes dividida entre 1024, sin decimales).

  • Bajo IPs con más peticiones:, las 3 IP con más peticiones (o menos, si no hay tantas), de más a menos: el número en 3 caracteres alineado a la derecha, un espacio y la IP. A igual número, por orden de texto de la IP, carácter a carácter (por eso 10.0.0.10 va antes que 10.0.0.2).

  • Página más pedida: ruta (N veces), con «vez» si es 1. Si hay empate, la primera en orden de texto.

  • Errores 404: y debajo, con dos espacios delante, cada ruta distinta que haya respondido 404, en orden de texto. Si no hay ninguna, Errores 404: ninguno en una sola línea.

  • Si ha habido líneas no válidas, termina con Líneas no válidas: N.

Formato de la entrada

  • El registro de accesos por la entrada estándar, una petición por línea en Common Log Format.

Campos de una línea del registro
CampoEjemploSignificado
1192.168.1.10IP del cliente
2 y 3- -identidad y usuario (casi siempre vacíos)
4 y 5[03/Oct/2026:10:15:32 +0200]fecha, hora y zona horaria
6, 7 y 8"GET /index.html HTTP/1.1"método, ruta y protocolo
9200código de respuesta
105120bytes enviados (- si no hay cuerpo)

Ejemplo: Diez peticiones

Entrada

192.168.1.10 - - [03/Oct/2026:10:15:32 +0200] "GET /index.html HTTP/1.1" 200 5120
192.168.1.10 - - [03/Oct/2026:10:15:33 +0200] "GET /css/estilos.css HTTP/1.1" 200 2048
10.0.0.7 - - [03/Oct/2026:10:16:01 +0200] "GET /index.html HTTP/1.1" 200 5120
10.0.0.7 - - [03/Oct/2026:10:16:02 +0200] "GET /favicon.ico HTTP/1.1" 404 512
172.16.0.3 - - [03/Oct/2026:10:17:45 +0200] "POST /login HTTP/1.1" 302 -
172.16.0.3 - - [03/Oct/2026:10:17:46 +0200] "GET /panel HTTP/1.1" 200 8192
192.168.1.10 - - [03/Oct/2026:10:18:10 +0200] "GET /admin HTTP/1.1" 404 512
10.0.0.7 - - [03/Oct/2026:10:19:00 +0200] "GET /index.html HTTP/1.1" 200 5120
192.168.1.10 - - [03/Oct/2026:10:20:15 +0200] "GET /api/datos HTTP/1.1" 500 128
8.8.4.4 - - [03/Oct/2026:10:21:30 +0200] "GET /index.html HTTP/1.1" 200 5120

Salida esperada

Peticiones: 10
Visitantes distintos: 4
Respuestas: 2xx 6 · 3xx 1 · 4xx 2 · 5xx 1
Datos enviados: 31 KB
IPs con más peticiones:
  4 192.168.1.10
  3 10.0.0.7
  2 172.16.0.3
Página más pedida: /index.html (4 veces)
Errores 404:
  /admin
  /favicon.ico

Ejemplo: Empates y líneas rotas

Entrada

10.0.0.2 - - [04/Oct/2026:09:00:00 +0200] "GET /b HTTP/1.1" 200 1024
10.0.0.10 - - [04/Oct/2026:09:00:01 +0200] "GET /a HTTP/1.1" 200 1024
línea rota
10.0.0.2 - - [04/Oct/2026:09:00:02 +0200] "GET /a HTTP/1.1" 301 -
10.0.0.10 - - [04/Oct/2026:09:00:03 +0200] "GET /b HTTP/1.1" 200 2048

10.0.0.3 - - [04/Oct/2026:09:00:04 +0200] "GET /c HTTP/1.1" 200 1024

Salida esperada

Peticiones: 5
Visitantes distintos: 3
Respuestas: 2xx 4 · 3xx 1 · 4xx 0 · 5xx 0
Datos enviados: 5 KB
IPs con más peticiones:
  2 10.0.0.10
  2 10.0.0.2
  1 10.0.0.3
Página más pedida: /a (2 veces)
Errores 404: ninguno
Líneas no válidas: 1
Código de partida (bash)
#!/bin/bash# Orden de texto byte a byte: así sort y [[ < ]] ordenan igual en cualquier máquinaexport LC_ALL=C declare -A por_ip por_ruta rutas_404total=0bytes_total=0invalidas=0c2=0 c3=0 c4=0 c5=0 while read -r ip _ _ _ _ metodo ruta _ codigo bytes _; do  [ -z "$ip" ] && continue  # TODO: valida la línea y actualiza los contadoresdone # TODO: escribe el informe

46. Alta masiva de usuarios a partir de un CSV

Difícil · Bash · 70 minutos · apuntesdam.com/ejercicios/bash/alta-masiva-de-usuarios

Al empezar el curso, el administrador del instituto tiene que crear las cuentas de los alumnos nuevos en el servidor Linux. Secretaría le pasa un CSV con nombre, apellidos y grupo, y hacerlo a mano con useradd uno por uno no es una opción.

El login sigue la norma del centro: la inicial del nombre, el primer apellido y la inicial del segundo, en minúsculas y sin tildes ni eñes (Lucía Martín Ruiz → lmartinr). Si ya existe, se le añade un número (lmartinr2, lmartinr3…). Hay que tener en cuenta tanto las cuentas que ya hay en el sistema como las que se van creando en el propio fichero.

Un script que crea usuarios es peligroso de probar, así que primero se ejecuta en modo simulación: en lugar de crear nada, escribe las órdenes que ejecutaría. Así se pueden revisar antes de lanzarlas de verdad.

Requisitos

  • La primera línea es GRUPOS grupo1,grupo2,… (si no, escribe La primera línea debe ser GRUPOS grupo1,grupo2… y termina). La segunda es EXISTEN cuenta1,cuenta2,… con las cuentas que ya hay en el sistema, que puede estar vacía tras la palabra (si no es esa línea, Falta la línea EXISTEN y termina).

  • Cada línea siguiente es nombre;apellido1;apellido2;grupo (el segundo apellido puede estar vacío). Las líneas vacías se ignoran. Si falta el nombre, el primer apellido o el grupo, o hay más campos, escribe # Línea N: faltan o sobran campos; si el grupo no está en la lista, # Línea N: grupo desconocido G (N es el número de línea de la entrada).

  • Login: inicial del nombre + primer apellido + inicial del segundo apellido, sin tildes (á é í ó ú ü), con la ñ como n, en minúsculas y quitando todo lo que no sea una letra de la a a la z. Si coincide con una cuenta existente o ya creada, se prueba añadiendo 2, 3… hasta encontrar uno libre.

  • Por cada alta escribe useradd -m -s /bin/bash -g grupo -c "Nombre Apellido1 Apellido2" login (el comentario con el nombre completo tal como viene, sin segundo apellido si está vacío) y chage -d 0 login (para obligar a cambiar la contraseña en el primer inicio de sesión).

  • Al final, # Altas: N (grupo1 n1, grupo2 n2, …) con todos los grupos en el orden de la línea GRUPOS.

Formato de la entrada

  • Línea 1: GRUPOS lista. Línea 2: EXISTEN lista. Resto: nombre;apellido1;apellido2;grupo.

Ejemplo: Alumnos y profesores nuevos

Entrada

GRUPOS alumnos,profesores,administracion
EXISTEN root,ana,lmartinr
Lucía;Martín;Ruiz;alumnos
Luis;Martín;Rodríguez;alumnos
Íñigo;Núñez;Ibáñez;profesores
María José;O'Donnell;;alumnos
Pedro;Gil;López;conserjes
Sofía;Ruiz

Salida esperada

useradd -m -s /bin/bash -g alumnos -c "Lucía Martín Ruiz" lmartinr2
chage -d 0 lmartinr2
useradd -m -s /bin/bash -g alumnos -c "Luis Martín Rodríguez" lmartinr3
chage -d 0 lmartinr3
useradd -m -s /bin/bash -g profesores -c "Íñigo Núñez Ibáñez" inunezi
chage -d 0 inunezi
useradd -m -s /bin/bash -g alumnos -c "María José O'Donnell" modonnell
chage -d 0 modonnell
# Línea 7: grupo desconocido conserjes
# Línea 8: faltan o sobran campos
# Altas: 4 (alumnos 3, profesores 1, administracion 0)

Ejemplo: Logins repetidos dentro del fichero

Entrada

GRUPOS aula1
EXISTEN
Ana;García;Pérez;aula1
Antonio;García;Pons;aula1
Alba;García;Puig;aula1

Ana;García;Pérez;aula1

Salida esperada

useradd -m -s /bin/bash -g aula1 -c "Ana García Pérez" agarciap
chage -d 0 agarciap
useradd -m -s /bin/bash -g aula1 -c "Antonio García Pons" agarciap2
chage -d 0 agarciap2
useradd -m -s /bin/bash -g aula1 -c "Alba García Puig" agarciap3
chage -d 0 agarciap3
useradd -m -s /bin/bash -g aula1 -c "Ana García Pérez" agarciap4
chage -d 0 agarciap4
# Altas: 4 (aula1 4)
Código de partida (bash)
#!/bin/bash# Genera las órdenes para dar de alta usuarios a partir de un CSV, sin ejecutarlas (modo simulación)export LC_ALL=C # «Lucía Martín Ruiz» → «lmartinr»: sin tildes ni eñes, en minúsculas y solo letrasnormalizar() {  local s=$1  # TODO  echo "$s"} read -r palabra lista_grupos# TODO: comprueba la línea GRUPOS, lee la línea EXISTEN y procesa cada usuario

47. Rotación de copias de seguridad abuelo-padre-hijo

Difícil · Bash · 70 minutos · apuntesdam.com/ejercicios/bash/rotacion-de-copias-abuelo-padre-hijo

Un servidor hace cada noche una copia completa en un fichero backup-AAAA-MM-DD.tar.gz. Si no se borra ninguna, el disco se llena; si se guardan solo las últimas, no hay forma de volver a cómo estaba todo hace dos meses. La solución clásica es la rotación abuelo-padre-hijo (GFS): copias diarias (hijos) para la última semana, semanales (padres) para el último mes y mensuales (abuelos) para el último medio año.

En este servidor la política es: conservar las 7 copias más recientes; además, las hechas en domingo de los últimos 28 días; y además, las hechas el día 1 de mes de los últimos 6 meses (contando el actual). Una copia puede cumplir varias reglas a la vez.

El script recibe la fecha de hoy y el listado de ficheros (lo que daría ls) y escribe qué se conserva y por qué, y qué se borra. Así se puede revisar antes de borrar nada.

Requisitos

  • La primera línea es HOY AAAA-MM-DD con una fecha que exista (si no, La primera línea debe ser HOY AAAA-MM-DD y termina). Cada línea siguiente es el nombre de un fichero; las vacías se ignoran.

  • Un nombre que no sea exactamente backup-AAAA-MM-DD.tar.gz con una fecha que exista escribe Ignorado (nombre no válido): nombre; una copia con fecha posterior a hoy, Ignorado (fecha futura): nombre. Los avisos salen en el orden de la entrada. Si no queda ninguna copia válida, No hay copias.

  • Las copias válidas se tratan de la más reciente a la más antigua. Motivos para conservar una copia: diaria si está entre las 7 más recientes; semanal si es de un domingo y han pasado menos de 28 días desde ella hasta hoy; mensual si es del día 1 y es del mes de hoy o de uno de los 5 anteriores.

  • Para cada copia escribe CONSERVAR nombre (motivos) con los motivos separados por coma y espacio en el orden diaria, semanal, mensual, o BORRAR nombre (BORRAR seguido de cuatro espacios, para que los nombres queden alineados).

  • Al final, Se conservan C y se borran B.

Formato de la entrada

  • Línea 1: HOY AAAA-MM-DD. Resto: un nombre de fichero por línea.

Ejemplo: Seis meses de copias

Entrada

HOY 2026-10-04
backup-2026-10-03.tar.gz
backup-2026-10-02.tar.gz
backup-2026-10-01.tar.gz
backup-2026-09-30.tar.gz
backup-2026-09-29.tar.gz
backup-2026-09-28.tar.gz
backup-2026-09-27.tar.gz
backup-2026-09-26.tar.gz
backup-2026-09-25.tar.gz
backup-2026-09-20.tar.gz
backup-2026-09-13.tar.gz
backup-2026-09-06.tar.gz
backup-2026-09-01.tar.gz
backup-2026-08-30.tar.gz
backup-2026-08-01.tar.gz
backup-2026-07-01.tar.gz
backup-2026-06-01.tar.gz
backup-2026-05-01.tar.gz
backup-2026-04-01.tar.gz

Salida esperada

CONSERVAR backup-2026-10-03.tar.gz (diaria)
CONSERVAR backup-2026-10-02.tar.gz (diaria)
CONSERVAR backup-2026-10-01.tar.gz (diaria, mensual)
CONSERVAR backup-2026-09-30.tar.gz (diaria)
CONSERVAR backup-2026-09-29.tar.gz (diaria)
CONSERVAR backup-2026-09-28.tar.gz (diaria)
CONSERVAR backup-2026-09-27.tar.gz (diaria, semanal)
BORRAR    backup-2026-09-26.tar.gz
BORRAR    backup-2026-09-25.tar.gz
CONSERVAR backup-2026-09-20.tar.gz (semanal)
CONSERVAR backup-2026-09-13.tar.gz (semanal)
BORRAR    backup-2026-09-06.tar.gz
CONSERVAR backup-2026-09-01.tar.gz (mensual)
BORRAR    backup-2026-08-30.tar.gz
CONSERVAR backup-2026-08-01.tar.gz (mensual)
CONSERVAR backup-2026-07-01.tar.gz (mensual)
CONSERVAR backup-2026-06-01.tar.gz (mensual)
CONSERVAR backup-2026-05-01.tar.gz (mensual)
BORRAR    backup-2026-04-01.tar.gz
Se conservan 14 y se borran 5

Ejemplo: Nombres no válidos y fechas futuras

Entrada

HOY 2026-03-01
backup-2026-02-29.tar.gz
backup-2026-02-28.tar.gz
copia-2026-02-27.tar.gz
backup-2026-03-02.tar.gz
backup-2026-03-01.tar.gz
backup-2025-11-01.tar.gz
backup-2025-09-01.tar.gz

Salida esperada

Ignorado (nombre no válido): backup-2026-02-29.tar.gz
Ignorado (nombre no válido): copia-2026-02-27.tar.gz
Ignorado (fecha futura): backup-2026-03-02.tar.gz
CONSERVAR backup-2026-03-01.tar.gz (diaria, semanal, mensual)
CONSERVAR backup-2026-02-28.tar.gz (diaria)
CONSERVAR backup-2025-11-01.tar.gz (diaria, mensual)
CONSERVAR backup-2025-09-01.tar.gz (diaria)
Se conservan 4 y se borran 0
Código de partida (bash)
#!/bin/bash# Política abuelo-padre-hijo: 7 diarias, domingos de las últimas 4 semanas y el día 1 de los últimos 6 mesesexport LC_ALL=C read -r palabra hoy# TODO: valida la fecha de hoy, lee los nombres de las copias y decide cuáles se conservan

48. Monitor de espacio en disco con umbrales

Medio · Bash · 50 minutos · apuntesdam.com/ejercicios/bash/monitor-de-espacio-en-disco

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.

Requisitos

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

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

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

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

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

Formato de la entrada

  • Línea 1: UMBRALES aviso crítico. Después, la salida completa de df -P (con su cabecera).

Ejemplo: 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 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

Ejemplo: Umbrales no válidos

Entrada

UMBRALES 95 90
Filesystem 1024-blocks Used Available Capacity Mounted on

Salida esperada

La primera línea debe ser UMBRALES aviso crítico (aviso < crítico <= 100)
Código de partida (bash)
#!/bin/bash# Lee la salida de «df -P» y avisa de los sistemas de ficheros que se llenanexport LC_ALL=C read -r palabra aviso critico# TODO: valida los umbrales, salta la cabecera y analiza cada línea de df

49. Analizador de la configuración de Nginx

Muy difícil · Bash · 90 minutos · apuntesdam.com/ejercicios/bash/analizador-de-configuracion-nginx

En un servidor con varias webs, la configuración de Nginx crece con el tiempo: un server para el dominio, otro para la API, otro que nadie recuerda… y los errores aparecen: un sitio que escucha en el 443 sin certificado (Nginx no arranca), un autoindex on olvidado que enseña los ficheros a cualquiera, o un dominio con HTTPS cuyo puerto 80 sigue sirviendo la web sin cifrar.

nginx -t comprueba la sintaxis, pero no esas buenas prácticas. Vas a escribir un script que lea un fichero de sitios (el que estaría en /etc/nginx/sites-available/), resuma cada bloque server y avise de los problemas.

El formato de Nginx es sencillo de leer línea a línea: directivas terminadas en ; y bloques entre llaves (server { … }, y dentro bloques location … { … }). Para saber dónde termina cada server hay que llevar la cuenta de las llaves abiertas.

Requisitos

  • Se ignoran los comentarios (desde # hasta el final de la línea), los espacios de los extremos y las líneas vacías. Cada línea es server {, otra apertura de bloque (termina en {), un cierre } o una directiva terminada en ;. Las líneas se numeran desde 1 contando todas.

  • Errores, que detienen el script: Error: llave de cierre sin abrir en la línea N, Error: bloque fuera de un server en la línea N, Error: directiva fuera de un server en la línea N y, al final, Error: llaves sin cerrar. Si no hay ningún server, No hay ningún bloque server.

  • De cada server se guardan (también desde dentro de sus location): los puertos de listen (el número tras los dos puntos, si los hay, y ssl si la línea lo lleva; sin repetir; 80 si no hay ningún listen), los nombres de server_name, el primer root, el primer proxy_pass, la URL del último return (lo que va tras el código), si tiene ssl_certificate, si tiene ssl_certificate_key y si tiene autoindex on.

  • Por cada servidor: Servidor N (líneas A-B): puerto P · nombres · acción, con los puertos separados por y (443 ssl y 80), los nombres tal cual o (sin nombre), y como acción redirige a URL, proxy a URL, sirve RUTA o no sirve nada, por ese orden de preferencia.

  • Avisos, primero los de cada servidor en orden (Servidor N: usa ssl pero le falta ssl_certificate y ssl_certificate_key —o solo el que falte—, Servidor N: autoindex activado (muestra la lista de ficheros), Servidor N: no tiene root, return ni proxy_pass) y después, por orden alfabético, dominio: aparece en K servidores del puerto P (para cada dominio y puerto con más de un servidor) y dominio: el puerto 80 no redirige a HTTPS (para cada dominio que se sirve en el 443 sin un servidor en el 80 cuyo return vaya a https://).

  • Escribe Avisos: y cada aviso con - delante, o Sin avisos.

Formato de la entrada

  • Un fichero de configuración de sitios de Nginx.

Ejemplo: Un dominio con API

Entrada

# Sitio principal
server {
    listen 80;
    listen [::]:80;
    server_name ejemplo.com www.ejemplo.com;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    listen [::]:443 ssl;
    server_name ejemplo.com www.ejemplo.com;
    ssl_certificate /etc/letsencrypt/live/ejemplo.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/ejemplo.com/privkey.pem;
    root /var/www/ejemplo;

    location /descargas/ {
        autoindex on;   # se dejó activado tras una prueba
    }
}

server {
    listen 443 ssl;
    server_name api.ejemplo.com;
    ssl_certificate /etc/ssl/api.crt;
    location / {
        proxy_pass http://127.0.0.1:3000;
    }
}

Salida esperada

Servidor 1 (líneas 2-7): puerto 80 · ejemplo.com www.ejemplo.com · redirige a https://$host$request_uri
Servidor 2 (líneas 9-20): puerto 443 ssl · ejemplo.com www.ejemplo.com · sirve /var/www/ejemplo
Servidor 3 (líneas 22-29): puerto 443 ssl · api.ejemplo.com · proxy a http://127.0.0.1:3000
Avisos:
  - Servidor 2: autoindex activado (muestra la lista de ficheros)
  - Servidor 3: usa ssl pero le falta ssl_certificate_key
  - api.ejemplo.com: el puerto 80 no redirige a HTTPS

Ejemplo: Servidores repetidos y uno vacío

Entrada

server {
    server_name intranet.local;
}
server {
    listen 8080;
    server_name intranet.local;
    root /srv/intranet;
}
server {
    listen 8080;
    server_name intranet.local;
    return 302 /mantenimiento.html;
}

Salida esperada

Servidor 1 (líneas 1-3): puerto 80 · intranet.local · no sirve nada
Servidor 2 (líneas 4-8): puerto 8080 · intranet.local · sirve /srv/intranet
Servidor 3 (líneas 9-13): puerto 8080 · intranet.local · redirige a /mantenimiento.html
Avisos:
  - Servidor 1: no tiene root, return ni proxy_pass
  - intranet.local: aparece en 2 servidores del puerto 8080
Código de partida (bash)
#!/bin/bash# Revisa un fichero de sitios de Nginx (como /etc/nginx/sites-available/web) y avisa de los fallos típicosexport LC_ALL=C n=0 profundidad=0 numero=0declare -a inicio fin puertos nombres raiz retorno proxy cert clave autoindex while IFS= read -r linea || [ -n "$linea" ]; do  numero=$((numero + 1))  # TODO: quita comentarios y espacios, sigue las llaves y guarda las directivas de cada serverdone # TODO: resumen de cada servidor y avisos