14. Ansible Playbook loop循环语句
目录
loop关键字说明
在ansible 2.5及以前的版本当中,所有的循环都是使用with_X
风格。但是从2.6版本开始,官方开始推荐使用loop
关键字来代替with_X
风格的关键字。
在playbook中使用循环,直接使用loop关键字即可。
如下示例,启动httpd和postfix服务:
tasks: - name: postfix and httpd are running service: name: "{{ item }}" state: started loop: - postfix - httpd
那么在这个示例当中,其实就是使用loop代替了with_list
循环。
事实上,我们可以使用loop关键字搭配一些过滤器来替换更多的、更复杂的with_X
循环。
loop_control
loop_control用于在循环时,获取列表的索引
- hosts: test gather_facts: no vars: testlist: - a - [b,c,[e,f]] - d tasks: - debug: msg: "{{ index }}: {{ item }}" loop: "{{ testlist | flatten(levels=1) }}" loop_control: index_var: index
输出结果:
PLAY [localhost] ****************************************************************** TASK [debug] ********************************************************************** ok: [localhost] => (item=a) => { "msg": "0: a" } ok: [localhost] => (item=b) => { "msg": "1: b" } ok: [localhost] => (item=c) => { "msg": "2: c" } ok: [localhost] => (item=[u'e', u'f']) => { "msg": "3: [u'e', u'f']" } ok: [localhost] => (item=d) => { "msg": "4: d" } PLAY RECAP ************************************************************************ localhost : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
参数说明:
index_var
:loop_control
的选项,让我们指定一个变量,loop_control
会将列表元素的索引值存放到这个指定的变量中
with_list
loop可以替代with_list,当处理嵌套列表时,列表不会被拉平
- hosts: test gather_facts: no vars: testlist: - a - [b,c] - d tasks: - debug: msg: "{{ item }}" loop: "{{ testlist }}"
with_flattened
将所有嵌套都拉平
- hosts: test gather_facts: no vars: testlist: - a - [b,c] - d tasks: - debug: msg: "{{ item }}" loop: "{{ testlist| flatten }}"
with_items
只拉平第一层
- hosts: test gather_facts: no vars: testlist: - a - [b,c] - d tasks: - debug: msg: "{{ item }}" loop: "{{ testlist| flatten(levels=1) }}"
with_indexed_items
通过flatten过滤器(加参数),再配合loop_control
关键字,可以替代with_indexed_items
,当处理多层嵌套的列表时,只有列表中的第一层会被拉平
- hosts: test gather_facts: no vars: testlist: - a - [b,c] - d tasks: - debug: msg: "{{ index }}: {{ item }}" loop: "{{ testlist | flatten(levels=1) }}" loop_control: index_var: index
参数说明:
loop_control
: 用于控制循环的行为,比如在循环时获取到元素的索引index_var
:loop_control
的选项,让我们指定一个变量,loop_control
会将列表元素的索引值存放到这个指定的变量中
with_together
zip_longest
过滤器配合list过滤器,可以替代with_together
- hosts: test gather_facts: no vars: testlist1: [a,b] testlist2: [1,2,3] testlist3: [A,B,C,D] tasks: - debug: msg: "{{ item.0 }} -- {{ item.1 }} -- {{ item.2 }}" # [a,1,A],[b,2,B],['',3,C],['','',D] with_together: - "{{ testlist1 }}" - "{{ testlist2 }}" - "{{ testlist3 }}" - debug: # [a,1,A],[b,2,B] msg: "{{ item.0 }} -- {{ item.1 }} -- {{ item.2 }}" loop: "{{ testlist1 | zip_longest(testlist2,testlist3) | list}}"
当多个列表使用with_together
进行对齐合并时,如果多个列表的长度不同,则使用最长的列表进行对齐,由于短列表中的元素数量不够,所以使用空值与长列表中的元素进行对齐,zip_longest
过滤器也会像with_together
一样,对列表进行组合,但是还需要借助list过滤器,将组合后的数据列表化。
在使用zip_longest过滤器代替with_together关键字时,默认也是使用空值与长列表中的元素进行对齐,但是也可以指定其他的字符串代替空值,如下示例即使用”NONE”代替空值:
- debug: msg: "{{ item.0 }} - {{ item.1 }} - {{ item.2 }}" loop: "{{ testlist1 | zip_longest(testlist2,testlist3,filevalue='NONE') | list }}"
zip_longest默认使用最长的列表长度进行对齐,当有多个列表的长度不同时,如果希望使用最短的列表对齐,则可以使用zip过滤器:
- debug: msg: "{{ item.0 }} - {{ item.1 }} - {{ item.2 }}" loop: "{{ testlist1 | zip(testlist2,testlist3) | list }}"
with_nested/with_cartesian
可使用product过滤器配合list过滤器以替代with_nested
或者with_cartesian
。product过滤器也是需要将组合后的数据进行列表化,所以需要与list过滤器配合使用:
- hosts: localhost gather_facts: no vars: testlist1: [a,b,c] testlist2: [1,2,3,4] tasks: - debug: msg: "{{item.0}} --- {{ item.1}}" loop: "{{ testlist1 | product(testlist2) | list}}" # 结果 ok: [localhost] => (item=[u'a', 1]) => { "msg": "a --- 1" } ok: [localhost] => (item=[u'a', 2]) => { "msg": "a --- 2" } ok: [localhost] => (item=[u'a', 3]) => { "msg": "a --- 3" } ok: [localhost] => (item=[u'a', 4]) => { "msg": "a --- 4" } ok: [localhost] => (item=[u'b', 1]) => { "msg": "b --- 1" } ok: [localhost] => (item=[u'b', 2]) => { "msg": "b --- 2" } ok: [localhost] => (item=[u'b', 3]) => { "msg": "b --- 3" } ok: [localhost] => (item=[u'b', 4]) => { "msg": "b --- 4" } ok: [localhost] => (item=[u'c', 1]) => { "msg": "c --- 1" } ok: [localhost] => (item=[u'c', 2]) => { "msg": "c --- 2" } ok: [localhost] => (item=[u'c', 3]) => { "msg": "c --- 3" } ok: [localhost] => (item=[u'c', 4]) => { "msg": "c --- 4" }
with_sequence
使用range过滤器配合list过滤器可以替代with_sequence:
- hosts: localhost gather_facts: no tasks: - debug: msg: "{{ item }}" loop: "{{ range(0,6,2) | list }}" # 结果 ok: [localhost] => (item=0) => { "msg": 0 } ok: [localhost] => (item=2) => { "msg": 2 } ok: [localhost] => (item=4) => { "msg": 4
上例中表示生成数字,从0开始,到6结束,步长为2。但是需要说明的是,range函数的操作不包含结束范围,也就是说上面的循环只会生成0,2,4三个数字,而不包含6。
另外,with_sequence还有格式化的功能:
- debug: msg: "{{ item }}" with_sequence: start=2 end=6 stride=2 format="number is %0.2f" # 结果 ok: [localhost] => (item=number is 2.00) => { "msg": "number is 2.00" } ok: [localhost] => (item=number is 4.00) => { "msg": "number is 4.00" } ok: [localhost] => (item=number is 6.00) => { "msg": "number is 6.00" }
可使用format配合loop实现:
- debug: msg: "{{ 'number is %0.2f' |format(item) }}" loop: "{{range(2,7,2) |list}}" # 结果 ok: [localhost] => (item=number is 2.00) => { "msg": "number is 2.00" } ok: [localhost] => (item=number is 4.00) => { "msg": "number is 4.00" } ok: [localhost] => (item=number is 6.00) => { "msg": "number is 6.00" }
with_random_choice
使用random函数可以替代with_random_choice,由于random函数是随机取出列表中的一个值,并不涉及循环操作,所以并不使用loop关键字:
- hosts: test gather_facts: no vars: testlist: [a,b,c] tasks: - debug: msg: "{{ testlist | random }}"
with_dict
可使用loop配合dict2items过滤器实现with_dict功能:
- hosts: test gather_facts: no vars: users: alice: female bob: male tasks: - debug: msg: "{{ item.key }} is {{ item.value}}" loop: "{{users | dict2items }}"
with_subelements
可使用loop配合subelements过滤器替代with_subelements:
- hosts: test gather_facts: no vars: users: - name: bob gender: male hobby: - Skateboard - VideoGame - name: alice gender: female hobby: - Music tasks: - debug: msg: "{{ item.0.name }}'s hobby is {{ item.1}}" with_subelements: - "{{ users }}" - hobby - debug: msg: "{{ item.0.name }}'s hobby is {{ item.1}}" loop: "{{ users | subelements('hobby') }}"
使用zip_longest过滤器将两个列表中的元素对齐合并
- hosts: localhost gather_facts: no vars: testlist1: [a,b] testlist2: [1,2,3] testlist3: [A,B,C,D] tasks: - debug: msg: "['{{ item.0 }}','{{ item.1 }}' ,'{{ item.2 }}']" # a,1,A b,2,B '',3,c, '','',D loop: "{{ testlist1 | zip_longest(testlist2,testlist3) | list}}"
输出结果如下:
TASK [debug] ********************************************************************** ok: [localhost] => (item=[u'a', 1, u'A']) => { "msg": [ "a", "1", "A" ] } ok: [localhost] => (item=[u'b', 2, u'B']) => { "msg": [ "b", "2", "B" ] } ok: [localhost] => (item=[None, 3, u'C']) => { "msg": [ "", "3", "C" ] } ok: [localhost] => (item=[None, None, u'D']) => { "msg": [ "", "", "D" ] }
当多个列表进行对齐合并时,如果多个列表的长度不同,则使用最长的列表进行对齐,由于短列表中的元素数量不够,所以使用空值与长列表中的元素进行对齐,zip_longest
过滤器会对列表进行组合,但是还需要借助list过滤器,将组合后的数据列表化。
在使用zip_longest
过滤器时,默认使用空值与长列表中的元素进行对齐,但是也可以指定其他的字符串代替空值,如下示例即使用”NONE”代替空值:
- debug: msg: "{{ item.0 }} - {{ item.1 }} - {{ item.2 }}" loop: {{ testlist1 | zip_longest(testlist2,testlist3,filevalue='NONE') | list }}
zip_longest默认使用最长的列表长度进行对齐,当有多个列表的长度不同时,如果希望使用最短的列表对齐,则可以使用zip过滤器:
- debug: msg: "{{ item.0 }} - {{ item.1 }} - {{ item.2 }}" # a,1,A b,2,B loop: {{ testlist1 | zip(testlist2,testlist3) | list }}
在循环语句中注册变量
下面是一个register的变量在循环中使用的例子:
# cat register_loop.yml - name: registered variable usage as a loop list hosts: test tasks: - name: ensure /mnt/bkspool exists file: path: /mnt/bkspool state: directory - name: retrieve the list of home directories command: ls /home register: home_dirs - name: Show home_dirs results debug: var: home_dirs.stdout_lines - name: add home dirs to the backup spooler file: path: /mnt/bkspool/{{ item }} src: /home/{{ item }} state: link force: yes loop: "{{ home_dirs.stdout_lines }}"
在循环语句中注册变量:
- name: Loop Register test gather_facts: no hosts: test tasks: - name: Looping Echo Task shell: "echo this is my item: {{ item }}" loop: - one - two register: echo_results - name: Show echo_results variable debug: var: echo_results
执行语句,可以看到变量的返回结果为一个字典列表:
ok: [10.1.61.187] => { "echo_results": { "changed": true, "msg": "All items completed", "results": [ { "ansible_loop_var": "item", "changed": true, "cmd": "echo this is my item: one", "delta": "0:00:00.004905", "end": "2019-06-10 00:23:51.814151", "failed": false, "invocation": { "module_args": { "_raw_params": "echo this is my item: one", "_uses_shell": true, "argv": null, "chdir": null, "creates": null, "executable": null, "removes": null, "stdin": null, "stdin_add_newline": true, "strip_empty_ends": true, "warn": true } }, "item": "one", "rc": 0, "start": "2019-06-10 00:23:51.809246", "stderr": "", "stderr_lines": [], "stdout": "this is my item: one", "stdout_lines": [ "this is my item: one" ] }, { "ansible_loop_var": "item", "changed": true, "cmd": "echo this is my item: two", "delta": "0:00:00.004736", "end": "2019-06-10 00:23:52.008981", "failed": false, "invocation": { "module_args": { "_raw_params": "echo this is my item: two", "_uses_shell": true, "argv": null, "chdir": null, "creates": null, "executable": null, "removes": null, "stdin": null, "stdin_add_newline": true, "strip_empty_ends": true, "warn": true } }, "item": "two", "rc": 0, "start": "2019-06-10 00:23:52.004245", "stderr": "", "stderr_lines": [], "stdout": "this is my item: two", "stdout_lines": [ "this is my item: two" ] } ] } }