Saltar al contenido
sudoquest@root
Linux

Scripts y tareas programadas

Scripts de bash (variables, if, for y while), copias con tar y zip, borrar lo viejo con find, instalar con apt y que cron lo haga todo cada noche.

35 min de lectura

Lo que haces a mano todos los días se te acaba olvidando algún día. Por eso, en un servidor, lo que se repite se escribe una vez en un script y se programa para que se haga solo. Así se hacen las copias de seguridad, la limpieza de lo viejo y casi todo el mantenimiento.

En este tema aprendes a escribir scripts de bash (con variables, argumentos, decisiones y bucles), a hacer copias con tar, a borrar las viejas con find, a instalar lo que falta con apt y a dejarlo todo programado con cron. Los ejemplos son de srv-obrador, el servidor de un obrador de pan: cada día guarda los pedidos en /srv/pedidos y sus copias en /srv/copias.

Tu primer script

Un script es un archivo de texto con órdenes, una por línea, que se ejecutan en orden. Aquí no hay editor, así que se escribe con cat y un here-doc: todo lo que escribas hasta la línea EOF va al archivo. Mientras falte el EOF, la terminal te pide más con >:

tecnico@srv-obrador:~$ cat > pedidos.sh <<'EOF'
> #!/bin/bash
> # Los pedidos de hoy
> echo "Pedidos en /srv/pedidos:"
> ls /srv/pedidos
> EOF

La primera línea, #!/bin/bash (el shebang), dice con qué programa se ejecuta el archivo. Las demás que empiezan por # son comentarios. Las comillas de 'EOF' hacen que lo de dentro se guarde tal cual, sin sustituir nada: es lo que quieres casi siempre en un script.

Un archivo nuevo no se puede ejecutar: le falta el permiso.

tecnico@srv-obrador:~$ ./pedidos.sh
bash: ./pedidos.sh: Permission denied
tecnico@srv-obrador:~$ chmod +x pedidos.sh
tecnico@srv-obrador:~$ ./pedidos.sh
Pedidos en /srv/pedidos:
pedido-0410.txt  pedido-0416.txt  pedido-0424.txt  pedido-0428.txt
pedido-0413.txt  pedido-0418.txt  pedido-0425.txt

El ./ delante dice “el de aquí”: los programas sin ruta se buscan en las carpetas del PATH (/usr/bin, /usr/local/bin…), y tu carpeta no es una de ellas. Los scripts de todo el servidor se suelen dejar en /usr/local/bin.

Ejercicio

Tu primer script

En el obrador repiten cada mañana las mismas órdenes para ver los pedidos. Guárdalas en un script: un archivo con órdenes que se ejecuta de una vez.

Preparando la máquina…

Variables y $(…)

Una variable se crea con NOMBRE=valor, sin espacios alrededor del =, y se usa con $ delante. Con espacios, bash cree que FECHA es una orden:

tecnico@srv-obrador:~$ FECHA = hoy
FECHA: command not found

$(…) ejecuta una orden y se sustituye por lo que escribe. Así se guarda la fecha, o la cuenta de algo:

tecnico@srv-obrador:~$ HOY=$(date +%F)
tecnico@srv-obrador:~$ echo "Hoy es $HOY"
Hoy es 2026-11-10
tecnico@srv-obrador:~$ N=$(ls /srv/pedidos | wc -l)
tecnico@srv-obrador:~$ echo "Hay $N pedidos"
Hay 7 pedidos

Entre comillas dobles, $N se sustituye; entre comillas simples, no (echo 'Hay $N pedidos' escribe $N tal cual). date +%F es la fecha como AAAA-MM-DD: ordenada, sirve para poner nombre a las copias.

Un script recibe argumentos: $1 es el primero, $2 el segundo, $# cuántos hay y "$@" todos:

tecnico@srv-obrador:~$ cat > hola.sh <<'EOF'
> #!/bin/bash
> echo "Hola, $1. Me has dado $# argumentos."
> EOF
tecnico@srv-obrador:~$ bash hola.sh Marta Luis
Hola, Marta. Me has dado 2 argumentos.

