
Le module ansible.builtin.replace substitue toutes les occurrences d'un motif regex dans un fichier — sans toucher au reste de la ligne ni du fichier. C'est l'outil pour les changements transversaux : mettre à jour une URL d'API, changer un nom d'hôte qui apparaît à 15 endroits, propager une nouvelle version. Public visé : intermédiaires Ansible. Cette page distingue replace de lineinfile (différence cruciale), montre les patterns d'usage avec before et after pour limiter la zone de remplacement, et liste les pièges classiques (regex gourmande, idempotence cassée, capture group mal échappée).
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- Remplacer un motif regex par une nouvelle valeur dans un fichier.
- Limiter la zone de remplacement avec
before:etafter:. - Distinguer
replace(modifie un motif) delineinfile(modifie une ligne entière). - Utiliser des groupes de capture pour préserver une partie du motif d'origine.
- Diagnostiquer une idempotence cassée par un double run.
Prérequis
Section intitulée « Prérequis »- Bases Ansible (cf. Premiers pas).
- Notions de regex Python (les regex sont du
rePython, pas du POSIX strict). - Un fichier cible avec des motifs à remplacer (config, JSON, YAML…).
L'usage de base — substituer toutes les occurrences
Section intitulée « L'usage de base — substituer toutes les occurrences »- name: Mettre à jour l'URL de l'API dans tous les services ansible.builtin.replace: path: /etc/myapp/services.yml regexp: 'https://api-old\.example\.com' replace: 'https://api.example.com'Comportement :
- Toutes les occurrences du motif
https://api-old.example.comdans le fichier sont remplacées. - Si aucune occurrence n'est trouvée → task
ok(idempotent). - Si le fichier n'existe pas → task échoue (pas de
create:dansreplace).
C'est le contraste fondamental avec lineinfile : replace ne sait pas ajouter une ligne, il modifie ce qui existe déjà.
La différence cruciale avec lineinfile
Section intitulée « La différence cruciale avec lineinfile »C'est la confusion la plus fréquente. Mémorisez ce tableau :
| Aspect | lineinfile | replace |
|---|---|---|
| Granularité | Une ligne entière | Un motif (peut être partie de ligne) |
| Cas d'usage | Ajouter/modifier/supprimer une ligne identifiée | Substituer un motif partout dans le fichier |
| Si motif absent | Ajoute la ligne (par défaut) | Ne fait rien (idempotent) |
| Multi-occurrences | Une seule ligne traitée par run | Toutes les occurrences traitées |
| Multi-lignes | Pas supporté | Supporté via (?s) ou multiline: false |
| Regex style | Python re simplifié | Python re complet |
Règle de décision :
- « Je veux que cette ligne existe avec cette valeur » →
lineinfile. - « Je veux remplacer ce motif partout » →
replace.
Limiter la zone — before et after
Section intitulée « Limiter la zone — before et after »Pour ne remplacer que dans une section d'un fichier, utiliser before (limite supérieure) et after (limite inférieure) :
- name: Activer SSL UNIQUEMENT dans la section [server] ansible.builtin.replace: path: /etc/myapp/config.ini after: '^\[server\]' # commence à matcher après cette ligne before: '^\[' # arrête au début de la section suivante regexp: '^ssl_enabled\s*=\s*false' replace: 'ssl_enabled = true'Le bloc actif est entre after (exclu) et before (exclu). Si une seule des deux est définie, l'autre prend respectivement le début ou la fin du fichier.
Groupes de capture pour préserver une partie
Section intitulée « Groupes de capture pour préserver une partie »Comme avec lineinfile, on peut utiliser des backrefs Python \g<1>, \g<2> pour conserver une partie du motif :
- name: Bumper la version d'un paquet npm dans package.json ansible.builtin.replace: path: /opt/app/package.json regexp: '"express":\s*"\^?\d+\.\d+\.\d+"' replace: '"express": "^4.18.2"'
# Ou avec capture pour préserver le préfixe- name: Forcer toutes les URL http vers https en gardant le path ansible.builtin.replace: path: /etc/myapp/links.txt regexp: 'http://(api\.example\.com)(/[^\s]*)?' replace: 'https://\g<1>\g<2>'Avec replace, contrairement à lineinfile, pas besoin de backrefs: true — c'est le comportement par défaut.
Validation — validate: aussi disponible
Section intitulée « Validation — validate: aussi disponible »Comme lineinfile, le module accepte un validator pour ne pas écrire un fichier syntaxiquement cassé :
- name: Désactiver SELinux uniquement si le validator passe ansible.builtin.replace: path: /etc/selinux/config regexp: '^SELINUX=\w+' replace: 'SELINUX=permissive' validate: "/usr/sbin/sestatus -v %s || /bin/true"%s est remplacé par le chemin temporaire candidat. Le validator doit retourner 0 pour qu'Ansible commit le fichier.
Multi-lignes — (?s) ou (?m)
Section intitulée « Multi-lignes — (?s) ou (?m) »Par défaut, le regex Python . ne matche pas les nouvelles lignes. Pour des remplacements multi-lignes :
# Remplacer un bloc multi-lignes (?s) = DOTALL- name: Réinitialiser une section JSON ansible.builtin.replace: path: /etc/myapp/config.json regexp: '(?s)"servers":\s*\[.*?\]' replace: '"servers": []'(?s) active le mode DOTALL — . matche aussi \n. Combiné avec *? (non gourmand), on peut remplacer un bloc complet sans risquer de manger trop loin.
Idempotence et double run
Section intitulée « Idempotence et double run »Comme tout module modificateur de fichier, replace doit être idempotent :
# 1er run — `changed` si le motif est trouvé et remplacéansible-playbook update-urls.yml
# 2e run immédiat — DOIT être `ok` (le motif a disparu après le 1er run)ansible-playbook update-urls.ymlSi le 2e run rapporte encore changed, c'est que la substitution n'a pas fait disparaître le motif — typiquement parce que regexp matche aussi le résultat de replace :
# ❌ NON-IDEMPOTENT — chaque run ajoute encore "https://"- ansible.builtin.replace: path: /etc/myapp/links.txt regexp: 'http' replace: 'https://http'
# ✓ IDEMPOTENT — la regex ne match plus après remplacement- ansible.builtin.replace: path: /etc/myapp/links.txt regexp: 'http://' replace: 'https://'Règle de design : la regex doit matcher uniquement la valeur d'origine, pas la valeur d'arrivée.
Cas d'usage typiques RHCE / RHCSA
Section intitulée « Cas d'usage typiques RHCE / RHCSA »Quelques patterns d'examen à connaître :
# Désactiver SELinux à chaud (config avant reboot)- ansible.builtin.replace: path: /etc/selinux/config regexp: '^SELINUX=\w+' replace: 'SELINUX=disabled'
# Changer le default runlevel systemd- ansible.builtin.replace: path: /etc/systemd/system/default.target regexp: 'multi-user\.target' replace: 'graphical.target'
# Remplacer toutes les références à un domaine déprécié- ansible.builtin.replace: path: /etc/hosts regexp: 'old\.corp\.local' replace: 'new.corp.local'
# Forcer un timeout dans tous les fichiers d'un dossier (loop)- ansible.builtin.replace: path: "{{ item }}" regexp: 'timeout\s*=\s*\d+' replace: 'timeout = 30' loop: - /etc/myapp/conf.d/db.conf - /etc/myapp/conf.d/cache.conf - /etc/myapp/conf.d/api.confPièges courants
Section intitulée « Pièges courants »| Symptôme | Cause | Solution |
|---|---|---|
changed à chaque run | La regex matche aussi le résultat | Concevoir la regex pour ne plus matcher après remplacement |
| Trop de remplacements | Regex gourmande (.*) | Utiliser non-gourmand (.*?) ou ancres ^/$ |
| Aucun remplacement alors que le motif existe | Caractères spéciaux non échappés | Échapper ., \, (, ), [, ], etc. |
replace ne traverse pas les newlines | Mode DOTALL non activé | Préfixer la regex avec (?s) |
| Service pas reload après modification | notify: manquant | Ajouter notify: + handler |
| Fichier inexistant — task fail | replace ne crée pas | Utiliser copy avec force: false ou template à la place |
| Encoding cassé sur fichier non-UTF8 | replace lit en UTF-8 par défaut | Pour les fichiers latin1 ou autre, considérer script ou command avec sed |
À retenir
Section intitulée « À retenir »replacesubstitue un motif partout dans le fichier — pas un seul cas.- N'ajoute jamais de ligne — utiliser
lineinfilepour ça. beforeetafterlimitent la zone de remplacement à une section identifiée par regex.(?s)active DOTALL pour les remplacements multi-lignes.- Toujours utiliser
*?non gourmand sauf intention contraire. - La regex ne doit plus matcher après remplacement — sinon idempotence cassée.
validate:disponible comme danslineinfilepour les fichiers critiques.- Pas de
create:— le fichier doit exister.
Pratiquer dans le lab
Section intitulée « Pratiquer dans le lab »Cette page a un lab d'accompagnement : labs/modules-fichiers/replace/ dans stephrobert/ansible-training.
Challenge — sur db1.lab, à partir d'un /etc/myapp.conf multi-sections ([server], [client]) :
- Substituer toute URL
http://api-old.example.comenhttps://api.example.com(substitution simple). - Activer
ssl_enabled = trueuniquement dans la section[server](before:/after:pour limiter la zone). - Bumper la valeur de
port=de8080à8443via groupe de capture (\g<1>).
Validation pytest+testinfra :
ansible-playbook labs/modules-fichiers/replace/challenge/solution.ymlansible-playbook labs/modules-fichiers/replace/challenge/solution.yml # 2e run = changed=0pytest -v labs/modules-fichiers/replace/challenge/tests/Les tests vérifient la substitution effective, le maintien de ssl_enabled=false dans [client] (pour valider le before/after), et l'idempotence sur double run.