Aller au contenu
Infrastructure as Code medium

Module Ansible replace : remplacer un motif dans un fichier existant

11 min de lecture

Logo Ansible

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).

  • Remplacer un motif regex par une nouvelle valeur dans un fichier.
  • Limiter la zone de remplacement avec before: et after:.
  • Distinguer replace (modifie un motif) de lineinfile (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.
  • Bases Ansible (cf. Premiers pas).
  • Notions de regex Python (les regex sont du re Python, 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.com dans 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: dans replace).

C'est le contraste fondamental avec lineinfile : replace ne sait pas ajouter une ligne, il modifie ce qui existe déjà.

C'est la confusion la plus fréquente. Mémorisez ce tableau :

Aspectlineinfilereplace
GranularitéUne ligne entièreUn motif (peut être partie de ligne)
Cas d'usageAjouter/modifier/supprimer une ligne identifiéeSubstituer un motif partout dans le fichier
Si motif absentAjoute la ligne (par défaut)Ne fait rien (idempotent)
Multi-occurrencesUne seule ligne traitée par runToutes les occurrences traitées
Multi-lignesPas supportéSupporté via (?s) ou multiline: false
Regex stylePython 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.

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.

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.

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.

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.

Comme tout module modificateur de fichier, replace doit être idempotent :

Fenêtre de terminal
# 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.yml

Si 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.

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.conf
SymptômeCauseSolution
changed à chaque runLa regex matche aussi le résultatConcevoir la regex pour ne plus matcher après remplacement
Trop de remplacementsRegex gourmande (.*)Utiliser non-gourmand (.*?) ou ancres ^/$
Aucun remplacement alors que le motif existeCaractères spéciaux non échappésÉchapper ., \, (, ), [, ], etc.
replace ne traverse pas les newlinesMode DOTALL non activéPréfixer la regex avec (?s)
Service pas reload après modificationnotify: manquantAjouter notify: + handler
Fichier inexistant — task failreplace ne crée pasUtiliser copy avec force: false ou template à la place
Encoding cassé sur fichier non-UTF8replace lit en UTF-8 par défautPour les fichiers latin1 ou autre, considérer script ou command avec sed
  • replace substitue un motif partout dans le fichier — pas un seul cas.
  • N'ajoute jamais de ligne — utiliser lineinfile pour ça.
  • before et after limitent 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 dans lineinfile pour les fichiers critiques.
  • Pas de create: — le fichier doit exister.

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]) :

  1. Substituer toute URL http://api-old.example.com en https://api.example.com (substitution simple).
  2. Activer ssl_enabled = true uniquement dans la section [server] (before: / after: pour limiter la zone).
  3. Bumper la valeur de port= de 8080 à 8443 via groupe de capture (\g<1>).

Validation pytest+testinfra :

Fenêtre de terminal
ansible-playbook labs/modules-fichiers/replace/challenge/solution.yml
ansible-playbook labs/modules-fichiers/replace/challenge/solution.yml # 2e run = changed=0
pytest -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.

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracing. Aujourd'hui, ce site ne couvre même pas mes frais d'hébergement, d'électricité, de matériel, de logiciels, mais surtout de cafés.

Un soutien régulier, même symbolique, m'aide à garder ces ressources gratuites et à continuer de produire des guides de qualité. Merci pour votre appui.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn