Schleifen ermöglichen in Ansible den gleichen Task auf eine Liste von Elementen komfortabel auszuführen.

Dateien anlegen
Nehmen wir mal an, wir wollen in einem Play eine Anzahl von Testdateien erzeugen. Dies lässt sich erreichen, in dem wir für jedes Verzeichnis eine ansible.builtin.file Task in das Play hinein schreiben. Das ist allerdings unschön und bläht das Play unnötig auf. Zunächst brauchen wir einen subtask, um das test Verzeichnis anzulegen und die Testdateien aus einem vorhergehenden Lauf zu löschen.Dabei habe ich auch selbst gelernt, dass du nicht mehrere gleiche Tasks in ein Play untereinander schreiben kannst. Ansible nimmt dann immer nur den letzten. Daher spalte ich das in zwei separate Tasks auf.
Datei: subtasks/setuploop.yml
---
- name: create test dir
ansible.builtin.file:
path: test
state: directory
- name: delete test files
ansible.builtin.file:
path: "test/*"
state: absent
Das eigentliche Play muss ebenso strukturiert sein. Es soll die vier Dateien
- test1.json
- test1.txt
- test2.txt
- test3.txt
- test4.dat
im Testverzeichnis erzeugen. Dies entspricht einem touch
Kommando in der bash.
Datei: roles/noloop/tasks/main.yml
- include: ../subtasks/setuploop.yml
- name: erzeuge test1.txt
ansible.builtin.file:
path: "test/test1.txt"
state: touch
- name: erzeuge test1.json
ansible.builtin.file:
path: "test/test1.json"
state: touch
- name: erzeuge test2.txt
ansible.builtin.file:
path: "test/test2.txt"
state: touch
- name: erzeuge test3.txt
ansible.builtin.file:
path: "test/test3.txt"
state: touch
- name: erzeuge testr4.dat
ansible.builtin.file:
path: "test/test4.dat"
state: touch
Der Lauf des Playbook läuft erwartungsgemäß

Allerdings wiederholt das Play den gleichen Task mehrfach mit unterschiedlichen Werten. Das ist unschön und bläht das Play nur auf. Eleganter funktioniert das mit einer Schleife
with_items: Schleife
Eine Schleife bekommt in Ansible meist eine Liste übergeben. Für jeden Eintrag in dieser Liste wird der Task, an den die Schleife gebunden ist, wiederholt.
Datei: roles/with_items/tasks/main.yml
- include: ../subtasks/setuploop.yml
- name: erzeuge Testdateien
ansible.builtin.file:
path: "test/{{ item }}"
state: touch
with_items:
- test1.txt
- test1.json
- test2.txt
- test3.txt
- test4.dat
Allein durch die YML Syntax erkennst du schon, der einzige ansible.builtin.file Task in dem Play wird mit einer with_loop:
Schleife versehen, die die Liste der Dateien enthält. Die Schleife legt den aktuellen Dateinamen in einer Variable item
ab, die nur innerhalb der Schleifenkonstrukts gültig ist.

Dieser Schleifentyp war der erste, den ich kennengelernt habe. Inzwischen gilt er als veraltet und wird möglicherweise irgendwann nicht mehr unterstützt. Ich wollte ihn nur der Vollständigkeit erwähnen.
loop: Schleife
Die modernere und zukunftssichere Schleife ist die loop: Schleife, die mit Ansible Version 2.5 eingeführt wurde. Das Play unterscheidet sich kaum vom Vorgänger.
Datei: roles/loop/tasks/main.yml
- include: ../subtasks/setuploop.yml
- name: erzeuge Testdateien
ansible.builtin.file:
path: "test/{{ item }}"
state: touch
loop:
- test1.txt
- test1.json
- test2.txt
- test3.txt
- test4.dat
Du verwendest einfach loop: statt with_items:, alles andere bleibt gleich.
externe Liste
Unschön ist jetzt nur noch, dass die Dateinamen fest im Play eingebunden sind. Wir können sie aber in eine externe Datei auslagern.
Datei loop_files.yml
testfiles:
- test1.txt
- test1.json
- test2.txt
- test3.txt
- test4.dat
Im Playbook lesen wir diese Datei mit Hilfe von vars_files:
ein
Datei: loop.yml
- hosts: "{{ target }}"
vars_files:
- loop_files.yml
gather_facts: false
remote_user: pi
roles:
- loop
Im Play können wir jetzt die feste Liste aus der loop:
löschen und über die Liste testfiles
aus der externen Datei laufen.
Datei: roles/loop/tasks/main.yml
- include: ../subtasks/setuploop.yml
- name: erzeuge Testdateien
ansible.builtin.file:
path: "test/{{ item }}"
state: touch
loop: "{{ testfiles }}"
with_lines:
Die with_lines:
Schleife iteriert über alle Einträge einer Liste, die zeilenweise im with_lines
Statement definiert wird. In diesem Beispiel wird ein find
Kommando aufgerufen, dass alle *.conf Dateien in /etc sucht. Der Output von find landet dann in der items
Variable. Als zweites wird ein ls auf
Homeverzeichnis aufgerufen.
Date: roles/with_lines/tasks/main.yml
- name: "Beispiel: Alle .conf Dateien in /etc auflisten"
debug:
msg: "Gefundene Konfigurationsdatei: {{ item }}"
with_lines:
- "find /etc -name '*.conf' -type f 2>/dev/null | head -5"
- ls ~