Con bash hola.sh no hace falta el permiso de ejecución: el que se ejecuta es bash, que lee el archivo.

Ejercicio

Variables y $(…)

Un script necesita datos que cambian: la fecha de hoy, cuántos pedidos hay, lo que le pasas al lanzarlo. Guárdalos en variables.

Preparando la máquina…

Decidir: if y [ ]

Toda orden acaba con un código de salida: 0 si fue bien, otro número si no. Está en $? justo después. La orden [ … ] (también llamada test) no escribe nada: solo comprueba algo y acaba con 0 (verdad) o 1 (mentira):

tecnico@srv-obrador:~$ [ -d /srv/archivo ]; echo $?
1

-d es “existe y es un directorio”; -f, un archivo normal; -e, que existe. Los números se comparan con -eq, -ne, -gt (mayor), -lt (menor), -ge y -le; los textos, con = y !=. Los espacios dentro de los corchetes son obligatorios.

if ejecuta un bloque según el código de salida de la condición. ! le da la vuelta:

tecnico@srv-obrador:~$ if [ ! -d /srv/archivo ]; then sudo mkdir /srv/archivo; echo "creada"; fi
creada
tecnico@srv-obrador:~$ if [ ! -d /srv/archivo ]; then sudo mkdir /srv/archivo; echo "creada"; else echo "ya estaba"; fi
ya estaba
tecnico@srv-obrador:~$ if [ $N -gt 5 ]; then echo "día fuerte"; else echo "día tranquilo"; fi
día fuerte

En un script, cada parte suele ir en su línea (if …, then, las órdenes, fi); en la terminal, en una sola, separadas por ;.

Ejercicio

Decidir: if y test

Un script tiene que comprobar antes de actuar: ¿existe la carpeta?, ¿hay pedidos? Con if y [ ] decide qué hacer.

Preparando la máquina…

Repetir: for y while

for da una vuelta por cada elemento de una lista, con él en la variable. Con un comodín, la lista son los archivos:

