← Automatización con Scripts en Bash Bloque 8 · Tareas programadas · Tema 34 de 42

Tema 34

El entorno de cron y sus errores típicos

«En la terminal funciona, pero en cron no hace nada.» Es probablemente la queja más repetida sobre cron, y casi nunca es culpa de cron. Una tarea programada corre en un mundo mucho más pobre que la terminal: sin los programas de ~/bin, sin la configuración de ~/.bashrc, sin idioma, sin pantalla y sin nadie que lea sus mensajes de error. Este tema muestra exactamente qué recibe una tarea, cómo reproducir ese entorno para encontrar el problema antes que cron, cómo guardar lo que la tarea dice y cómo averiguar qué pasó cuando algo falla.

1. «En la terminal funciona»

Cuando abres una terminal, Bash lee varios archivos de configuración, como ~/.profile y ~/.bashrc, que agregan directorios al PATH, definen variables, alias y funciones, y fijan el idioma. Todo lo que ejecutas después hereda ese entorno, y uno se acostumbra a él sin notarlo. Una tarea de cron no tiene nada de eso: la lanza un servicio del sistema, sin sesión, sin terminal y sin leer ningún archivo de configuración del usuario.

Comparación entre la terminal y una tarea de cron. En la terminal: PATH completo con ~/bin y /usr/local/bin; se leen ~/.profile y ~/.bashrc; hay una terminal y la variable TERM; el idioma está configurado; existen el agente de SSH y la sesión gráfica; la salida se ve en la pantalla. En cron: PATH=/usr/bin:/bin; no se lee ningún archivo de configuración; no hay terminal; el idioma suele ser C, en inglés; no hay agente de SSH ni sesión gráfica; la salida va por correo o se pierde En tu terminal En una tarea de cron PATH con ~/bin y /usr/local/bin Se leen ~/.profile y ~/.bashrc Hay una terminal: TERM, colores Idioma configurado: es_AR.UTF-8 Agente de SSH, sesión gráfica La salida se ve en la pantalla PATH=/usr/bin:/bin No se lee ninguna configuración Sin terminal ni entrada Idioma C, en inglés Sin agente de SSH ni pantalla La salida va por correo, o se pierde
El mismo comando, en dos mundos distintos. Casi todos los errores de este tema salen de la columna derecha.

Por eso la pregunta útil no es «¿por qué cron no funciona?», sino «¿de qué cosa de mi terminal depende este script sin que yo lo sepa?». Las secciones que siguen responden esa pregunta, una diferencia por vez.

2. El entorno de una tarea

La forma más directa de conocer el entorno de cron es pedírselo. Una tarea de prueba que guarda la salida de env en un archivo lo muestra tal cual:

terminal · lo que ve una tarea
$ (crontab -l; echo '* * * * * env > /tmp/env-cron.txt') | crontab -
# … un minuto después:
$ cat /tmp/env-cron.txt
HOME=/home/ana
LOGNAME=ana
PATH=/usr/bin:/bin
SHELL=/bin/sh
PWD=/home/ana
$ env | wc -l
47
$ crontab -l | grep -v env-cron | crontab -           # quitar la tarea de prueba

Cinco variables contra cuarenta y siete. Según el sistema pueden aparecer algunas más, como LANG en los Debian que la definen en /etc/default/locale, o las que se hayan escrito al comienzo del crontab, como vimos en el tema 33. Pero nunca estará lo que agregan los archivos de configuración de tu sesión.

Comparar las dos listas muestra de un vistazo qué le falta a la tarea. comm recibe dos listas ordenadas y, con -13, muestra solo las líneas que están en la segunda y no en la primera:

terminal · las variables que solo tiene la terminal
$ comm -13 <(cut -d= -f1 /tmp/env-cron.txt | sort) <(env | cut -d= -f1 | sort)
DBUS_SESSION_BUS_ADDRESS
DISPLAY
EDITOR
LANG
SSH_AUTH_SOCK
TERM
USER
XDG_RUNTIME_DIR
…