with_fileglob:
with_fileglob:
baut eine Schleife aufgrund einer regular Expression auf, so dass über eine Liste von Dateien gelaufen werden kann. Damit lässt sich das find von oben so schreiben. Die regular expression /etc/*.conf baut eine Liste aller Dateien in /etc, die auf .conf enden auf.
Datei: roles/with_fileglob/tasks/main.yml
- name: "Beispiel: Alle .conf Dateien in /etc auflisten"
debug:
msg: "Gefundene Konfigurationsdatei: {{ item }}"
with_fileglob:
- /etc/*.conf

with_sequence:
Mit with_sequence
kannst du eine Schleife mit Zähler bauen. Dazu gibst du Start- start= und Endwert end= als Parameter an. Mit dem Parameter format= wird ein String formatiert, in den der aktuelle Zähler eingesetzt wird.
Datei: roles/with_sequence/tasks/main.yml
- name: "Beispiel: Zählschleife"
debug:
msg: "User {{ item }}"
with_sequence: start=1 end=5 format="user%02d"
Im Beispiel zählt die Schleife von 1 bis 5 und erzeugt einen String „userxx“. Der aktuelle Wert des Zählers wird immer zweistellig %02d
angehängt.
with_dict:
Die with_dict:
Schleife ist with_lines:
sehr ähnlich, mit dem Unterschied, dass jede Zeile einen Dictionaryeintrag enthält über den iteriert wird.
Datei: roles/with_dict/tasks/main.yml
- name: Benutzer mit bestimmten Shells anlegen
hosts: localhost
become: yes
vars:
users:
alice: /bin/bash
bob: /bin/zsh
carol: /bin/sh
tasks:
- name: Benutzer anlegen
user:
name: "{{ item.key }}"
shell: "{{ item.value }}"
with_dict: "{{ users }}"
In diesem Beispiel wird über ein Dictionary, in dem zu einem Benutzernamen eine Shell als Wert zugeordnet ist. So etwas in der Art findest du auch in der /etc/passwd wieder.
retries:
Nicht selten kommt es vor, dass ein Task warten soll, bis ein bestimmter Zustand eingetreten ist.
Ein häufiger Anwendungsfall ist, zu warten, bis ein Dockercontainer gestartet ist. Dafür steht uns die retries:
Schleife zur Verfügung. Mit ihr können wir eine bestimmte Anzahl von Wiederholungen durchlaufen. Das Schlüsselwort until:
bestimmt den Zustand, bei dem die Schleifenbedingung erfüllt ist und die Schleife beendet wird.
Datei: roles/retries/tasks/main.yml
- name: Warte bis Container läuft
docker_container_info:
name: homeassistant
register: container_info
until: container_info.container.State.Status == "running"
retries: 20
delay: 3
# Wartet bis Container vollständig gestartet ist
Dieses Beispiel prüft in maximal 20 Wiederholungen, ob der Container homeassistant läuft. Dabei wird 3 immer 3 Sekunden nach jedem Schleifendurchlauf gewartet(delay: 3
). docker_container_info fragt den Zustand des Containers „homeassistant“ ab und legt das Ergebnis in der Variable container_info
ab. Diese können wir dann in until:
als Bedingung benutzen.
Wichtig bei dieser Schleifenart ist, dass until:
eine Bedingung enthält, die auch wirklich eintreten kann. Außerdem solltest du retries:
nicht zu hoch ansetzen.
Ich wechsle jetzt auf meinen Homeassistant Raspi, um das Playbook zu testen.In einer Login-Shell teste ich das Playbook, in einer anderen stoppe ich zunächst mal den homeassistant Container.

Nachdem ich das Playbook gestartet habe, fahre ich den Container wieder hoch. Im Bild siehst du, dass es erst nach drei Versuchen weiterläuft.
loop: oder with_?
Alle Schleifen, die mit with_ beginnen, gelten inzwischen als veraltet und sollten nicht mehr eingesetzt werden. Sie lassen sich aber in der Regel durch eine loop: ersetzen.
So lässt sich das with_fileglob: von oben wie folgt in eine loop: umschreiben.
loop: "{{ query('fileglob', '/etc/*.conf') }}"
Für die anderen Schleifenarten habe ich die loop: Syntax tabellarisch zusammengestellt, ohne sie näher auszuprobieren.
with_ Schleife | loop: Syntax | Bemerkung |
---|---|---|
with_items | loop: „{{ items }}“ | 1:1 Ersetzung |
with_list | loop: „{{ mylist }}“ | mehr oder weniger identisch zu with_items |
with_dict | loop: „{{ mydict }}“ | |
with_nested | loop: „{{ mylist1 }}“ | |
with_indexed_items | loop: „{{ items }}““ | |
with_together | loop: „{{ list1 }}“ | |
with_sequence | loop: „{{ query(’sequence‘, ’start=0 end=4 stride=1′) }}“ | Nutzt Lookup-Plugin sequence |
with_fileglob | loop: „{{ query(‚fileglob‘, ‚/etc/*.conf‘) }}“ | |
with_lines | loop: „{{ lookup(‚pipe‘, ‚command‘, wantlist=True) }}“ | Nutzt Lookup-Plugin pipe |
with_files | loop: „{{ lookup(‚file‘, ‚myfile.txt‘) }}“ | Für Inhalte einzelner Dateien |
with_random_choice | loop: „{{ [1,2,3] }}“ |
Fazit
Mit Schleifen kannst du wiederkehrende Tasks in Ansible einfach wiederholen, ohne zusätzlichen ohne schreiben zu müssen. Das hält das Play schlank und übersichtlich.
Bei neuen Playbooks solltest du aber auf with_*
Schleifen verzichten und besser auf die moderne loop:
Variante zurückgreifen.
Ich habe in diesem Post die Playbooks weggelassen, da sie eh immer nur die eine jeweilige Rolle des Play besitzen. Du findest sie aber im Git-Repo.