tecnico@srv-obrador:~$ for f in /srv/pedidos/*.txt; do echo "$f"; done
/srv/pedidos/pedido-0410.txt
/srv/pedidos/pedido-0413.txt
/srv/pedidos/pedido-0416.txt
/srv/pedidos/pedido-0418.txt
/srv/pedidos/pedido-0424.txt
/srv/pedidos/pedido-0425.txt
/srv/pedidos/pedido-0428.txt

Si pulsas Intro antes del done, la terminal te pide el resto con >, como en los here-docs:

tecnico@srv-obrador:~$ for f in /srv/pedidos/*.txt; do
> echo "== $f"
> head -1 "$f"
> done
== /srv/pedidos/pedido-0410.txt
cliente: elena
== /srv/pedidos/pedido-0413.txt
cliente: elena
== /srv/pedidos/pedido-0416.txt
cliente: pablo
…

"$f" va entre comillas por si un nombre tiene espacios. while repite mientras la condición sea verdad; $((…)) hace cuentas:

tecnico@srv-obrador:~$ i=1; while [ $i -le 5 ]; do echo $i; i=$((i+1)); done
1
2
3
4
5

Ejercicio

Repetir: for y while

Lo mismo para cada pedido: con un for se hace una vez por archivo, sin escribirlo a mano.

Preparando la máquina…

Copias: tar y find

tar junta una carpeta entera en un solo archivo: -c crea, -z comprime (gzip) y -f dice el nombre. Con la fecha en el nombre, cada día tiene su copia:

tecnico@srv-obrador:~$ sudo tar -czf /srv/copias/pedidos-$(date +%F).tar.gz /srv/pedidos
tar: Removing leading `/' from member names

El aviso es normal: dentro de la copia, las rutas se guardan sin la / del principio, para que al sacarlas no pisen nada. -t enseña lo que hay dentro sin sacarlo (y -x, en otra carpeta, lo saca):

tecnico@srv-obrador:~$ tar -tzf /srv/copias/pedidos-2026-11-10.tar.gz
srv/pedidos/
srv/pedidos/pedido-0410.txt
srv/pedidos/pedido-0413.txt
srv/pedidos/pedido-0416.txt
…

Para eso están las copias: si un archivo se pierde o se estropea, se devuelve de una de antes. sudo tar -xzf /srv/copias/pedidos-2026-11-10.tar.gz -C / srv/pedidos/pedido-0413.txt saca solo ese (nombrado tal cual lo enseña -t) y, con -C /, a su sitio de siempre; sin nombrar ninguno, los sacaría todos y pisaría los de ahora.

Una copia al día llena el disco. Las viejas se borran con find y -mtime, que cuenta días completos desde el último cambio: +7 es “más de 7”.

tecnico@srv-obrador:~$ ls -l /srv/copias
total 36
-rw-r--r-- 1 root root 3251 Oct 18 11:20 pedidos-2026-10-18.tar.gz
-rw-r--r-- 1 root root 2992 Oct 25 11:20 pedidos-2026-10-25.tar.gz
-rw-r--r-- 1 root root 2844 Oct 29 11:20 pedidos-2026-10-29.tar.gz
-rw-r--r-- 1 root root 2733 Nov  1 11:20 pedidos-2026-11-01.tar.gz
-rw-r--r-- 1 root root 2585 Nov  5 11:20 pedidos-2026-11-05.tar.gz
-rw-r--r-- 1 root root 2511 Nov  7 11:20 pedidos-2026-11-07.tar.gz
-rw-r--r-- 1 root root 2474 Nov  8 11:20 pedidos-2026-11-08.tar.gz
-rw-r--r-- 1 root root 2437 Nov  9 11:20 pedidos-2026-11-09.tar.gz
-rw-r--r-- 1 root root  499 Nov 10 11:20 pedidos-2026-11-10.tar.gz
tecnico@srv-obrador:~$ find /srv/copias -name '*.tar.gz' -mtime +7
/srv/copias/pedidos-2026-11-01.tar.gz
/srv/copias/pedidos-2026-10-29.tar.gz
/srv/copias/pedidos-2026-10-25.tar.gz
/srv/copias/pedidos-2026-10-18.tar.gz

Ejercicio

Copias: tar y find -mtime

La copia de los pedidos va a /srv/copias, con la fecha en el nombre. Y como se acumulan, las de más de una semana sobran.

Preparando la máquina…

Para quien usa Windows: zip y unzip

Un .tar.gz es lo normal entre servidores, pero en Windows no se abre con doble clic. Para mandar algo a una gestoría, o a cualquiera con Windows, se usa zip: -r mete las carpetas con todo lo que llevan dentro (sin él, solo la carpeta, vacía).

tecnico@srv-obrador:~$ zip -r ~/pedidos.zip /srv/pedidos
  adding: srv/pedidos/ (stored 0%)
adding: srv/pedidos/pedido-0410.txt (stored 0%)
adding: srv/pedidos/pedido-0413.txt (stored 0%)
adding: srv/pedidos/pedido-0418.txt (deflated 5%)
…

Como tar, guarda las rutas sin la / del principio. deflated es cuánto ha comprimido; stored, que lo ha guardado tal cual (en archivos tan pequeños, comprimir no ahorra nada). unzip -l enseña lo que hay dentro sin sacarlo, y unzip -d lo saca en otra carpeta:

tecnico@srv-obrador:~$ unzip -l ~/pedidos.zip
Archive:  /home/tecnico/pedidos.zip
Length      Date    Time    Name
---------  ---------- -----   ----
      0  2026-11-10 10:20   srv/pedidos/
     40  2026-11-10 10:13   srv/pedidos/pedido-0410.txt
     39  2026-11-10 08:53   srv/pedidos/pedido-0413.txt
…
---------                     -------
    279                     8 files
tecnico@srv-obrador:~$ unzip ~/pedidos.zip -d /tmp/revision
Archive:  /home/tecnico/pedidos.zip
 creating: /tmp/revision/srv/pedidos/
extracting: /tmp/revision/srv/pedidos/pedido-0410.txt
extracting: /tmp/revision/srv/pedidos/pedido-0413.txt
inflating: /tmp/revision/srv/pedidos/pedido-0418.txt
…

zip y unzip tampoco vienen de serie: se instalan con apt, que viene ahora.

Ejercicio

Para la gestoría: zip y unzip

La gestoría lleva las cuentas en Windows y pide los pedidos de hoy en un .zip, que abre sin instalar nada. Prepáralo y compruébalo antes de mandarlo.

Preparando la máquina…

Instalar lo que falta: apt

Si un script usa un programa que no está, falla con command not found (código 127). Los programas se instalan con apt, desde los repositorios de Ubuntu, y hace falta ser root:

tecnico@srv-obrador:~$ apt install sqlite3
E: Could not open lock file /var/lib/dpkg/lock-frontend - open (13: Permission denied)
E: Unable to acquire the dpkg frontend lock (/var/lib/dpkg/lock-frontend), are you root?
tecnico@srv-obrador:~$ sudo apt install sqlite3
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
Suggested packages:
sqlite3-doc
The following NEW packages will be installed:
sqlite3
0 upgraded, 1 newly installed, 0 to remove and 0 not upgraded.
…
Setting up sqlite3 (3.37.2-2ubuntu0.7) ...

Si apt va a instalar más de lo que pediste, pregunta [Y/n] antes; con -y contesta que sí. apt list --installed dice lo que hay instalado, y apt show qué es un paquete.

Que se haga solo: cron

cron es el servicio que ejecuta tareas a su hora. Cada línea son cinco campos de tiempo y la orden:

┌───────── minuto (0-59)
│ ┌─────── hora (0-23)
│ │ ┌───── día del mes (1-31)
│ │ │ ┌─── mes (1-12)
│ │ │ │ ┌─ día de la semana (0-7; 0 y 7 son el domingo)
│ │ │ │ │
30 3 * * * /home/tecnico/pedidos.sh > /tmp/pedidos.txt

* es “cualquiera”: esa línea es todos los días a las 3:30. */15 en el minuto sería cada cuarto de hora; 1-5 en el día de la semana, de lunes a viernes. Cada usuario tiene su lista, que se ve con crontab -l y se cambia con crontab (aquí, con un here-doc; en un servidor de verdad, crontab -e abre un editor):