3. Reproducir ese entorno

Esperar un minuto a que cron ejecute una prueba, mirar el resultado, corregir y volver a esperar es lento. Mucho más cómodo es ejecutar el comando en la terminal pero con un entorno como el de cron. Eso hace env -i: arranca un programa con el entorno vacío, salvo las variables que se le indiquen:

terminal · el mismo comando, con el entorno de cron
$ respaldar -q
Respaldo completo: 1284 archivos, 412 MiB
$ cd ~ && env -i HOME="$HOME" LOGNAME="$USER" PATH=/usr/bin:/bin SHELL=/bin/sh /bin/sh -c 'respaldar -q'
/bin/sh: 1: respaldar: not found

Ahí está el error que en cron pasaba inadvertido. La línea reproduce las tres cosas que importan: el entorno casi vacío, la shell /bin/sh en lugar de Bash, y el directorio personal como directorio de trabajo. Lo que va entre comillas simples es exactamente lo que dice el crontab después de los campos de tiempo, salvo que en el crontab los % llevan barra y aquí no.

Como es largo de escribir, conviene guardarlo como función en ~/.bashrc, el único lugar donde eso tiene sentido, porque se usa en la terminal:

~/.bashrc
# comocron 'comando' — ejecuta un comando con un entorno parecido al de cron
comocron() {
  (cd ~ && env -i HOME="$HOME" LOGNAME="$USER" PATH=/usr/bin:/bin SHELL=/bin/sh /bin/sh -c "$1")
}

Los paréntesis ejecutan el cd en una subshell, para no cambiar el directorio de la terminal. El parecido no es total: cron no tiene terminal, y aquí la salida sigue viéndose en la pantalla. Pero alcanza para descubrir la gran mayoría de los problemas.

4. El PATH y las rutas absolutas

El problema más frecuente es el del ejemplo anterior: con PATH=/usr/bin:/bin, cualquier programa instalado en otro lugar no se encuentra por su nombre. Eso incluye los scripts propios de ~/bin, casi todo lo que está en /usr/local/bin, las herramientas de /usr/sbin como ip o iptables, y lo instalado con gestores como pip, npm o snap. command -v dice dónde está cada uno:

terminal
$ command -v respaldar rsync node
/home/ana/bin/respaldar
/usr/bin/rsync
/home/ana/.nvm/versions/node/v22.11.0/bin/node

Hay tres soluciones, que conviene combinar:

  1. Rutas absolutas en el crontab. El programa que el crontab ejecuta se escribe siempre con su ruta completa: /home/ana/bin/respaldar -q, no respaldar -q.
  2. Un PATH completo al comienzo del crontab, como vimos en el tema 33, escrito sin variables porque cron no las expande.
  3. Un PATH fijo al comienzo del script. Es la más robusta, porque el script queda autosuficiente: funciona igual en cron, en la terminal, con sudo o llamado desde otro script, y no depende de que nadie haya configurado nada.
el comienzo de un script pensado para cron
#!/usr/bin/env bash
set -euo pipefail

export PATH="$HOME/bin:/usr/local/bin:/usr/bin:/bin"

# Los archivos del propio script, relativos a su ubicación y no al directorio actual
dir_script=$(dirname -- "$(readlink -f -- "$0")")
config="$dir_script/respaldo.conf"

Las dos últimas líneas resuelven el otro problema de rutas. Un script que abre respaldo.conf o ./datos/ los busca en el directorio actual, que en la terminal suele ser el del script, porque uno lo ejecuta desde allí, pero en cron es el directorio personal. Calcular las rutas a partir de la ubicación del script, con readlink -f, que resuelve los enlaces simbólicos, hace que el directorio actual deje de importar.

5. Lo que falta además del PATH

El PATH es el caso más visible, pero no el único. Estas son las otras diferencias que rompen tareas, con su síntoma típico:

