环境
- 管理节点:Ubuntu 22.04
- 控制节点:CentOS 8
- Ansible:2.15.6
循环的方法
loop
with_<lookup>
until
用这几种方式都可以实现循环。其中, loop
是推荐的用法,在很多时候能够替换 with_<lookup>
。
loop
和 with_<lookup>
with_<lookup>
使用了lookup插件,比如 with_items
使用的是 items
lookup。(注:可参见我另一篇文档。)
loop
等同于 with_list
。注意, loop
是作用在list上的,如果用在字符串上会报错。
yaml
---
- hosts: all
tasks:
- name: task1
debug:
msg: "{{ item }}"
loop: "{{ ['aaa', 'bbb', 'ccc'] }}"
- name: task2
debug:
msg: "{{ item }}"
with_list: "{{ ['aaa', 'bbb', 'ccc'] }}"
- name: task3
debug:
msg: "{{ item }}"
with_items: "{{ ['aaa', 'bbb', 'ccc'] }}"
运行结果如下:
powershell
TASK [task1] ***************************************************************************************
ok: [192.168.1.55] => (item=aaa) => {
"msg": "aaa"
}
ok: [192.168.1.55] => (item=bbb) => {
"msg": "bbb"
}
ok: [192.168.1.55] => (item=ccc) => {
"msg": "ccc"
}
TASK [task2] ***************************************************************************************
ok: [192.168.1.55] => (item=aaa) => {
"msg": "aaa"
}
ok: [192.168.1.55] => (item=bbb) => {
"msg": "bbb"
}
ok: [192.168.1.55] => (item=ccc) => {
"msg": "ccc"
}
TASK [task3] ***************************************************************************************
ok: [192.168.1.55] => (item=aaa) => {
"msg": "aaa"
}
ok: [192.168.1.55] => (item=bbb) => {
"msg": "bbb"
}
ok: [192.168.1.55] => (item=ccc) => {
"msg": "ccc"
}
可见,本例中 loop
, with_list
, with_items
的效果是一样的。
但是对于嵌套的list, loop
和 with_items
并不是完全等同的。
yaml
......
- name: task4
debug:
msg: "{{ item }}"
loop: "{{ ['aaa', ['bbb', ['ddd', 'eee']], 'ccc'] }}"
- name: task5
debug:
msg: "{{ item }}"
with_items: "{{ ['aaa', ['bbb', ['ddd', 'eee']], 'ccc'] }}"
......
运行结果如下:
powershell
TASK [task4] ***************************************************************************************
ok: [192.168.1.55] => (item=aaa) => {
"msg": "aaa"
}
ok: [192.168.1.55] => (item=['bbb', ['ddd', 'eee']]) => {
"msg": [
"bbb",
[
"ddd",
"eee"
]
]
}
ok: [192.168.1.55] => (item=ccc) => {
"msg": "ccc"
}
TASK [task5] ***************************************************************************************
ok: [192.168.1.55] => (item=aaa) => {
"msg": "aaa"
}
ok: [192.168.1.55] => (item=bbb) => {
"msg": "bbb"
}
ok: [192.168.1.55] => (item=['ddd', 'eee']) => {
"msg": [
"ddd",
"eee"
]
}
ok: [192.168.1.55] => (item=ccc) => {
"msg": "ccc"
}
可见:
loop
只针对最外层的list,不管是否有嵌套。with_items
则是解开了第一层嵌套的list。这个行为比较诡异,要么就不要管嵌套,要么就全部处理,为什么只处理第一层嵌套呢?
实际上,对于 loop
,可用 flatten
filter来指定解开嵌套:
(注:flatten是扁平化的意思,这里的扁平化和Java8里扁平化流的概念类似,即把层次结构转换为线性结构)
yaml
......
- name: task6
debug:
msg: "{{ item }}"
loop: "{{ ['aaa', ['bbb', ['ddd', 'eee']], 'ccc'] | flatten }}"
- name: task7
debug:
msg: "{{ item }}"
loop: "{{ ['aaa', ['bbb', ['ddd', 'eee']], 'ccc'] | flatten(levels=1) }}"
......
运行结果如下:
powershell
TASK [task6] ***************************************************************************************
ok: [192.168.1.55] => (item=aaa) => {
"msg": "aaa"
}
ok: [192.168.1.55] => (item=bbb) => {
"msg": "bbb"
}
ok: [192.168.1.55] => (item=ddd) => {
"msg": "ddd"
}
ok: [192.168.1.55] => (item=eee) => {
"msg": "eee"
}
ok: [192.168.1.55] => (item=ccc) => {
"msg": "ccc"
}
TASK [task7] ***************************************************************************************
ok: [192.168.1.55] => (item=aaa) => {
"msg": "aaa"
}
ok: [192.168.1.55] => (item=bbb) => {
"msg": "bbb"
}
ok: [192.168.1.55] => (item=['ddd', 'eee']) => {
"msg": [
"ddd",
"eee"
]
}
ok: [192.168.1.55] => (item=ccc) => {
"msg": "ccc"
}
可见, flatten
默认会处理所有嵌套,也可以通过 levels
选项,指定处理几层嵌套。
由于 with_items
处理一层嵌套,所以, with_items
就相当于 loop
指定了 flatten(levels=1)
。在本例中,task5和task7的运行结果是一样的。
需要使用 lookup
的循环,多使用 with_<lookup>
语句,而不是 loop
语句。比如:
yaml
---
- hosts: all
tasks:
- name: task1
debug:
msg: "{{ item }}"
loop: "{{ lookup('fileglob', '/tmp/*.txt', wantlist=True) }}"
- name: task2
debug:
msg: "{{ item }}"
with_fileglob: "/tmp/*.txt"
运行结果如下:
powershell
TASK [task1] ***************************************************************************************
ok: [192.168.1.55] => (item=/tmp/b.txt) => {
"msg": "/tmp/b.txt"
}
ok: [192.168.1.55] => (item=/tmp/a.txt) => {
"msg": "/tmp/a.txt"
}
TASK [task2] ***************************************************************************************
ok: [192.168.1.55] => (item=/tmp/b.txt) => {
"msg": "/tmp/b.txt"
}
ok: [192.168.1.55] => (item=/tmp/a.txt) => {
"msg": "/tmp/a.txt"
}
显然,此处使用 with_fileglob
比使用 loop
要简洁。
注: fileglob
获取指定目录下符合条件的文件名(不包含子目录)。
循环的种类
简单list
yaml
---
- hosts: all
vars:
var1: ['aaa', 'bbb', 'ccc']
tasks:
- name: task1 # list常量
debug:
msg: "{{ item }}"
loop: "{{ ['aaa', 'bbb', 'ccc'] }}"
# loop: ['aaa', 'bbb', 'ccc'] 可以简写
- name: task2 # list常量
debug:
msg: "{{ item }}"
loop:
- aaa # 引号可以省略
- "bbb"
- "ccc"
- name: task3 # list变量
debug:
msg: "{{ item }}"
loop: "{{ var1 }}"
运行结果如下:
powershell
TASK [task1] ***************************************************************************************
ok: [192.168.1.55] => (item=aaa) => {
"msg": "aaa"
}
ok: [192.168.1.55] => (item=bbb) => {
"msg": "bbb"
}
ok: [192.168.1.55] => (item=ccc) => {
"msg": "ccc"
}
TASK [task2] ***************************************************************************************
ok: [192.168.1.55] => (item=aaa) => {
"msg": "aaa"
}
ok: [192.168.1.55] => (item=bbb) => {
"msg": "bbb"
}
ok: [192.168.1.55] => (item=ccc) => {
"msg": "ccc"
}
TASK [task3] ***************************************************************************************
ok: [192.168.1.55] => (item=aaa) => {
"msg": "aaa"
}
ok: [192.168.1.55] => (item=bbb) => {
"msg": "bbb"
}
ok: [192.168.1.55] => (item=ccc) => {
"msg": "ccc"
}
复杂list
yaml
---
- hosts: all
vars:
var1: [{name: "Tom", age: 20}, {name: "Jerry", age: 18}]
tasks:
- name: task1
debug:
msg: "Name: {{ item.name }}. Age: {{ item.age }}"
loop: "{{ [{ 'name': 'Tom', 'age': 20 }, { 'name': 'Jerry', 'age': 18 }] }}"
#loop: "{{ [{ name: 'Tom', age: 20 }, { name: 'Jerry', age: 18 }] }}" # 报错!说name未定义
#loop: [{ name: 'Tom', age: 20 }, { name: 'Jerry', age: 18 }] # OK
#loop: [{ 'name': 'Tom', 'age': 20 }, { 'name': 'Jerry', 'age': 18 }] # OK
- name: task2
debug:
msg: "Name: {{ item.name }}. Age: {{ item.age }}"
loop:
- { name: "Tom", age: 20 }
- { name: "Jerry", age: 18 }
- name: task3
debug:
msg: "Name: {{ item.name }}. Age: {{ item.age }}"
loop: "{{ var1 }}"
可以看到,对于key要不要加引号,行为好像有点诡异,最好还是加上吧。
运行结果如下:
powershell
TASK [task1] ***************************************************************************************
ok: [192.168.1.55] => (item={'name': 'Tom', 'age': 20}) => {
"msg": "Name: Tom. Age: 20"
}
ok: [192.168.1.55] => (item={'name': 'Jerry', 'age': 18}) => {
"msg": "Name: Jerry. Age: 18"
}
TASK [task2] ***************************************************************************************
ok: [192.168.1.55] => (item={'name': 'Tom', 'age': 20}) => {
"msg": "Name: Tom. Age: 20"
}
ok: [192.168.1.55] => (item={'name': 'Jerry', 'age': 18}) => {
"msg": "Name: Jerry. Age: 18"
}
TASK [task3] ***************************************************************************************
ok: [192.168.1.55] => (item={'name': 'Tom', 'age': 20}) => {
"msg": "Name: Tom. Age: 20"
}
ok: [192.168.1.55] => (item={'name': 'Jerry', 'age': 18}) => {
"msg": "Name: Jerry. Age: 18"
}
dict
如果要遍历一个dict,则需要使用 dict2items
filter,将其转换为list:
yaml
---
- hosts: all
vars:
var1: {name: "Tom", age: 20}
tasks:
- name: task1
debug:
msg: "Key: {{ item.key }}. Value: {{ item.value }}"
loop: "{{ {'name': 'Tom', 'age': 20} | dict2items }}"
- name: task2
debug:
msg: "Key: {{ item.key }}. Value: {{ item.value }}"
loop: "{{ var1 | dict2items }}"
运行结果如下:
powershell
TASK [task1] ***************************************************************************************
ok: [192.168.1.55] => (item={'key': 'name', 'value': 'Tom'}) => {
"msg": "Key: name. Value: Tom"
}
ok: [192.168.1.55] => (item={'key': 'age', 'value': 20}) => {
"msg": "Key: age. Value: 20"
}
TASK [task2] ***************************************************************************************
ok: [192.168.1.55] => (item={'key': 'name', 'value': 'Tom'}) => {
"msg": "Key: name. Value: Tom"
}
ok: [192.168.1.55] => (item={'key': 'age', 'value': 20}) => {
"msg": "Key: age. Value: 20"
}
本例中,dict为:
yaml
{name: "Tom", age: 20}
转为list后:
yaml
[
{
"key": "name",
"value": "Tom"
},
{
"key": "age",
"value": 20
}
]
循环结果的register变量
yaml
---
- hosts: all
tasks:
- name: task1
shell: "echo {{ item }}"
loop: "{{ ['aaa', 'bbb'] }}"
register: var1
- name: task2
debug:
msg: "{{ var1 }}"
运行结果如下:
powershell
TASK [task1] ***************************************************************************************
changed: [192.168.1.55] => (item=aaa)
changed: [192.168.1.55] => (item=bbb)
TASK [task2] ***************************************************************************************
ok: [192.168.1.55] => {
"msg": {
"changed": true,
"msg": "All items completed",
"results": [
{
"ansible_loop_var": "item",
"changed": true,
"cmd": "echo aaa",
"delta": "0:00:00.002332",
"end": "2023-11-21 22:29:54.990234",
"failed": false,
"invocation": {
"module_args": {
"_raw_params": "echo aaa",
"_uses_shell": true,
"argv": null,
"chdir": null,
"creates": null,
"executable": null,
"removes": null,
"stdin": null,
"stdin_add_newline": true,
"strip_empty_ends": true
}
},
"item": "aaa",
"msg": "",
"rc": 0,
"start": "2023-11-21 22:29:54.987902",
"stderr": "",
"stderr_lines": [],
"stdout": "aaa",
"stdout_lines": [
"aaa"
]
},
{
"ansible_loop_var": "item",
"changed": true,
"cmd": "echo bbb",
"delta": "0:00:00.002036",
"end": "2023-11-21 22:29:55.223227",
"failed": false,
"invocation": {
"module_args": {
"_raw_params": "echo bbb",
"_uses_shell": true,
"argv": null,
"chdir": null,
"creates": null,
"executable": null,
"removes": null,
"stdin": null,
"stdin_add_newline": true,
"strip_empty_ends": true
}
},
"item": "bbb",
"msg": "",
"rc": 0,
"start": "2023-11-21 22:29:55.221191",
"stderr": "",
"stderr_lines": [],
"stdout": "bbb",
"stdout_lines": [
"bbb"
]
}
],
"skipped": false
}
}
可见,register变量把循环操作的结果放到了叫做 results
的list里。因此,后续可以遍历 results
,做相应处理,比如:
yaml
---
- hosts: all
tasks:
- name: task1
shell: "cat {{ item }}"
loop: "{{ ['/tmp/a.txt', '/tmp/d.txt'] }}"
register: var1
ignore_errors: true
- name: task2
debug:
msg: "{{ var1 }}"
- name: task3
fail:
msg: "Something is wrong!"
when: item.rc != 0
loop: "{{ var1.results }}"
假设 /tmp/d.txt
不存在,则运行结果如下:
powershell
TASK [task1] ***************************************************************************************
changed: [192.168.1.55] => (item=/tmp/a.txt)
failed: [192.168.1.55] (item=/tmp/d.txt) => {"ansible_loop_var": "item", "changed": true, "cmd": "cat /tmp/d.txt", "delta": "0:00:00.002438", "end": "2023-11-21 22:46:28.216904", "item": "/tmp/d.txt", "msg": "non-zero return code", "rc": 1, "start": "2023-11-21 22:46:28.214466", "stderr": "cat: /tmp/d.txt: No such file or directory", "stderr_lines": ["cat: /tmp/d.txt: No such file or directory"], "stdout": "", "stdout_lines": []}
...ignoring
TASK [task2] ***************************************************************************************
ok: [192.168.1.55] => {
"msg": {
"changed": true,
"failed": true,
"msg": "One or more items failed",
"results": [
{
"ansible_loop_var": "item",
"changed": true,
"cmd": "cat /tmp/a.txt",
"delta": "0:00:00.003006",
"end": "2023-11-21 22:46:27.995302",
"failed": false,
"invocation": {
"module_args": {
"_raw_params": "cat /tmp/a.txt",
"_uses_shell": true,
"argv": null,
"chdir": null,
"creates": null,
"executable": null,
"removes": null,
"stdin": null,
"stdin_add_newline": true,
"strip_empty_ends": true
}
},
"item": "/tmp/a.txt",
"msg": "",
"rc": 0,
"start": "2023-11-21 22:46:27.992296",
"stderr": "",
"stderr_lines": [],
"stdout": "aaaaa\nb\nccccc",
"stdout_lines": [
"aaaaa",
"b",
"ccccc"
]
},
{
"ansible_loop_var": "item",
"changed": true,
"cmd": "cat /tmp/d.txt",
"delta": "0:00:00.002438",
"end": "2023-11-21 22:46:28.216904",
"failed": true,
"invocation": {
"module_args": {
"_raw_params": "cat /tmp/d.txt",
"_uses_shell": true,
"argv": null,
"chdir": null,
"creates": null,
"executable": null,
"removes": null,
"stdin": null,
"stdin_add_newline": true,
"strip_empty_ends": true
}
},
"item": "/tmp/d.txt",
"msg": "non-zero return code",
"rc": 1,
"start": "2023-11-21 22:46:28.214466",
"stderr": "cat: /tmp/d.txt: No such file or directory",
"stderr_lines": [
"cat: /tmp/d.txt: No such file or directory"
],
"stdout": "",
"stdout_lines": []
}
],
"skipped": false
}
}
TASK [task3] ***************************************************************************************
skipping: [192.168.1.55] => (item={'changed': True, 'stdout': 'aaaaa\nb\nccccc', 'stderr': '', 'rc': 0, 'cmd': 'cat /tmp/a.txt', 'start': '2023-11-21 22:46:27.992296', 'end': '2023-11-21 22:46:27.995302', 'delta': '0:00:00.003006', 'msg': '', 'invocation': {'module_args': {'_raw_params': 'cat /tmp/a.txt', '_uses_shell': True, 'stdin_add_newline': True, 'strip_empty_ends': True, 'argv': None, 'chdir': None, 'executable': None, 'creates': None, 'removes': None, 'stdin': None}}, 'stdout_lines': ['aaaaa', 'b', 'ccccc'], 'stderr_lines': [], 'failed': False, 'item': '/tmp/a.txt', 'ansible_loop_var': 'item'})
failed: [192.168.1.55] (item={'changed': True, 'stdout': '', 'stderr': 'cat: /tmp/d.txt: No such file or directory', 'rc': 1, 'cmd': 'cat /tmp/d.txt', 'start': '2023-11-21 22:46:28.214466', 'end': '2023-11-21 22:46:28.216904', 'delta': '0:00:00.002438', 'failed': True, 'msg': 'non-zero return code', 'invocation': {'module_args': {'_raw_params': 'cat /tmp/d.txt', '_uses_shell': True, 'stdin_add_newline': True, 'strip_empty_ends': True, 'argv': None, 'chdir': None, 'executable': None, 'creates': None, 'removes': None, 'stdin': None}}, 'stdout_lines': [], 'stderr_lines': ['cat: /tmp/d.txt: No such file or directory'], 'item': '/tmp/d.txt', 'ansible_loop_var': 'item'}) => {"ansible_loop_var": "item", "changed": false, "item": {"ansible_loop_var": "item", "changed": true, "cmd": "cat /tmp/d.txt", "delta": "0:00:00.002438", "end": "2023-11-21 22:46:28.216904", "failed": true, "invocation": {"module_args": {"_raw_params": "cat /tmp/d.txt", "_uses_shell": true, "argv": null, "chdir": null, "creates": null, "executable": null, "removes": null, "stdin": null, "stdin_add_newline": true, "strip_empty_ends": true}}, "item": "/tmp/d.txt", "msg": "non-zero return code", "rc": 1, "start": "2023-11-21 22:46:28.214466", "stderr": "cat: /tmp/d.txt: No such file or directory", "stderr_lines": ["cat: /tmp/d.txt: No such file or directory"], "stdout": "", "stdout_lines": []}, "msg": "Something is wrong!"}
本例中,由于 /tmp/d.txt
不存在, results
的第2个元素,其rc值为1。
注意:每一次迭代,其结果就会放到register变量里,而不是整个循环结束后才放的。
yaml
---
- hosts: all
tasks:
- name: task1
shell: echo "{{ item }}"
loop:
- "aaa"
- "bbb"
register: var1
changed_when: var1.stdout != "aaa"
运行结果如下:
powershell
TASK [task1] ***************************************************************************************
ok: [192.168.1.55] => (item=aaa)
changed: [192.168.1.55] => (item=bbb)
可见,第一次迭代没有满足判断条件,而第二次迭代满足判断条件了。
复杂循环
遍历嵌套list
yaml
---
- hosts: all
tasks:
- name: task1
debug:
msg: "{{ item[0] }} {{ item[1] }}"
loop: "{{ ['Zhang', 'Li'] | product(['San', 'Si']) | list }}"
运行结果如下:
powershell
TASK [task1] ***************************************************************************************
ok: [192.168.1.55] => (item=['Zhang', 'San']) => {
"msg": "Zhang San"
}
ok: [192.168.1.55] => (item=['Zhang', 'Si']) => {
"msg": "Zhang Si"
}
ok: [192.168.1.55] => (item=['Li', 'San']) => {
"msg": "Li San"
}
ok: [192.168.1.55] => (item=['Li', 'Si']) => {
"msg": "Li Si"
}
本例中,把两个list做笛卡尔乘积,生成了一个新的嵌套list:
yaml
[
[
"Zhang",
"San"
],
[
"Zhang",
"Si"
],
[
"Li",
"San"
],
[
"Li",
"Si"
]
]
然后遍历外层list,并通过 item[0]
、 item[1]
访问内层list的元素。
Retry
yaml
---
- hosts: all
tasks:
- name: task1
shell: cat /tmp/a.txt
register: var1
until: var1.stdout.find("OK") != -1
retries: 3
delay: 5
运行结果如下:
powershell
TASK [task1] ***************************************************************************************
FAILED - RETRYING: [192.168.1.55]: task1 (3 retries left).
FAILED - RETRYING: [192.168.1.55]: task1 (2 retries left).
FAILED - RETRYING: [192.168.1.55]: task1 (1 retries left).
fatal: [192.168.1.55]: FAILED! => {"attempts": 3, "changed": true, "cmd": "cat /tmp/a.txt", "delta": "0:00:00.002228", "end": "2023-11-23 07:53:18.333193", "msg": "", "rc": 0, "start": "2023-11-23 07:53:18.330965", "stderr": "", "stderr_lines": [], "stdout": "aaaaa\nb\nccccc", "stdout_lines": ["aaaaa", "b", "ccccc"]}
在运行过程中,编辑 /tmp/a.txt
文件(注意是在目标机器上),添加 OK
的内容,则运行结果如下:
powershell
TASK [task1] ***************************************************************************************
FAILED - RETRYING: [192.168.1.55]: task1 (3 retries left).
FAILED - RETRYING: [192.168.1.55]: task1 (2 retries left).
changed: [192.168.1.55]
注:
retries
的缺省值是3
,delay
的缺省值是5
。
遍历inventory
假设 /etc/ansible/hosts
内容如下:
powershell
[myvms]
192.168.1.55
[myself]
127.0.0.1
yaml
---
- hosts: all
tasks:
- name: task1
debug:
msg: "{{ item }}"
loop: "{{ groups['all'] }}"
#loop: "{{ groups['myvms'] }}"
运行结果如下:
powershell
TASK [task1] ***************************************************************************************
ok: [192.168.1.55] => (item=192.168.1.55) => {
"msg": "192.168.1.55"
}
ok: [192.168.1.55] => (item=127.0.0.1) => {
"msg": "127.0.0.1"
}
ok: [127.0.0.1] => (item=192.168.1.55) => {
"msg": "192.168.1.55"
}
ok: [127.0.0.1] => (item=127.0.0.1) => {
"msg": "127.0.0.1"
}
可见,打印了所有的主机名。
为什么打印了两次呢?这是因为指定了 hosts: all
,所以在两个目标机器上都运行了一次。
若改为 hosts: myvms
,则运行结果如下:
powershell
TASK [task1] ***************************************************************************************
ok: [192.168.1.55] => (item=192.168.1.55) => {
"msg": "192.168.1.55"
}
ok: [192.168.1.55] => (item=127.0.0.1) => {
"msg": "127.0.0.1"
}
如果只想遍历 myvms
,则把 loop: "{``{ groups['all'] }}"
改为 loop: "{``{ groups['myvms'] }}"
,运行结果如下:
powershell
TASK [task1] ***************************************************************************************
ok: [192.168.1.55] => (item=192.168.1.55) => {
"msg": "192.168.1.55"
}
也可以通过 loop: "{``{ ansible_play_batch }}"
指定遍历当前play的主机:
powershell
TASK [task1] ***************************************************************************************
ok: [192.168.1.55] => (item=192.168.1.55) => {
"msg": "192.168.1.55"
}
注: groups
和 ansible_play_batch
都是Ansible的特殊变量,参见 https://docs.ansible.com/ansible/latest/reference_appendices/special_variables.html
。
还可以通过 inventory_hostnames
lookup来指定遍历的主机:
yaml
---
- hosts: myvms
tasks:
- name: task1
debug:
msg: "{{ item }}"
loop: "{{ query('inventory_hostnames', 'all') }}"
运行结果如下:
powershell
TASK [task1] ***************************************************************************************
ok: [192.168.1.55] => (item=192.168.1.55) => {
"msg": "192.168.1.55"
}
ok: [192.168.1.55] => (item=127.0.0.1) => {
"msg": "127.0.0.1"
}
遍历 all
,同时排除 myvms
,则指定:loop: "{``{ query('inventory_hostnames', 'all:!myvms') }}"
:
powershell
TASK [task1] ***************************************************************************************
ok: [192.168.1.55] => (item=127.0.0.1) => {
"msg": "127.0.0.1"
}
loop_control
label
前面我们提到,遍历一个dict:
yaml
---
- hosts: all
vars:
var1: {name: "Tom", age: 20}
tasks:
- name: task1
debug:
msg: "Key: {{ item.key }}. Value: {{ item.value }}"
loop: "{{ {'name': 'Tom', 'age': 20} | dict2items }}"
#loop_control:
# label: "{{ item.key}}"
运行结果如下:
powershell
TASK [task1] ***************************************************************************************
ok: [192.168.1.55] => (item={'key': 'name', 'value': 'Tom'}) => {
"msg": "Key: name. Value: Tom"
}
ok: [192.168.1.55] => (item={'key': 'age', 'value': 20}) => {
"msg": "Key: age. Value: 20"
}
注意其中的 (item={'key': 'name', 'value': 'Tom'})
等,如果item数据量很大,则输出量很大。此处可以使用 label
指定打印的内容(比如只打印key,不打印value),见注释部分。
运行结果如下:
powershell
TASK [task1] ***************************************************************************************
ok: [192.168.1.55] => (item=name) => {
"msg": "Key: name. Value: Tom"
}
ok: [192.168.1.55] => (item=age) => {
"msg": "Key: age. Value: 20"
}
pause
在每次循环迭代之间,暂停一段时间(秒)。
yaml
---
- hosts: all
tasks:
- name: task1
debug:
msg: "{{ item }}"
loop: "{{ ['aaa', 'bbb', 'ccc'] }}"
loop_control:
pause: 3
index_var
指定下标变量,然后通过该变量获取下标值(从0开始)。
yaml
---
- hosts: all
tasks:
- name: task1
debug:
msg: "{{ idx }}: {{ item }}"
loop: "{{ ['aaa', 'bbb', 'ccc'] }}"
loop_control:
index_var: idx
运行结果如下:
powershell
TASK [task1] ***************************************************************************************
ok: [192.168.1.55] => (item=aaa) => {
"msg": "0: aaa"
}
ok: [192.168.1.55] => (item=bbb) => {
"msg": "1: bbb"
}
ok: [192.168.1.55] => (item=ccc) => {
"msg": "2: ccc"
}
loop_var
循环的元素名称,默认叫做 item
,而对于嵌套循环,为避免混淆内外循环的item,可用 loop_var
指定item名称。
创建文件 test19.yml
如下:
yaml
---
- hosts: all
tasks:
- name: task1
include_tasks: test20.yml
loop: [1, 2, 3]
loop_control:
loop_var: item_outer
创建文件 test20.yml
如下:
yaml
---
- name: inner_task1
debug:
msg: "Outer item = {{ item_outer }}, Inner item = {{ item_inner }}"
loop: "{{ ['aaa', 'bbb', 'ccc'] }}"
loop_control:
loop_var: item_inner
运行结果如下:
powershell
TASK [task1] ***************************************************************************************
included: /root/temp/temp1121/test20.yml for 192.168.1.55 => (item=1)
included: /root/temp/temp1121/test20.yml for 192.168.1.55 => (item=2)
included: /root/temp/temp1121/test20.yml for 192.168.1.55 => (item=3)
TASK [inner_task1] *********************************************************************************
ok: [192.168.1.55] => (item=aaa) => {
"msg": "Outer item = 1, Inner item = aaa"
}
ok: [192.168.1.55] => (item=bbb) => {
"msg": "Outer item = 1, Inner item = bbb"
}
ok: [192.168.1.55] => (item=ccc) => {
"msg": "Outer item = 1, Inner item = ccc"
}
TASK [inner_task1] *********************************************************************************
ok: [192.168.1.55] => (item=aaa) => {
"msg": "Outer item = 2, Inner item = aaa"
}
ok: [192.168.1.55] => (item=bbb) => {
"msg": "Outer item = 2, Inner item = bbb"
}
ok: [192.168.1.55] => (item=ccc) => {
"msg": "Outer item = 2, Inner item = ccc"
}
TASK [inner_task1] *********************************************************************************
ok: [192.168.1.55] => (item=aaa) => {
"msg": "Outer item = 3, Inner item = aaa"
}
ok: [192.168.1.55] => (item=bbb) => {
"msg": "Outer item = 3, Inner item = bbb"
}
ok: [192.168.1.55] => (item=ccc) => {
"msg": "Outer item = 3, Inner item = ccc"
}
本例中,外部循环的item命名为 item_outer
,而内部循环的item命名为 item_inner
。
扩展循环变量
添加 extended: true
,则可以访问如下变量:
ansible_loop.allitems
:所有元素ansible_loop.index
:从1开始ansible_loop.index0
:从0开始ansible_loop.revindex
:倒数,从1开始ansible_loop.revindex0
:倒数,从0开始ansible_loop.first
:是否是第一个元素ansible_loop.last
:是否是最后一个元素ansible_loop.length
:元素数量ansible_loop.previtem
:前一个元素(第一次迭代时未定义)ansible_loop.nextitem
:后一个元素(最后一次迭代时未定义)
yaml
---
- hosts: all
tasks:
- name: task1
debug:
msg: "ansible_loop.allitems = {{ ansible_loop.allitems }}, ansible_loop.index = {{ ansible_loop.index }}, ansible_loop.index0 = {{ ansible_loop.index0 }}, ansible_loop.revindex = {{ ansible_loop.revindex }}, ansible_loop.revindex0 = {{ ansible_loop.revindex0 }}, ansible_loop.first = {{ ansible_loop.first }}, ansible_loop.last = {{ ansible_loop.last }}, ansible_loop.length = {{ ansible_loop.length }}, ansible_loop.previtem = {{ ansible_loop.previtem | default('no previous') }}, ansible_loop.nextitem = {{ ansible_loop.nextitem | default('no next') }}"
loop: "{{ ['aaa', 'bbb', 'ccc'] }}"
loop_control:
extended: true
运行结果如下:
powershell
TASK [task1] ***************************************************************************************
ok: [192.168.1.55] => (item=aaa) => {
"msg": "ansible_loop.allitems = ['aaa', 'bbb', 'ccc'], ansible_loop.index = 1, ansible_loop.index0 = 0, ansible_loop.revindex = 3, ansible_loop.revindex0 = 2, ansible_loop.first = True, ansible_loop.last = False, ansible_loop.length = 3, ansible_loop.previtem = no previous, ansible_loop.nextitem = bbb"
}
ok: [192.168.1.55] => (item=bbb) => {
"msg": "ansible_loop.allitems = ['aaa', 'bbb', 'ccc'], ansible_loop.index = 2, ansible_loop.index0 = 1, ansible_loop.revindex = 2, ansible_loop.revindex0 = 1, ansible_loop.first = False, ansible_loop.last = False, ansible_loop.length = 3, ansible_loop.previtem = aaa, ansible_loop.nextitem = ccc"
}
ok: [192.168.1.55] => (item=ccc) => {
"msg": "ansible_loop.allitems = ['aaa', 'bbb', 'ccc'], ansible_loop.index = 3, ansible_loop.index0 = 2, ansible_loop.revindex = 1, ansible_loop.revindex0 = 0, ansible_loop.first = False, ansible_loop.last = True, ansible_loop.length = 3, ansible_loop.previtem = bbb, ansible_loop.nextitem = no next"
}
注:如果 ansible_loop.allitems
很大,为了节省内存,可以设置 extended_allitems: false
:
yaml
loop_control:
extended: true
extended_allitems: false
获取 loop_var
的值
比如指定了 loop_var: myitem
,则可以通过 {``{ myitem }}
来获取item,也可以通过 {``{ lookup('vars', ansible_loop_var) }}
获取item。
yaml
---
- hosts: all
tasks:
- name: task1
debug:
msg: "{{ myitem }} , {{ lookup('vars', ansible_loop_var) }}"
loop: [1, 2, 3]
loop_control:
loop_var: myitem
运行结果如下:
powershell
TASK [task1] ***************************************************************************************
ok: [192.168.1.55] => (item=1) => {
"msg": "1 , 1"
}
ok: [192.168.1.55] => (item=2) => {
"msg": "2 , 2"
}
ok: [192.168.1.55] => (item=3) => {
"msg": "3 , 3"
}
可见,二者效果是一样的。
参考
https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_loops.html
https://docs.ansible.com/ansible/latest/reference_appendices/special_variables.html
https://docs.ansible.com/ansible/latest/inventory_guide/intro_patterns.html