tecnico@srv-obrador:~$ crontab <<'EOF'
> 30 3 * * * /home/tecnico/pedidos.sh > /tmp/pedidos.txt
> EOF
tecnico@srv-obrador:~$ crontab -l
30 3 * * * /home/tecnico/pedidos.sh > /tmp/pedidos.txt

Las tareas del sistema van en /etc/crontab o, mejor, en un archivo propio dentro de /etc/cron.d. Ahí cada línea lleva el usuario entre la hora y la orden:

0 3 * * * root /usr/local/bin/copia-pedidos.sh

Cron lo apunta todo en su diario. Al día siguiente se ve que la tarea se lanzó, y con qué usuario:

tecnico@srv-obrador:~$ journalctl -u cron --no-pager | grep tecnico
Nov 11 03:30:00 srv-obrador CRON[417]: pam_unix(cron:session): session opened for user tecnico(uid=1000) by (uid=0)
Nov 11 03:30:00 srv-obrador CRON[417]: (tecnico) CMD (/home/tecnico/pedidos.sh > /tmp/pedidos.txt)
Nov 11 03:30:00 srv-obrador CRON[417]: pam_unix(cron:session): session closed for user tecnico

Si una línea está mal (un campo que no existe, o falta el usuario en /etc/cron.d), cron no la ejecuta nunca, y el diario dice por qué: Error: bad hour, Error: bad username… Mira siempre el diario después de tocar cron.

Ejercicio

Programar: cron

La copia tiene que hacerse sola, cada noche, aunque nadie esté conectado. Eso es cron: tu crontab dice qué se ejecuta y cuándo.

Preparando la máquina…