FaltaSíntomaSolución
Los archivos de configuración: ~/.bashrc, ~/.profileAlias, funciones y variables propias no existen; herramientas como nvm, pyenv o conda no están activadas.Definir en el script lo que necesita, o llamar al programa con su ruta completa.
La terminal y TERMtput: No value for $TERM; programas que preguntan algo o muestran barras de progreso fallan o se cuelgan.Colores solo con [[ -t 2 ]], como en el tema 25; opciones como -q, --batch, -y.
La entrada estándarUn read recibe fin de archivo y el script sigue con un valor vacío.Ningún dato interactivo: todo por argumentos o archivos de configuración.
El idioma (LANG)Los nombres de días y meses salen en inglés; cambia el orden de sort; los caracteres acentuados se ven mal.Fijar LC_ALL en el script con el idioma que se quiera.
El agente de SSHssh y rsync piden una contraseña que nadie escribe, y fallan con Permission denied.Una clave dedicada, sin frase de paso o con permisos limitados, indicada con -i, y -o BatchMode=yes.
sudo con contraseñasudo: a terminal is required to read the password.Poner la tarea en el crontab de root o en /etc/cron.d, o una regla de sudoers para ese comando.
La sesión gráficanotify-send u otros programas gráficos no muestran nada.Para avisos, mejor un correo o un mensaje a un servicio de chat.

El idioma merece un ejemplo, porque no produce un error sino un resultado distinto, que puede pasar inadvertido mucho tiempo:

terminal · el mismo informe, en dos idiomas
$ date '+Informe del %A %d de %B'
Informe del jueves 15 de octubre
$ comocron "date '+Informe del %A %d de %B'"
Informe del Thursday 15 de October
$ comocron "LC_ALL=es_AR.UTF-8 date '+Informe del %A %d de %B'"
Informe del jueves 15 de octubre

Una tentación frecuente es poner al comienzo del script source ~/.bashrc para «tener lo mismo que en la terminal». No funciona bien: la mayoría de los .bashrc empiezan comprobando si la shell es interactiva y, si no lo es, terminan sin hacer nada; y si no, cargan cosas pensadas para una persona, como el prompt o los alias. Es mejor que el script declare explícitamente lo que necesita, aunque sean tres líneas más.

6. Adónde va la salida

Lo que una tarea escribe, en la salida estándar o en la de errores, cron lo envía por correo al usuario, o a la dirección de MAILTO. Eso supone que el equipo tiene configurado un servicio de correo, lo que hoy es raro: en la mayoría de los servidores y equipos personales no lo hay, y entonces cron descarta la salida y deja una sola línea en su registro:

registro de cron · la salida se perdió
oct 15 02:30:01 servidor CRON[51201]: (ana) CMD (/home/ana/bin/respaldar -q)
oct 15 02:30:04 servidor CRON[51200]: (CRON) info (No MTA installed, discarding output)

Esa línea dice que la tarea escribió algo, quizás un mensaje de error, y que nadie lo verá. La solución es decidir adónde va la salida, redirigiéndola en el crontab:

redirigir en el crontab
# Las dos salidas, agregadas a un archivo
30 2 * * * /home/ana/bin/respaldar -q >> /home/ana/registros/respaldo.log 2>&1

# Las dos salidas, al registro del sistema, con una etiqueta
30 2 * * * systemd-cat -t respaldo /home/ana/bin/respaldar -q

El orden de las redirecciones importa: >> archivo 2>&1 primero manda la salida estándar al archivo y después hace que la de errores vaya al mismo lugar. Al revés, 2>&1 >> archivo, los errores se copian al destino que tenía la salida estándar antes del cambio, y siguen yendo al correo. El directorio del archivo tiene que existir: cron no lo crea, y si no está, la redirección falla y el comando ni siquiera se ejecuta.

