Cron et Crontab, kesako ?
Posté : dim. 15 sept. 2024 16:46
Le CRON : Qu'est-ce que c'est ? Comment ça fonctionne ?
Tout le monde sait ce qu'est un réveil-matin, cet appareil qu'on programme pour qu'à une certaine heure il émette un signal sonore, ou allume la radio, ou clignote, ou déclenche quelque action destinée à vous réveiller. Un tel dispositif, destiné à lancer une action à un moment prédéterminé, existe sous Unix et Linux. Il s'appelle le CRON.
Le CRON est donc un programme qui, à chaque minute, vérifie s'il doit lancer une ou plusieurs actions programmées. Comme ce programme s'exécute comme un service en arrière-plan, on dit qu'il s'agit d'un daemon (crond). Pour savoir ce qu'il a à faire, il se base sur une configuration qui peut être modifiée par une commande : crontab. Son nom qui vient de ChRONological TABle. La localisation de la configuration n'a pas besoin d'être connue puisque cette configuration sera modifiée par l'utilisation de la commande crontab.
Néanmoins, pour les curieux, vous les trouverez, pour Linux Mint, dans /var/spool/cron/crontabs. Vous noterez également l'existence d'un répertoire /var/spool/anachron. Il s'agit là des actions qui peuvent être lancées de façon asynchrone, dans le cas des ordinateurs qui ne fonctionnent pas 24/7.
On emploiera indistinctement commande, action ou tâche, c'est la même chose.
J'apprends à lire le CRON
Maintenant qu'on sait tout ça, comment est-ce qu'on joue avec ?
La première chose, c'est de prendre connaissance de la liste des tâches programmées. Ca se fait grâce à la commande suivante (à exécuter dans un terminal, comme n'importe quelle commande manuelle) :
A ce stade, votre liste des tâches programmées est probablement vide. Si tel est le cas vous obtiendrez le message "no crontab for [user]". Nous verrons juste un peu plus loin comment créer une entrée dans le crontab. Si des tâches sont déjà programmées, elles apparaîtront sous le format suivant :
Dans ce exemple, vous constatez :
Notez le "#" au début, parce qu'en ce moment pour notre exemple c'est les vacances, donc pas besoin d'aller les chercher 
Vous avez compris à ce stade que les commandes suivantes sont donc parfaitement équivalentes :
Ok, maintenant qu'on sait ça, on peut commencer à combiner et faire des trucs rigolos. Pour une action à déclencher chaque lundi à 7h30 du 1er au 7 du mois, puis du 14 au 21 :
Chaque valeur que vous spécifiez pour une condition agit comme un filtre cumulatif (un ET logique). Pour que l'action soit exécutée, les 5 conditions doivent être remplies.
Comme vous vous en doutez, on peut toujours ajouter une condition dans la commande elle-même bien entendu, ce qui permet de compléter si toutefois on avait besoin d'une fonctionnalité manquante. Exemple, si on veut lancer une commande tous les vendredis, une semaine sur deux, à minuit :
Le calcul de la semaine sur deux se fait dans la partie commande elle-même pour introduire une condition de déclenchement supplémentaire. Bon, ok, c'est triché. On aurait pu faire plus élégant :
Le premier qui explique pourquoi 336 dans une réponse à ce post gagne la considération de l'équipe éditoriale.
Autre exemple pour tous les derniers jours du mois à minuit :
Ah, oui, les jours de semaine, comme les jours de mois, peuvent aussi être exprimés sous forme des trois premières lettres de leur nom (en anglais). Mais des subtilités comme ça, y'en a pas mal. Par exemple, le dimanche est le jour 0 mais aussi le jour 7, ce qui permet d'assurer la compatibilté avec les deux types de semaine. Nos amis développeurs sont des petits malins qui adorent ce genre d'astuces.
Et les crontabs des autres ?
Le CRON peut être spécifique à un utilisateur, ou général à tout le système. Un utilisateur peut s'être programmé des tâches qui n'ont aucun intérêt pour les autres utilisateurs du système (pas besoin de réveiller Madame qui fait une grasse mat' pour aller emmener les enfants au volley du samedi matin).
Si vous n'êtes pas le seul utilisateur de votre ordinateur, vous ne verrez que vos propres programmations. Mais si vous êtes l'adminstrateur, vous pouvez avoir besoin de voir ce que les autres ont mis en place. C'est faisable, avec un petit sudo devant :
En remplaçant évidemment username par le nom d'utilisateur dont vous voulez voir la programmation.
Et les actions programmées par le système lui-même ?
Le crontab du système lui-même peut être accédé par la lecture du fichier /etc/crontab.
Les scripts programmés sont rangés dans /etc/cron.daily/, /etc/cron.weekly/, etc. On peut y accéder avec un simple
Des applications peuvent elles-mêmes programmer des actions pour leurs propres besoins. Elles insèreront alors leurs entrées au format CRON dans le fichier /etc/cron.d
J'apprends à écrire le CRON
Maintenant qu'on sait lire, apprenons à écrire. Ce petit tutoriel n'a pas l'ambition de vous apprendre à coder en bash, ce n'est pas l'objet. On va aborder des choses très basiques uniquement, juste de quoi avoir de bonnes pratiques vis-à-vis de notre écriture du crontab.
Tout commence par la commande suivante :
Si vous n'avez encore jamais édité le crontab, la commande vous propose de choisir l'éditeur, tout en conseillant l'un d'eux, le plus facile à utiliser. Faites ce que vous voulez, mais si vous n'êtes pas familier de ce type d'outil, choisissez celui qui est conseillé, c'est conseillé (!)
Quand vous aurez fini d'écrire votre programmation de tâche, enregistrez le fichier avec le nom proposé et quittez l'éditeur. Pour Nano, c'est [ctrl]+[o] suivi de [enter] puis de [ctrl]+[x].
Si vous êtes super à l'aise et que vous voulez fournir votre propre fichier texte au format crontab et l'intégrer comme votre nouvelle table de programmation :
Enfin si vous voulez faire table rase de votre programmation (et uniquement la vôtre, celle de l'utilisateur courant donc) :
Si vous voulez vérifier votre expression, un petit outil bien pratique : https://crontab.guru/
Comment on gère les erreurs ?
Et oui, bonne question ! Comme ces commandes vont s'exécuter en arrière-plan, donc sans qu'on les voit faire, il va bien falloir qu'on s'assure qu'elles fonctionnent bien, ou qu'a minima, des erreurs ne vont pas se cumuler, voire, pire, partir en boucle infinie et remplir nos logs d'un tas de fatras.
Il faut savoir qu'un système Linux attribue systématiquement 3 fichiers (ou flux) à un programme (ou une commande):
Fort de cette notion de flux et de redirection, on va pouvoir commencer à gérer les erreurs de nos commandes. A la fin de notre commande, on va donc ajouter une redirection, c'est-à-dire détourner un flux de données de sa destination par défaut vers la destination de notre choix. On peut donc tout simplement ajouter "> ~/resultat.txt" en fin de notre commande et tout ce qui sortirait en résultat standard irait dans le fichier "resultat.txt". Ainsi quand je fais "echo Salut les gars !", 1 vaut "Salut les gars !". Si ma commande est "echo Salut les gars ! > ~/resultat.txt", alors j'envoie mon "Salut les gars !" (la sortie standard de ma commande) dans le fichier resultat.txt de mon répertoire Home et rien ne sort sur l'écran puisque j'ai redirigé (vous avez compris au passage que ~/ est un raccourci pour le répertoire home de l'utilisateur courant
Dans cet exemple, on redirige le flux STDOUT, mais on pourrait rediriger tout pareillement le STDERR. Il suffirait d'ajouter " 2>~/resultat-errors.txt" à ma commande.
Plus classiquement, quand on veut tout neutraliser et être sûr de ne générer aucune erreur, on utilise alors une expression un peu spéciale à la fin de notre commande.
Mais qu'est-ce que c'est que ce bazar ? No panic, on va décomposer et vous allez tout comprendre.
On a vu qu'une commande suivie d'un " > fichier" redirigeait le résultat de sa sortie standard (STDOUT) dans le fichier spécifié. Là on choisit de rediriger vers le fichier /dev/null. Ce fichier est très particulier puisqu'en fait le système sait que ce qui arrive sur ce fichier, il ne doit tout simplempent rien en faire. Il l'ignore. Aucune opération, rien, nada. Ce qui arrive là est perdu pour l'éternité.
On a vu aussi qu'on pouvait rediriger le STDOUT et qu'on pouvait aussi tout pareillement rediriger le STDERR. C'est ce qu'on fait quand on écrit "2>&1"j, ça veut dire : tout ce qui arrive dans 2 (donc STDERR) tu l'envoies dans ce vers quoi 1 (donc STDOUT) est lui-même redirigé, donc ici, vers /dev/null. Attention pas dans STDOUT lui-même mais bien dans ce vers quoi STDOUT est redirigé. C'est à ça que sert le petit "&". Malins ces développeurs !
Ainsi tout ce qui sortira de notre commande suivie de ">/dev/null 2>&1", tant sur STDOUT que sur STDERR sera totalement annihilé, direction le néant absolu, bon débarras !
Et si on veut lancer plusieurs commandes à la suite ?
Deux solutions basiques :
L'opérateur & évalue l'expression de gauche (A) et de droite (B) avant de prononcer son résultat.
L'opérateur && est un "raccourci" dans le sens où, si l'expression à gauche de l'opérateur (A) est fausse, il ne cherche pas à continuer et s'interrompt puisqu'il sait déjà que le résultat final sera faux (faux & n'importe quoi vaut toujours faux).
Utiliser l'opérateur & permet donc de s'assurer que toute la chaine de commande sera exécutée, alors que l'opérateur && permet de s'arrêter dès qu'une commande de la chaîne lue de gauche à droite retourne faux.
Ok, j'ai compris comment lancer une commande et une chaîne de commandes, mais si je veux lancer un script complet ?
Rien de plus simple !
En fait l'interpréteur par défaut est utilisé pour une commande simple, mais si on veut lancer un script, donc une série de commandes, il va falloir s'assurer de plusieurs petites choses.
Imaginons que je veuille lancer un script qui va générer une notification. J'écris mon super script et je le sauvegarde dans home/bazar/faiscoucou.sh
1) Spécifier au début du script l'interpréteur à utiliser pour exécuter le script :
Vous avez compris : "#!" est le petit truc sympa qui informe le kernel de ce qu'il doit utiliser pour interpréter vos commandes. Ici c'est du bash, mais ça pourrait être du PHP ou autre chose, selon vos compétences.
2) S'assurer que le script est exécutable :
A vous de jouer !
Tout le monde sait ce qu'est un réveil-matin, cet appareil qu'on programme pour qu'à une certaine heure il émette un signal sonore, ou allume la radio, ou clignote, ou déclenche quelque action destinée à vous réveiller. Un tel dispositif, destiné à lancer une action à un moment prédéterminé, existe sous Unix et Linux. Il s'appelle le CRON.
Le CRON est donc un programme qui, à chaque minute, vérifie s'il doit lancer une ou plusieurs actions programmées. Comme ce programme s'exécute comme un service en arrière-plan, on dit qu'il s'agit d'un daemon (crond). Pour savoir ce qu'il a à faire, il se base sur une configuration qui peut être modifiée par une commande : crontab. Son nom qui vient de ChRONological TABle. La localisation de la configuration n'a pas besoin d'être connue puisque cette configuration sera modifiée par l'utilisation de la commande crontab.
Néanmoins, pour les curieux, vous les trouverez, pour Linux Mint, dans /var/spool/cron/crontabs. Vous noterez également l'existence d'un répertoire /var/spool/anachron. Il s'agit là des actions qui peuvent être lancées de façon asynchrone, dans le cas des ordinateurs qui ne fonctionnent pas 24/7.
On emploiera indistinctement commande, action ou tâche, c'est la même chose.
J'apprends à lire le CRON
Maintenant qu'on sait tout ça, comment est-ce qu'on joue avec ?
La première chose, c'est de prendre connaissance de la liste des tâches programmées. Ca se fait grâce à la commande suivante (à exécuter dans un terminal, comme n'importe quelle commande manuelle) :
Code : Tout sélectionner
crontab -lCode : Tout sélectionner
mm hh jm MM jw commande- mm représente les minutes (0-59)
- hh représente l'heure (0 à 23)
- jm représente le jour du mois (1-31)
- MM représente le mois (1-12)
- jw représente le jour de la semaine (0-6, 0 pour le dimanche)
- commande
Code : Tout sélectionner
5 19 5 * * echo Check ton solde !- que chaque valeur est séparée par un espace ;
- qu'une valeur peut être omise, et on utilise alors un astérisque "*" qui signifie "n'importe quelle valeur possible pour cette colonne".
- "-", aka "tiret du 6", permet d'indiquer une plage de valeurs, par exemple 8-10 dans MM signifie "d'aout à octobre" ;
- "," la virgule est utilisée s'il y a plusieurs valeurs admises, par exemple 1,2,4,5 dans jw signifie lundi, mardi, jeudi ou vendredi.
- "/" permet de donner un rythme et se lit donc "tou(te)s les", par exemple "*/12" dans hh signifie "toutes les 12h" ; La valeur à gauche du "/" détermine le point de départ du calcul, par exemple "5/3" dans la colonne hh signifie tous les trois heures à partir de 5h et jusqu'à 23, et "5-15/3" signifie donc toutes les trois heures à partir de 5h et jusqu'à 15h.
- "#" en début de ligne permet de mettre une action en commentaire, et donc elle ne sera plus lancée même si les conditions sont remplies.
Code : Tout sélectionner
#45 16 5-15 * 1,2,4,5 echo Récupérer les enfants à l'écoleVous avez compris à ce stade que les commandes suivantes sont donc parfaitement équivalentes :
Code : Tout sélectionner
* * * * * commande
0-59 0-23 0-31 0-12 0-6 commande
* * * 0,1,2,3,4,5,6,7,8,9,10,11,12 0,1,2,3,4,5,6 commandeOk, maintenant qu'on sait ça, on peut commencer à combiner et faire des trucs rigolos. Pour une action à déclencher chaque lundi à 7h30 du 1er au 7 du mois, puis du 14 au 21 :
Code : Tout sélectionner
30 7 1-7,14-21 * 1 echo Debout là-dedans !!!Comme vous vous en doutez, on peut toujours ajouter une condition dans la commande elle-même bien entendu, ce qui permet de compléter si toutefois on avait besoin d'une fonctionnalité manquante. Exemple, si on veut lancer une commande tous les vendredis, une semaine sur deux, à minuit :
Code : Tout sélectionner
0 0 * * Fri [ $(expr $(date +%W) \% 2) -eq 1 ] && commandCode : Tout sélectionner
0 */336 * * Fri commandAutre exemple pour tous les derniers jours du mois à minuit :
Code : Tout sélectionner
0 0 28-31 * * [ `/bin/date +\%d` -gt `/bin/date +\%d -d "1 day"` ] && commandEt les crontabs des autres ?
Le CRON peut être spécifique à un utilisateur, ou général à tout le système. Un utilisateur peut s'être programmé des tâches qui n'ont aucun intérêt pour les autres utilisateurs du système (pas besoin de réveiller Madame qui fait une grasse mat' pour aller emmener les enfants au volley du samedi matin).
Si vous n'êtes pas le seul utilisateur de votre ordinateur, vous ne verrez que vos propres programmations. Mais si vous êtes l'adminstrateur, vous pouvez avoir besoin de voir ce que les autres ont mis en place. C'est faisable, avec un petit sudo devant :
Code : Tout sélectionner
sudo crontab -l -u usernameEt les actions programmées par le système lui-même ?
Le crontab du système lui-même peut être accédé par la lecture du fichier /etc/crontab.
Code : Tout sélectionner
cat /etc/crontabCode : Tout sélectionner
ls -l /etc/cron.dailyJ'apprends à écrire le CRON
Maintenant qu'on sait lire, apprenons à écrire. Ce petit tutoriel n'a pas l'ambition de vous apprendre à coder en bash, ce n'est pas l'objet. On va aborder des choses très basiques uniquement, juste de quoi avoir de bonnes pratiques vis-à-vis de notre écriture du crontab.
Tout commence par la commande suivante :
Code : Tout sélectionner
crontab -eQuand vous aurez fini d'écrire votre programmation de tâche, enregistrez le fichier avec le nom proposé et quittez l'éditeur. Pour Nano, c'est [ctrl]+[o] suivi de [enter] puis de [ctrl]+[x].
Si vous êtes super à l'aise et que vous voulez fournir votre propre fichier texte au format crontab et l'intégrer comme votre nouvelle table de programmation :
Code : Tout sélectionner
crontab monfichiercron.txtCode : Tout sélectionner
crontab -rComment on gère les erreurs ?
Et oui, bonne question ! Comme ces commandes vont s'exécuter en arrière-plan, donc sans qu'on les voit faire, il va bien falloir qu'on s'assure qu'elles fonctionnent bien, ou qu'a minima, des erreurs ne vont pas se cumuler, voire, pire, partir en boucle infinie et remplir nos logs d'un tas de fatras.
Il faut savoir qu'un système Linux attribue systématiquement 3 fichiers (ou flux) à un programme (ou une commande):
- 0 : STDIN, le flux d'entrée d'un process, autrement dit la donnée qu'on passe en entrée au programme, ici il s'agit de notre commande que CRON va exécuter.
- 1 : STDOUT, le flux de sortie standard quand tout fonctionne bien, en général l'écran.
- 2 : STDERR, le flux de sortie des erreurs, ce qui surgit de façon imprévue, l'écran ou une imprimante ou un fichier de logs par exemple.
- ">>" permet d'ajouter de la donnée à la fin d'un fichier défini, sans l'écraser.
- ">" permet de repartir sur un fichier tout neuf à chaque fois.
Fort de cette notion de flux et de redirection, on va pouvoir commencer à gérer les erreurs de nos commandes. A la fin de notre commande, on va donc ajouter une redirection, c'est-à-dire détourner un flux de données de sa destination par défaut vers la destination de notre choix. On peut donc tout simplement ajouter "> ~/resultat.txt" en fin de notre commande et tout ce qui sortirait en résultat standard irait dans le fichier "resultat.txt". Ainsi quand je fais "echo Salut les gars !", 1 vaut "Salut les gars !". Si ma commande est "echo Salut les gars ! > ~/resultat.txt", alors j'envoie mon "Salut les gars !" (la sortie standard de ma commande) dans le fichier resultat.txt de mon répertoire Home et rien ne sort sur l'écran puisque j'ai redirigé (vous avez compris au passage que ~/ est un raccourci pour le répertoire home de l'utilisateur courant
Dans cet exemple, on redirige le flux STDOUT, mais on pourrait rediriger tout pareillement le STDERR. Il suffirait d'ajouter " 2>~/resultat-errors.txt" à ma commande.
Plus classiquement, quand on veut tout neutraliser et être sûr de ne générer aucune erreur, on utilise alors une expression un peu spéciale à la fin de notre commande.
Code : Tout sélectionner
>/dev/null 2>&1On a vu qu'une commande suivie d'un " > fichier" redirigeait le résultat de sa sortie standard (STDOUT) dans le fichier spécifié. Là on choisit de rediriger vers le fichier /dev/null. Ce fichier est très particulier puisqu'en fait le système sait que ce qui arrive sur ce fichier, il ne doit tout simplempent rien en faire. Il l'ignore. Aucune opération, rien, nada. Ce qui arrive là est perdu pour l'éternité.
On a vu aussi qu'on pouvait rediriger le STDOUT et qu'on pouvait aussi tout pareillement rediriger le STDERR. C'est ce qu'on fait quand on écrit "2>&1"j, ça veut dire : tout ce qui arrive dans 2 (donc STDERR) tu l'envoies dans ce vers quoi 1 (donc STDOUT) est lui-même redirigé, donc ici, vers /dev/null. Attention pas dans STDOUT lui-même mais bien dans ce vers quoi STDOUT est redirigé. C'est à ça que sert le petit "&". Malins ces développeurs !
Ainsi tout ce qui sortira de notre commande suivie de ">/dev/null 2>&1", tant sur STDOUT que sur STDERR sera totalement annihilé, direction le néant absolu, bon débarras !
Et si on veut lancer plusieurs commandes à la suite ?
Deux solutions basiques :
- chainer les commandes en une seule, via l'opérateur "&", ce qui lancera toutes les commandes.
- chaîner les commandes en une seule via l'opérateur "&&", ce qui lancera une commande après l'autre.
L'opérateur & évalue l'expression de gauche (A) et de droite (B) avant de prononcer son résultat.
L'opérateur && est un "raccourci" dans le sens où, si l'expression à gauche de l'opérateur (A) est fausse, il ne cherche pas à continuer et s'interrompt puisqu'il sait déjà que le résultat final sera faux (faux & n'importe quoi vaut toujours faux).
Utiliser l'opérateur & permet donc de s'assurer que toute la chaine de commande sera exécutée, alors que l'opérateur && permet de s'arrêter dès qu'une commande de la chaîne lue de gauche à droite retourne faux.
Ok, j'ai compris comment lancer une commande et une chaîne de commandes, mais si je veux lancer un script complet ?
Rien de plus simple !
En fait l'interpréteur par défaut est utilisé pour une commande simple, mais si on veut lancer un script, donc une série de commandes, il va falloir s'assurer de plusieurs petites choses.
Imaginons que je veuille lancer un script qui va générer une notification. J'écris mon super script et je le sauvegarde dans home/bazar/faiscoucou.sh
1) Spécifier au début du script l'interpréteur à utiliser pour exécuter le script :
Code : Tout sélectionner
#!/bin/sh
notify-send "Salut les gars !"2) S'assurer que le script est exécutable :
Code : Tout sélectionner
chmod +x home/bazar/faiscoucou.sh
ls -l home/bazar/faiscoucou.shA vous de jouer !