Logrotate : corriger l’erreur MySQL “unexpected operator”

Depuis que j’ai déplacé les bases de données SQL sur une autre partition, logrotate semble avoir quelques soucis avec l’archivage des logs MySQL/MariaDB.

Voici le message d’erreur complet reçu depuis cron.daily :

/etc/cron.daily/logrotate: logrotate_script: 3: [: /var/run/mysqld/mysqld.pid: unexpected operatorCode language: JavaScript (javascript)

Ce message est un peu cryptique, mais le problème est simple : dans le script exécuté par logrotate, le test shell [ -f ... ] reçoit une valeur inattendue, souvent parce qu’une commande retourne plusieurs lignes au lieu d’un seul chemin de fichier PID.

Résultat : le shell ne comprend plus l’expression et retourne unexpected operator.

Comprendre l’erreur “unexpected operator”

L’erreur vient généralement d’une ligne de ce type dans le fichier de rotation MySQL/MariaDB :

if [ -f `my_print_defaults --mysqld | grep -oP "pid-file=\K[^$]+"` ]; thenCode language: CSS (css)

Le but de cette ligne est de récupérer le chemin du fichier PID de MySQL/MariaDB, puis de vérifier s’il existe.

Le problème apparaît si la commande retourne plusieurs chemins ou plusieurs lignes. Le shell se retrouve alors avec une expression de ce type :

if [ -f /var/run/mysqld/mysqld.pid /run/mysqld/mysqld.pid ]; thenCode language: JavaScript (javascript)

Or [ -f ... ] attend un seul chemin. Avec deux valeurs, il ne sait plus évaluer correctement le test. D’où l’erreur unexpected operator.

C’est souvent lié à une configuration MySQL/MariaDB qui contient plusieurs directives pid-file, parfois après migration, installation depuis un dépôt externe, ou déplacement de datadir/logs.

Vérifier ce que retourne my_print_defaults

Avant de modifier quoi que ce soit, vérifiez ce que retourne la commande utilisée par le script :

my_print_defaults --mysqld | grep 'pid-file'Code language: JavaScript (javascript)

Vous pouvez aussi tester la regex exacte :

my_print_defaults --mysqld | grep -oP "pid-file=\K[^$]+"Code language: JavaScript (javascript)

Si la commande retourne plusieurs lignes, vous avez trouvé la cause.

Exemple problématique :

/var/run/mysqld/mysqld.pid
/run/mysqld/mysqld.pidCode language: JavaScript (javascript)

Le test shell reçoit deux chemins, et logrotate se prend les pieds dans le tapis. Pas violemment, mais assez pour vous envoyer un email à l’aube.

Solution rapide : limiter grep au premier résultat

La correction historique consiste à limiter grep à une seule correspondance avec l’option -m1.

Éditez le fichier de configuration logrotate concerné :

sudo nano /etc/logrotate.d/mysql-server

Selon votre distribution, le fichier peut aussi s’appeler :

/etc/logrotate.d/mysql
/etc/logrotate.d/mariadb
/etc/logrotate.d/mysql-server

Recherchez la ligne suivante :

if [ -f `my_print_defaults --mysqld | grep -oP "pid-file=\K[^$]+"` ]; thenCode language: CSS (css)

Et remplacez-la par :

if [ -f `my_print_defaults --mysqld | grep -oPm1 "pid-file=\K[^$]+"` ]; thenCode language: CSS (css)

Le changement est minuscule :

grep -oP
grep -oPm1

L’option -m1 demande à grep de s’arrêter après la première correspondance. Le test [ -f ... ] reçoit donc un seul chemin et ne déclenche plus l’erreur.

Correction plus lisible : stocker le PID file dans une variable

Si vous modifiez un script vous-même, une version plus lisible consiste à éviter les backticks et à stocker le résultat dans une variable.

pid_file="$(my_print_defaults --mysqld | grep -oPm1 'pid-file=\K[^$]+')"

if [ -n "$pid_file" ] && [ -f "$pid_file" ]; then
    # actions post-rotation
fiCode language: PHP (php)

C’est plus robuste, plus lisible, et cela évite les surprises si le chemin contient des caractères inattendus.

Cela dit, si le fichier appartient à un paquet Debian/Ubuntu/MariaDB, je préfère faire une sauvegarde avant modification, car une mise à jour du paquet peut écraser ou remplacer le fichier.

sudo cp -a /etc/logrotate.d/mysql-server /etc/logrotate.d/mysql-server.bak.$(date +%Y%m%d-%H%M%S)Code language: JavaScript (javascript)

Tester logrotate sans attendre cron.daily

Inutile d’attendre le prochain passage de cron pour savoir si la correction fonctionne. Testez la configuration avec logrotate en mode debug.

sudo logrotate -d /etc/logrotate.conf

Le mode debug affiche ce que logrotate ferait, sans modifier les logs.

Pour tester uniquement un fichier :

sudo logrotate -d /etc/logrotate.d/mysql-server

Ou selon votre fichier :

sudo logrotate -d /etc/logrotate.d/mariadb

La page de manuel de logrotate documente l’option -d comme un mode debug qui n’applique aucune modification aux logs ni au fichier d’état.

Pour forcer une rotation réelle, uniquement si vous savez ce que vous faites :

sudo logrotate -f /etc/logrotate.d/mysql-server

ou :

sudo logrotate -f /etc/logrotate.d/mariadb

La même page de manuel documente -f comme l’option permettant de forcer une rotation, même si logrotate estime qu’elle n’est pas nécessaire.

Vérifier les directives pid-file en double

La correction grep -m1 règle le symptôme. Mais si plusieurs pid-file sont définis, cela vaut le coup de nettoyer la configuration MySQL/MariaDB.

Recherchez toutes les occurrences :

grep -R "^[[:space:]]*pid-file" /etc/mysql /etc/my.cnf 2>/dev/nullCode language: JavaScript (javascript)

Vous pouvez aussi inspecter toutes les options lues par MariaDB/MySQL :

my_print_defaults --mysqld

Si vous trouvez plusieurs valeurs contradictoires, gardez une seule directive cohérente, par exemple :

pid-file = /run/mysqld/mysqld.pidCode language: JavaScript (javascript)

Sur les systèmes modernes, /run est le chemin réel. /var/run est souvent un lien symbolique vers /run. Évitez d’avoir les deux si cela crée des doublons dans les scripts.

Distingo, le livret à 2%

Attention aux fichiers de paquet

Le fichier /etc/logrotate.d/mysql-server ou /etc/logrotate.d/mariadb est souvent fourni par un paquet système.

Avant modification, identifiez le paquet propriétaire :

dpkg -S /etc/logrotate.d/mysql-server 2>/dev/null
dpkg -S /etc/logrotate.d/mariadb 2>/dev/null
dpkg -S /etc/logrotate.d/mysql 2>/dev/nullCode language: JavaScript (javascript)

Si une mise à jour remplace le fichier, votre modification peut disparaître ou générer un conflit de configuration.

Quand c’est possible, je préfère corriger la cause dans la configuration MySQL/MariaDB — par exemple les directives pid-file en double — plutôt que patcher durablement le script logrotate fourni par le paquet.

Cela dit, si le script du paquet est réellement fautif, la correction grep -oPm1 reste pragmatique.

Vérifier la configuration logrotate de MariaDB

Une configuration logrotate MariaDB contient souvent un bloc de ce type :

/var/log/mysql/*.log {
    daily
    rotate 7
    missingok
    create 640 mysql adm
    compress
    sharedscripts
    postrotate
        test -x /usr/bin/mysqladmin || exit 0
        /usr/bin/mysqladmin flush-logs
    endscript
}Code language: JavaScript (javascript)

Les directives peuvent varier selon les distributions et versions.

Quelques directives utiles :

  • daily : rotation quotidienne ;
  • rotate 7 : conservation de sept archives ;
  • missingok : ne pas échouer si un fichier manque ;
  • compress : compresser les anciens logs ;
  • create : recréer le fichier avec les bons droits ;
  • sharedscripts : exécuter le script postrotate une seule fois pour plusieurs logs ;
  • postrotate : commandes exécutées après rotation.

Logrotate permet justement de définir des règles par fichier ou groupe de fichiers, puis d’exécuter des scripts avant ou après rotation.

S’assurer que MariaDB réouvre ses fichiers de logs

Après rotation, MariaDB doit réouvrir ses fichiers de logs. Sinon, il peut continuer à écrire dans l’ancien fichier renommé, ou ne plus écrire là où vous l’attendez.

La commande habituelle est :

mysqladmin flush-logs

ou avec MariaDB :

mariadb-admin flush-logs

Si votre script utilise mysqladmin, vérifiez que la commande existe :

command -v mysqladmin
command -v mariadb-admin

Et vérifiez que l’utilisateur utilisé par le script a les droits nécessaires pour exécuter flush-logs.

MariaDB moderne : mysqladmin ou mariadb-admin ?

Sur les installations MariaDB modernes, l’outil peut s’appeler mariadb-admin, même si mysqladmin reste parfois disponible comme alias ou compatibilité.

Vérifiez :

ls -l /usr/bin/mysqladmin /usr/bin/mariadb-admin 2>/dev/nullCode language: JavaScript (javascript)

Si le script logrotate fourni par le paquet appelle un binaire qui n’existe plus, vous pouvez obtenir d’autres erreurs. Dans ce cas, il faut adapter le script ou installer le paquet client adéquat.

On a déjà vu des cas où la transition MySQL/MariaDB changeait les noms ou options utilisés par les scripts, notamment autour de mysqld et mariadbd. Les migrations MySQL/MariaDB aiment beaucoup les petits détails qui cassent à 6 h 25, c’est leur sens du théâtre.

Cron ou systemd timer ?

Historiquement, logrotate tourne via /etc/cron.daily/logrotate. Sur certaines distributions modernes, il peut aussi être lancé via un timer systemd.

Vérifiez cron :

ls -l /etc/cron.daily/logrotate

Vérifiez systemd :

systemctl list-timers | grep -i logrotate
systemctl status logrotate.timer --no-pager
systemctl status logrotate.service --no-pagerCode language: PHP (php)

Le message d’erreur peut donc arriver par email cron, dans les logs systemd, ou les deux selon votre distribution.

Diagnostic rapide complet

Voici la séquence que j’utiliserais aujourd’hui :

# Identifier le fichier logrotate concerné.
ls -l /etc/logrotate.d/ | grep -E 'mysql|maria'

# Voir son contenu.
sudo sed -n '1,200p' /etc/logrotate.d/mysql-server 2>/dev/null
sudo sed -n '1,200p' /etc/logrotate.d/mariadb 2>/dev/null

# Tester ce que retourne my_print_defaults.
my_print_defaults --mysqld | grep 'pid-file'
my_print_defaults --mysqld | grep -oP "pid-file=\K[^$]+"

# Chercher les directives pid-file en double.
grep -R "^[[:space:]]*pid-file" /etc/mysql /etc/my.cnf 2>/dev/null

# Tester logrotate en debug.
sudo logrotate -d /etc/logrotate.conf

# Tester uniquement le fichier MySQL/MariaDB.
sudo logrotate -d /etc/logrotate.d/mysql-server 2>/dev/null
sudo logrotate -d /etc/logrotate.d/mariadb 2>/dev/null

# Vérifier les commandes admin SQL.
command -v mysqladmin
command -v mariadb-admin

# Voir les timers systemd.
systemctl list-timers | grep -i logrotateCode language: PHP (php)

Correction rapide

Si la cause est bien une commande grep qui retourne plusieurs chemins de PID, corrigez la ligne :

if [ -f `my_print_defaults --mysqld | grep -oP "pid-file=\K[^$]+"` ]; thenCode language: CSS (css)

en :

if [ -f `my_print_defaults --mysqld | grep -oPm1 "pid-file=\K[^$]+"` ]; thenCode language: CSS (css)

Puis testez :

sudo logrotate -d /etc/logrotate.d/mysql-server

ou :

sudo logrotate -d /etc/logrotate.d/mariadb

Si le mode debug ne remonte plus l’erreur, la prochaine exécution cron ne devrait plus envoyer d’email.

Mémo rapide

# Erreur.
logrotate_script: 3: [: /var/run/mysqld/mysqld.pid: unexpected operator

# Fichier à vérifier.
sudo nano /etc/logrotate.d/mysql-server
sudo nano /etc/logrotate.d/mariadb

# Ligne problématique.
if [ -f `my_print_defaults --mysqld | grep -oP "pid-file=\K[^$]+"` ]; then

# Correction.
if [ -f `my_print_defaults --mysqld | grep -oPm1 "pid-file=\K[^$]+"` ]; then

# Vérifier ce que retourne la commande.
my_print_defaults --mysqld | grep -oP "pid-file=\K[^$]+"

# Chercher les pid-file multiples.
grep -R "^[[:space:]]*pid-file" /etc/mysql /etc/my.cnf 2>/dev/null

# Tester logrotate sans rien modifier.
sudo logrotate -d /etc/logrotate.conf
sudo logrotate -d /etc/logrotate.d/mysql-server
sudo logrotate -d /etc/logrotate.d/mariadb

# Forcer une rotation si nécessaire.
sudo logrotate -f /etc/logrotate.d/mysql-server
sudo logrotate -f /etc/logrotate.d/mariadbCode language: PHP (php)

Conclusion

L’erreur logrotate_script: 3: [: /var/run/mysqld/mysqld.pid: unexpected operator vient généralement d’un test shell qui reçoit trop d’arguments, souvent parce que my_print_defaults retourne plusieurs valeurs pid-file.

La correction rapide consiste à limiter grep à la première correspondance :

grep -oPm1 "pid-file=\K[^$]+"Code language: JavaScript (javascript)

Mais le bon réflexe consiste aussi à vérifier s’il existe plusieurs directives pid-file dans la configuration MySQL/MariaDB, surtout après une migration ou un déplacement du datadir.

Enfin, testez toujours logrotate en mode debug avec logrotate -d avant d’attendre le prochain cron. Cela évite de découvrir le lendemain matin que le serveur vous a encore écrit une lettre d’amour passive-agressive.

Gravatar for Matt Biscay

Je suis Matt Biscay, développeur WordPress & WooCommerce certifié chez Codeable, administrateur système et enseignant.

J’aide les entreprises à créer, optimiser et fiabiliser leurs sites WordPress avec une approche technique propre : performance, sécurité, maintenance, développement sur mesure et résolution de problèmes complexes.

Sur Skyminds, je partage des tutoriels WordPress, WooCommerce, Linux et administration système, avec des solutions testées sur des cas réels et pensées pour durer.

Découvrez mes services WordPress et WooCommerce.

Opinions