systemd-cat envía todo al registro del sistema, que se consulta con journalctl -t respaldo, con fecha y hora en cada línea y la rotación ya resuelta. Es la versión para comandos externos del logger del tema 25. Y si el script es propio, la redirección puede ir dentro de él, con el exec >> registro 2>&1 de ese mismo tema, y la línea del crontab queda limpia.

No descartes la salida

Es muy común ver > /dev/null 2>&1 al final de las líneas de crontab, para que cron «no moleste». Funciona, pero tira también los mensajes de error, y cuando la tarea falla no queda ningún rastro de por qué. Si una tarea es demasiado conversadora, la solución es que hable menos cuando todo va bien, no taparle la boca.

7. Silencio cuando todo va bien

Hay una convención que hace que el correo de cron sea útil en lugar de molesto: una tarea no escribe nada si todo salió bien, y escribe solo cuando hay un problema. Con esa regla, cada correo de cron es una alarma que merece atención; sin ella, llegan treinta correos al día que nadie lee, y el que anuncia un fallo se pierde entre ellos.

Muchos programas tienen una opción para eso, como -q en rsync o wget. Los scripts propios pueden seguir la misma idea, con una opción -q como la del tema 25 que reduzca los mensajes a las advertencias y los errores. Y para un programa que no se puede silenciar, existe chronic, del paquete moreutils: ejecuta el comando guardando su salida, y solo la muestra si terminó con un código distinto de cero.

silencio salvo error
MAILTO=ana@example.com
30 2 * * * chronic /home/ana/bin/respaldar      # un correo solo si falla

El script de la sección 10 aplica esta misma idea, y agrega lo que chronic no hace: guardar también la salida de las ejecuciones exitosas, por si alguna vez hace falta consultarla.

8. Revisar qué pasó

Cuando una tarea no hizo lo que debía, conviene revisar en orden, desde lo más general a lo más particular, en lugar de cambiar cosas al azar. Es el método del tema 26 aplicado a cron:

  1. ¿Está cron en marcha? systemctl status cron, o crond en Fedora y RHEL. En contenedores y en algunos sistemas mínimos, cron ni siquiera está instalado. sistema
  2. ¿Está la línea en el crontab correcto? crontab -l muestra el del usuario actual; sudo crontab -l, el de root, que es otro. Una confusión habitual es instalar la tarea en uno y buscarla en el otro. instalada
  3. ¿Cron la lanzó? En el registro de cron tiene que haber una línea CMD a la hora prevista. Si no la hay, el problema está en el horario o en el archivo: un campo mal escrito, un % que cortó la línea, un archivo de /etc/cron.d ignorado. lanzada
  4. ¿Qué dijo? Su registro, su correo, o la línea No MTA installed que indica que dijo algo y se perdió. salida
  5. ¿Falla en el entorno de cron? Ejecutar la línea con comocron, y si falla allí, se depura en la terminal como cualquier script, con bash -x. reproducir
terminal · el registro de cron
$ journalctl -u cron --since today | grep -v pam_unix
oct 15 02:30:01 servidor CRON[51201]: (ana) CMD (/home/ana/bin/respaldar -q)
oct 15 02:30:04 servidor CRON[51200]: (CRON) info (No MTA installed, discarding output)
oct 15 03:00:01 servidor cron[612]: (*system*respaldo) WRONG FILE OWNER (/etc/cron.d/respaldo)
$ grep CRON /var/log/syslog                     # en sistemas sin journald

El grep -v quita las líneas de apertura y cierre de sesión, que aparecen en cada ejecución y no aportan nada aquí. La última línea muestra otro tipo de mensaje: cron rechazó un archivo de /etc/cron.d porque no pertenece a root. Es una de las pocas ocasiones en que cron avisa de un problema con un archivo; por eso conviene mirar su registro después de instalar uno.

Lo que el registro de cron no dice es cómo terminó la tarea: anota que la lanzó, pero no su código de salida ni cuánto tardó. Para saberlo, la tarea tiene que registrarlo ella misma.

9. Registrar cada ejecución

Tres preguntas aparecen siempre que se administra un conjunto de tareas programadas: ¿cuándo corrió por última vez?, ¿terminó bien?, ¿qué dijo? Si cada script responde a su manera, o no responde, las respuestas están dispersas o no existen. La solución habitual es un envoltorio: un script que recibe la tarea como argumento, la ejecuta y se ocupa de todo lo que la rodea.

un crontab con envoltorio
MAILTO=ana@example.com
30 2 * * *   /home/ana/bin/ejecutar-tarea -t 3600 respaldo /home/ana/bin/respaldar
*/10 * * * * /home/ana/bin/ejecutar-tarea -t 540 sincronizar /home/ana/bin/sincronizar
0 8 * * 1    /home/ana/bin/ejecutar-tarea informe /home/ana/bin/informe-semanal

Las tareas no necesitan saber nada de cron, y todas ganan lo mismo: un entorno conocido, un bloqueo contra ejecuciones simultáneas, un límite de tiempo, un registro con la fecha, el código y la duración de cada ejecución, y un correo solo cuando algo falla. Eso es lo que hace el script de la sección siguiente.

10. Un script completo

ejecutar-tarea reúne todo el tema y varias técnicas de los bloques anteriores: el modo estricto, getopts, flock, mktemp con trap, y los códigos de salida para distinguir los fallos.

ejecutar-tarea
#!/usr/bin/env bash
#
# ejecutar-tarea — envuelve una tarea de cron: le da un entorno conocido,
# evita ejecuciones simultáneas, registra cada ejecución y solo produce
# salida, y por lo tanto correo, cuando la tarea falla.
#
# Uso: ejecutar-tarea [-t SEGUNDOS] NOMBRE COMANDO [ARGUMENTO...]
#   -t  límite de tiempo; si se supera, la tarea se detiene
#
set -euo pipefail

export PATH="$HOME/bin:/usr/local/bin:/usr/bin:/bin"
export LC_ALL=C.UTF-8
umask 027

prog="${0##*/}"
uso() { echo "Uso: $prog [-t SEGUNDOS] NOMBRE COMANDO [ARGUMENTO...]" >&2; exit 2; }
ahora() { date '+%F %T'; }

limite=0
while getopts ":t:" opcion; do
  case "$opcion" in
    t) [[ "$OPTARG" =~ ^[1-9][0-9]*$ ]] || uso; limite="$OPTARG" ;;
    *) uso ;;
  esac
done
shift $(( OPTIND - 1 ))
(( $# >= 2 )) || uso
nombre="$1"; shift
[[ "$nombre" =~ ^[A-Za-z0-9_-]+$ ]] || { echo "$prog: nombre no válido: $nombre" >&2; exit 2; }

dir="${XDG_STATE_HOME:-$HOME/.local/state}/tareas"
mkdir -p "$dir"
registro="$dir/$nombre.log"

exec 9> "$dir/$nombre.lock"
if ! flock -n 9; then
  echo "$(ahora)  ya hay una ejecución en marcha; esta se omite" >> "$registro"
  exit 0
fi

if [[ -f "$registro" ]] && (( $(stat -c %s "$registro") > 1048576 )); then
  mv -f -- "$registro" "$registro.1"     # más de 1 MiB: se guarda uno anterior
fi

salida=$(mktemp)
trap 'rm -f -- "$salida"' EXIT

cd "$HOME"
echo "$(ahora)  inicio: $*" >> "$registro"
inicio=$SECONDS
codigo=0
if (( limite > 0 )); then
  timeout "$limite" "$@" < /dev/null > "$salida" 2>&1 || codigo=$?
else
  "$@" < /dev/null > "$salida" 2>&1 || codigo=$?
fi
duracion=$(( SECONDS - inicio ))

cat -- "$salida" >> "$registro"
echo "$(ahora)  fin: código $codigo, $duracion s" >> "$registro"
echo "$(ahora) $codigo $duracion" > "$dir/$nombre.ultimo"

if (( codigo != 0 )); then
  motivo="terminó con código $codigo"
  (( limite > 0 && codigo == 124 )) && motivo="superó el límite de $limite s"
  logger -t "$prog" -p user.err -- "$nombre: $motivo" || true
  echo "La tarea $nombre $motivo. Su salida fue:"     # esto lo envía cron por correo
  cat -- "$salida"
fi
exit "$codigo"
terminal · probarlo a mano, como lo haría cron
$ ejecutar-tarea respaldo /home/ana/bin/respaldar
$ echo $?
0
$ ejecutar-tarea sincronizar /home/ana/bin/sincronizar
La tarea sincronizar terminó con código 3. Su salida fue:
Conectando con copias.example.com…
sincronizar: no se pudo conectar con copias.example.com
$ ejecutar-tarea -t 2 lenta sleep 5
La tarea lenta superó el límite de 2 s. Su salida fue:
$ ejecutar-tarea informe informe-semanal
La tarea informe terminó con código 127. Su salida fue:
/home/ana/bin/ejecutar-tarea: line 56: informe-semanal: command not found
$ cat ~/.local/state/tareas/respaldo.log
2026-10-15 02:30:01  inicio: /home/ana/bin/respaldar
Copiando /home/ana/documentos…
Respaldo completo: 1284 archivos, 412 MiB
2026-10-15 02:31:47  fin: código 0, 106 s
$ cat ~/.local/state/tareas/sincronizar.ultimo
2026-10-15 10:40:02 3 1
$ journalctl -t ejecutar-tarea --since today
oct 15 10:40:02 servidor ejecutar-tarea[52817]: sincronizar: terminó con código 3
oct 15 10:41:12 servidor ejecutar-tarea[52866]: lenta: superó el límite de 2 s
oct 15 10:41:30 servidor ejecutar-tarea[52890]: informe: terminó con código 127

Con LC_ALL fijado, los mensajes de los programas salen en inglés, como el command not found del ejemplo, que es además lo que conviene para buscar un error en Internet. Y una limitación: el bloqueo lo hereda la tarea, de modo que si esta lanza un proceso en segundo plano que sigue vivo después de terminar, el bloqueo dura lo que dure ese proceso.

11. Errores frecuentes

EscribisteQué pasaCorrección
30 2 * * * respaldarrespaldar: not found: ~/bin no está en el PATH de cron.La ruta absoluta, y un PATH fijo en el script.
Un script que abre ./config.confEn cron, el directorio actual es el personal: no lo encuentra.Rutas a partir de dirname "$(readlink -f "$0")".
source ~/.bashrc al comienzo del scriptNo carga nada, porque la shell no es interactiva, o carga cosas inútiles.Declarar en el script lo que necesita.
... > /dev/null 2>&1 en el crontabLos errores se pierden, y cuando falla no hay rastro.Redirigir a un archivo o al registro del sistema.
... 2>&1 >> archivoLos errores siguen yendo al correo, o se pierden.>> archivo 2>&1
>> ~/registros/x.log sin crear el directorioLa redirección falla y la tarea no se ejecuta.Crear el directorio antes, o redirigir dentro del script.
Una tarea que escribe en cada ejecuciónMuchos correos que nadie lee; el del fallo se pierde.Silencio salvo error: -q, chronic o un envoltorio.
Fechas con date +%A en un informeSin idioma, salen en inglés.export LC_ALL=es_AR.UTF-8 en el script.
rsync o ssh con la clave del agenteEn cron no hay agente: Permission denied, o se queda esperando.-i clave y -o BatchMode=yes.
sudo dentro de la tareaa terminal is required to read the password.El crontab de root, /etc/cron.d, o una regla de sudoers.
Buscar la tarea con crontab -l siendo otro usuarioNo aparece: está en el crontab de root, o al revés.sudo crontab -l, sudo crontab -u usuario -l.

12. Resumen

13. Preguntas de repaso

Intenta responder cada una antes de abrir la respuesta.

1. ¿Por qué un programa instalado en ~/bin se encuentra en la terminal y no en cron?

Porque ~/bin lo agrega al PATH algún archivo de configuración de la sesión, como ~/.profile, que cron no lee. En cron, el PATH es solo /usr/bin:/bin.

2. ¿Cómo se averigua qué variables recibe exactamente una tarea de cron?

Con una tarea de prueba que guarde la salida de env en un archivo, como * * * * * env > /tmp/env-cron.txt, y quitándola después de revisar el archivo.

3. ¿Qué hace env -i HOME="$HOME" PATH=/usr/bin:/bin /bin/sh -c 'comando'?

Ejecuta el comando con /bin/sh y un entorno vacío salvo esas dos variables, parecido al de cron. Sirve para reproducir en la terminal los errores que aparecen en cron, sin esperar a que se ejecute la tarea.

4. Un script abre datos.csv, que está junto a él. Funciona a mano, pero no en cron. ¿Por qué, y cómo se corrige?

Porque el nombre es relativo al directorio actual, que a mano suele ser el del script y en cron es el directorio personal. Se corrige calculando la ruta desde la ubicación del script: "$(dirname -- "$(readlink -f -- "$0")")/datos.csv".

5. ¿Qué significa la línea No MTA installed, discarding output en el registro de cron?

Que la tarea escribió algo en su salida, que cron quiso enviar por correo, pero el equipo no tiene servicio de correo, y la salida se descartó. Puede haber sido un mensaje de error.

6. ¿Qué diferencia hay entre >> registro 2>&1 y 2>&1 >> registro?

En la primera, las dos salidas van al archivo. En la segunda, la de errores se copia al destino que tenía la salida estándar antes de redirigirla, es decir, el original de cron, y solo la salida estándar va al archivo.

7. ¿Por qué no conviene terminar las líneas de crontab con > /dev/null 2>&1?

Porque descarta también los mensajes de error: cuando la tarea falla, no queda ningún rastro de por qué. Es mejor guardar la salida en un registro y que la tarea calle cuando todo va bien.

8. ¿Qué hace chronic?

Ejecuta un comando guardando su salida, y solo la muestra si el comando terminó con un código distinto de cero. En cron, eso hace que llegue un correo solo cuando la tarea falla.

9. El registro de cron muestra la línea CMD de una tarea a la hora correcta. ¿Qué dice eso, y qué no?

Dice que cron lanzó el comando, de modo que el horario y la línea están bien. No dice si el comando funcionó: para eso hay que mirar su salida, su registro o su resultado.

10. ¿Por qué ejecutar-tarea termina con 0 cuando encuentra el bloqueo ocupado?

Porque que la ejecución anterior siga en marcha no es un fallo sino una situación prevista: la nueva se omite y lo anota en el registro. Terminar con 0 y sin escribir nada en la salida evita un correo innecesario.

Para practicar

  1. Guarda el entorno de cron con una tarea de prueba y compáralo con el de tu terminal usando comm. ¿Cuántas variables faltan?
  2. Agrega la función comocron a tu ~/.bashrc y úsala para probar los comandos de tu crontab. ¿Alguno falla?
  3. Escribe un script que use un programa de ~/bin y abra un archivo junto a él. Hazlo fallar en cron y luego corrígelo con un PATH fijo y rutas calculadas desde $0.
  4. Programa una tarea que escriba la fecha con date +\%A en un archivo, y comprueba en qué idioma sale. Luego fija LC_ALL.
  5. Instala ejecutar-tarea y usa con él una tarea que a veces falle, como un ping -c1 a un equipo que no siempre está encendido. Revisa el .log, el .ultimo y journalctl -t ejecutar-tarea.
  6. Escribe un script estado-tareas que lea todos los archivos .ultimo y muestre una tabla con el nombre, la fecha, el resultado y la duración de cada tarea, marcando las que fallaron o hace más de un día que no corren.