[TOC]
在使用ansible的过程中,我们经常需要处理一些返回信息而这些返回信息中,通常可能不是单独的一条返回信息而是一个信息列表;
循环常用关键字:
如果我们想要循环的处理信息列表中的每一条信息,我们该怎么办呢?
答:需要采用with_items关键字指定遍历的变量和item变量进行迭代输出;即"with_items"关键字会把返回的列表信息自动处理
,将每一条信息单独放在一个名为”item”的变量中,我们只要获取到名为”item”变量的变量值,即可循环的获取到列表中的每一条信息
#语法1:
tasks:
- debug:
msg: "{{item}}"
with_items: [ 1, 2, 3 ]
#语法2:
tasks:
- debug:
msg: "{{item}}"
with_items: [ 1, 2, 3 ]
#语法3:
tasks:
- debug:
msg: "{{item.test1}}" #"a"和"c"会被输出。
with_items:
- { test1: a, test2: b }
- { test1: c, test2: d }
#语法4:jinja2是一种模板语言,jinja2是一个基于python的模板引擎
- debug:
msg:
"{% for i in item %}
{{ i }}
{% endfor %}"
with_items : [ 1, 2, 3 ]
示例1:如果我想要获取到清单中所有分组的主机的主机名,并且获取返回信息中的第二条信息;
ansible testA -m debug -a "msg={{groups.all}}"
# 10.10.107.221 | SUCCESS => {
# "msg": [
# "local",
# "10.10.107.221",
# "10.20.172.179"
# ]
# }
#获取第二项
ansible testA -m debug -a "msg={{groups.all[1]}}"
# 10.10.107.221 | SUCCESS => {
# "msg": "10.10.107.221"
# }
实际演示:
# cat > withitems.yml<<END
---
- hosts: local
remote_user: root
gather_facts: no
tasks:
- debug:
msg: "{{item}}"
with_items: "{{groups.all}}"
- debug:
msg: "{% for i in item %}{{ i }}{% endfor %}"
with_items : ["1","2","3"] #注意需要是字符串类型
END
#执行结果
ansible-playbook withitems.yml
TASK [debug] #返回信息中的每一条信息都会循环的被debug模块处理 (一条执行一次)
ok: [local] => (item=local) => {
"msg": "local"
}
ok: [local] => (item=10.10.107.221) => {
"msg": "10.10.107.221"
}
ok: [local] => (item=10.20.172.179) => {
"msg": "10.20.172.179"
}
#jinja2语言循环语法
TASK [debug]
ok: [local] => (item=1) => {
"msg": "1"
}
ok: [local] => (item=2) => {
"msg": "2"
}
ok: [local] => (item=3) => {
"msg": "3"
}
比如,在没有学会使用循环之前如果想要在同一主机中创建四个文件,但是学了循环后您只需要将建立的文件放入数组之中,然后迭代使用;
循环常使用案例:
# cat > items1.yml <<END
---
- hosts: local
remote_user: root
gather_facts: no
vars:
dirs:
- "/tmp/a"
- "/tmp/b"
- "/tmp/c"
- "/tmp/d"
tasks:
- file:
path: "{{item}}" #关键点1 (多次运行file模块 - 每一行运行一次)
state: touch
with_items: "{{dirs}}" #重复的操作越多,使用循环则越方便 #关键点2
- shell: "whoami"
register: "cmd"
- debug:
msg: "{{cmd[item]}}" #关键点3 shell模块命令遍历返回的信息
with_items: "{{cmd}}" #关键点4
- debug:
var: cmd[item] #关键点3 shell模块命令遍历返回的信息
with_items: "{{cmd}}" #关键点4
END
(1)示例执行结果:
#循环建立文件
TASK [file]
changed: [local] => (item=/tmp/a)
changed: [local] => (item=/tmp/b)
changed: [local] => (item=/tmp/c)
changed: [local] => (item=/tmp/d)
#循环出来命令执行结果
TASK [debug]
ok: [local] => (item=changed) => {
"msg": true
}
ok: [local] => (item=stdout) => {
"msg": "root"
}
ok: [local] => (item=delta) => {
"msg": "0:00:00.022253"
}
ok: [local] => (item=stdout_lines) => {
"msg": ["root"]
}
ok: [local] => (item=end) => {
"msg": "2019-08-01 10:47:41.760052"
}
ok: [local] => (item=start) => {
"msg": "2019-08-01 10:47:41.737799"
}
ok: [local] => (item=cmd) => {
"msg": "whoami"
}
ok: [local] => (item=failed) => {
"msg": false
}
#debug vars执行返回的变量(有格式输出)
ok: [local] => (item=changed) => {
"ansible_loop_var": "item",
"cmd[item]": true,
"item": "changed"
}
ok: [local] => (item=stdout) => {
"ansible_loop_var": "item",
"cmd[item]": "root",
"item": "stdout"
}
....... 其他忽略
采用循环我们也可以像file模块一样执行多个命令,只需要将要执行的命令放入with_items关键字中
;
循环输出方式2:
# cat > items2.yml <<END
---
- hosts: local
remote_user: root
vars:
cmd: #关键点
- "hostname"
- "hostname -I"
gather_facts: no
tasks:
- name: "Mutil Shell Execute"
shell: "{{item}}" #关键点
with_items: "{{cmd}}"
register: returncmd #会把多次执行的结果放入returncmd变量之中
- debug:
var: returncmd
#当使用了循环以后,每次shell模块执行后的返回值都会放入一个名为"results"的序列中,我们可以直接调用使用
- name: "show shell execute result"
debug:
msg: "{{item.stdout}}" #关键点
with_items: "{{returncmd.results}}" #关键点
#因为debug模块只是输出了经过jinja2的for循环处理过的信息而已,debug模块并没有因为for循环而被重复的调用
- name: "jinja2 syntax"
debug:
msg: "{% for i in returncmd.results %} {{ i.stdout }} {% endfor %}" #for循环的语法为jinja2语言中的for循环语法
END
#(2)执行结果:
TASK [show shell execute result]
ok: [local] => (item={'stderr_lines': [], .... , 'stdout_lines': [u'master'], u'start': u'2019-08-01 11:17:55.900489'}) => {
"msg": "master"
}
ok: [local] => (item={'stderr_lines': [], ..... , 'stdout_lines': [u'10.10.107.222 192.168.1.99 '], u'start': u'2019-08-01 11:17:56.116440'}) => {
"msg": "10.10.107.222 192.168.1.99 "
}
#jinja2 语法更加的精简
TASK [jinja2 syntax]
ok: [local] => {
"msg": " master 10.10.107.222 192.168.1.99 "
}
WeiyiGeek.returncmd
前面我们说 with_items 会循环的输出列表(最外层大列表)中的每一项,按照之前的思路debug模块应该会将每个小列表作为一个小整体输出,而不应该输出小列表中的每个元素
,但是事实却是with_items将嵌套在大列表中的每个小列表都\”展开\”了,并且将小列表中的元素都输出了
如果我们想要将每个小列表作为一个整体输出该怎么办呢?
答: 那就需要我们的主人公 with_list关键字,可以将每个小列表作为一个整体输出;经过with_list处理后,每个嵌套在大列表中的小列表都被当做一个整体存放在item变量中,最终被debug作为一个小整体输出了,而不会像with_items一样将小列表"展开拉平"后一并将小列表中的元素循环输出
。
with_list语法:
#语法1:
with_list:
- [ 1, 2, 3 ]
- [ a, b ]
#语法2:
with_list:
-
- 1
- 2
- 3
-
- a
- b
with_list关键字与with_items 与 with_flattened 关键字区别示例:
# cat > itemsVSlist.yml <<END
---
- hosts: local
remote_user: root
gather_facts: no
tasks:
- name: " with_list show"
debug:
msg: "{{item}}"
with_list: #关键点
- [ 1, 2]
- [ a, b ]
- name: " with_items show"
debug:
msg: "{{item}}"
with_items: #关键点
- [ 1, 2 ]
- [ a, b ]
- name: " with_flattened show"
debug:
msg: "{{item}}"
with_flattened: #关键点
- [ 1, 2 ]
- [ a, b ]
END
执行结果:
TASK [with_list show] #以列表为单位输出
# ok: [local] => (item=[1, 2]) => {
# "msg": [
# 1,
# 2
# ]
# }
# ok: [local] => (item=[u'a', u'b']) => {
# "msg": [
# "a",
# "b"
# ]
# }
TASK [with_items show / with_flattened show] #会将元素单独输出
# ok: [local] => (item=1) => {
# "msg": 1
# }
# ok: [local] => (item=2) => {
# "msg": 2
# }
# ok: [local] => (item=a) => {
# "msg": "a"
# }
# ok: [local] => (item=b) => {
# "msg": "b"
# }
总结:
在处理"嵌套列表"时才会体现出区别
;描述:目前为止我们了解了三个关键字可以用于循环操作,它们是with_list、with_items、with_flattened,下面引出一个新的关键字 with_together 将两个列表中的元素"对齐合并-一一对应"
playbook(剧本案例):
# cat > withtogether.yml<<END
---
- hosts: local
remote_user: root
gather_facts: no
tasks:
- debug:
msg: "{{ item }}"
with_together:
- [ 1, 2, 3 ]
- [ a, b ]
END
#执行结果
# ok: [local] => (item=[1, u'a']) => {
# "msg": [
# 1,
# "a"
# ]
# }
# ok: [local] => (item=[2, u'b']) => {
# "msg": [
# 2,
# "b"
# ]
# }
# ok: [local] => (item=[3, u'c']) => {
# "msg": [
# 3,
# "c"
# ]
# }
从上述结果可以看出:
不存在列表中对应值将变成NULL
;ok: [local] => (item=[None, u'c']) => { "msg": [ null, #列表元素数量不同 [1,2] [a, b, c] "c" ] } ok: [local] => (item=[3, None]) => { "msg": [ 3, null #列表元素数量不同 [1,2,3] [a, b] ] 描述:”with_cartesian”关键字的作用就是将每个小列表中的元素按照”笛卡尔的方式”组合后,循环的处理每个组合;其实还有一个关键字可以代替”with_cartesian”,它就是"with_nested"与"with_cartesian"的效果一致
;
比如:我们要再{a,b,c}目录下分别建立下面两个目录{test1, test2},常规的用法可以采用# mkdir -p {a,b,c}/{test1,test2}
命令,再ansible采用shell模块执行:# ansible test70 -m shell -a "mkdir -p /testdir/testdir/{a,b,c}/{test1,test2}"
但我们这里需要采用with_cartesian关键来实现上述效果:
WeiyiGeek.
实际案例:
# cat > with_cartesian.yml<<END
---
- hosts: local
remote_user: root
gather_facts: no
tasks:
- name: "Show Create directory"
debug:
msg: "{{ item }}"
with_cartesian: #关键点
- [a, b, c]
- [test1, test2]
- name: "shell create Directory"
file:
path: "/tmp/{{item.0}}/{{item.1}}" #关键点
state: directory
with_cartesian:
- [a, b, c]
- [test1, test2]
END
执行结果:
#Debug msg情况 -> 笛卡尔积
TASK [Show Create directory]
ok: [local] => (item=[u'a', u'test1']) => {"msg": ["a","test1"]}
ok: [local] => (item=[u'a', u'test2']) => {"msg": ["a", "test2"]}
ok: [local] => (item=[u'b', u'test1']) => {"msg": ["b","test1"]}
ok: [local] => (item=[u'b', u'test2']) => {"msg": ["b","test2"]}
ok: [local] => (item=[u'c', u'test1']) => {"msg": ["c","test1"]}
ok: [local] => (item=[u'c', u'test2']) => {"msg": ["c","test2"]}
#创建的目录情况
TASK [shell create Directory]
ok: [local] => (item=[u'a', u'test1'])
ok: [local] => (item=[u'a', u'test2'])
ok: [local] => (item=[u'b', u'test1'])
ok: [local] => (item=[u'b', u'test2'])
ok: [local] => (item=[u'c', u'test1'])
ok: [local] => (item=[u'c', u'test2'])
描述:顾名思义应该与”索引”有关,”with_indexed_items”的作用就是在循环处理列表时为列表中的每一项添加"数字索引","索引"从0开始
.
第二层列表中的项如果仍然是一个列表"with_indexed_items"则不会拉平这个列表,而是将其当做一个整体进行编号
。基础示例:单层列表与多层列表
# cat > with_indexed_items.yml <<END
---
- hosts: local
remote_user: root
gather_facts: no
tasks:
- name: "Example play 1"
debug:
msg: "E.g 1: index is : {{ item.0 }} , value is {{ item.1 }}"
with_indexed_items: #将添加过编号的每一项放入到了item中,简单的单层列表
- test1 #test1索引编号是0
- test2 #test2索引编号是1
- test3 #test3索引编号是2
- name: "Example play 2"
debug:
msg: "E.g 2: index is : {{ item.0 }} , value is {{ item.1 }}"
with_indexed_items: #多层列表与with_flattened效果类似进行拉升
- [a,b,c]
- [test1, test2]
- name: "Example play 3"
debug:
msg: "E.g 3: index is : {{ item.0 }} , value is {{ item.1 }}"
with_indexed_items: #多层列表但是列表中镶嵌列表 (特殊重点)
- [a,b]
- [c, [d,e]]
- [end]
END
执行结果:
#TASK [Example play 1]
ok: [local] => (item=[0, u'test1']) => {"msg": "E.g 1: index is : 0 , value is test1"}
ok: [local] => (item=[1, u'test2']) => {"msg": "E.g 1: index is : 1 , value is test2"}
ok: [local] => (item=[2, u'test3']) => {"msg": "E.g 1: index is : 2 , value is test3"}
#TASK [Example play 2]
ok: [local] => (item=[0, u'a']) => {"msg": "E.g 2: index is : 0 , value is a"
ok: [local] => (item=[1, u'b']) => {"msg": "E.g 2: index is : 1 , value is b"}
ok: [local] => (item=[2, u'c']) => {"msg": "E.g 2: index is : 2 , value is c"}
ok: [local] => (item=[3, u'test1']) => {"msg": "E.g 2: index is : 3 , value is test1"}
ok: [local] => (item=[4, u'test2']) => {"msg": "E.g 2: index is : 4 , value is test2"}
#TASK [Example play 3]
ok: [local] => (item=[0, u'a']) => {"msg": "E.g 3: index is : 0 , value is a"}
ok: [local] => (item=[1, u'b']) => {"msg": "E.g 3: index is : 1 , value is b"}
ok: [local] => (item=[2, u'c']) => {"msg": "E.g 3: index is : 2 , value is c"}
ok: [local] => (item=[3, [u'd', u'e']]) => {"msg": "E.g 3: index is : 3 , value is [u'd', u'e']"}
ok: [local] => (item=[4, u'end']) => {"msg": "E.g 3: index is : 4 , value is end"}
描述:采用with_sequence关键字,可以指定开始与结束并且可以指定step步跳,即可以帮助我们按照顺序生成数字序列;
"with_sequence"还有一个小功能
,就是\”格式化\”输出数据的功能,\”格式化数据\”的方法与C语言的printf函数的使用方法类似,
基础案例:
# cat >with_sequence.yml<<END
---
- hosts: local
remote_user: root
gather_facts: no
tasks:
- name: "Method 1"
debug:
msg: "{{ item }}"
with_sequence: start=1 end=3 stride=1
#其中start=1表示从1开始,end=3表示到5结束,stride=1表示步长为1,即从1到5每次增加1
- name: "Method 2"
debug:
msg: "{{ item }}"
with_sequence: count=3
#count=5表示数字序列默认从1开始,到3结束,默认步长为1,与上述两种写法的效果相同
- name: "Demo 3"
debug:
msg: "{{ item }}" #" %0.2f"表示将数字格式化为一个保留两位小数点的浮点数
with_sequence: start=6 end=2 stride=-2 format="number is %0.2f" #关键点
- name: "Demo 4 - Create dir"
file:
path: "/tmp/test{{ item }}"
state: directory
with_sequence:
start=2
end=10
stride=2
END
执行结果:
#TASK [Method 1 / Method 1]
ok: [local] => (item=1) => {"msg": "1"}
ok: [local] => (item=2) => {"msg": "2"}
ok: [local] => (item=3) => {"msg": "3"}
#TASK [Demo 3]
ok: [local] => (item=number is 6.00) => {"msg": "number is 6.00"}
ok: [local] => (item=number is 4.00) => {"msg": "number is 4.00"}
ok: [local] => (item=number is 2.00) => {"msg": "number is 2.00"}
#TASK [Demo 4 - Create dir]
changed: [local] => (item=2)
changed: [local] => (item=4)
changed: [local] => (item=6)
changed: [local] => (item=8)
changed: [local] => (item=10)
总结:
描述:可以从列表的多个值中随机返回一个值;
我们使用\”with_random_choice\”处理这个列表,可以看出每次返回的结果是从列表中随机选中的一个
# cat >with_random_choice.yml<<END
---
- hosts: local
remote_user: root
gather_facts: no
tasks:
- debug:
msg: "{{ item }}"
with_random_choice:
- "one"
- 2
- "three"
- 4
- "five"
END
#执行结果(随机列表)
TASK [debug]
ok: [local] => (item=three) => {
"msg": "three"
}
描述:从字面意思就可看出,它是可以遍历对象的即字典类型的,分别将字典的key与value进行存储并且支持迭代;
基础案例:
# cat >with_dict.yml<<END
---
- hosts: local
remote_user: root
gather_facts: no
vars:
users:
WeiyiGeek: female
Baby_ang: male
tasks:
- name: "Demo 1.dict"
debug:
msg: "Username: {{ item.key }} , User's gender: {{ item.value }}"
with_dict: "{{users}}"
- name: "Demo 2.dict"
debug: #关键点
msg: "Item: {{ item.key }}, Username: {{ item.value['name'] }} , User's gender: {{ item.value.gender }} , User's Telephone: {{ item.value.telephone }}"
with_dict:
alice:
name: Alice Appleworth
gender: female
telephone: 123-456-7890
bob:
name: Bob Bananarama
gender: male
telephone: 987-654-3210
END
执行结果:
#TASK [Demo 1.dict]
ok: [local] => (item={'value': u'male', 'key': u'Baby_ang'}) => {
"msg": "Username: Baby_ang , User's gender: male"
}
ok: [local] => (item={'value': u'female', 'key': u'WeiyiGeek'}) => {
"msg": "Username: WeiyiGeek , User's gender: female"
}
#TASK [Demo 2.dict]
ok: [local] => (item={'value': {u'gender': u'male', u'name': u'Bob Bananarama', u'telephone': u'987-654-3210'}, 'key': u'bob'}) => {
"msg": "Item: bob, Username: Bob Bananarama , User's gender: male , User's Telephone: 987-654-3210"
}
ok: [local] => (item={'value': {u'gender': u'female', u'name': u'Alice Appleworth', u'telephone': u'123-456-7890'}, 'key': u'alice'}) => {
"msg": "Item: alice, Username: Alice Appleworth , User's gender: female , User's Telephone: 123-456-7890"
}
描述:该关键字支持复合类型的字典;”with_subelements”的以处理一个的复合结构的字典数据,在处理这个字典的同时,需要指定一个子元素,这个子元素的值必须是一个列表,”with_subelements”会将子元素的列表中的每一项作为一个整体,将其他子元素作为一个整体,然后将两个整体组合成item。
基础案例: 由于item由两个整体组成,所以我们通过item.0获取到第一个小整体,即gender和name属性,然后通过item.1获取到第二个小整体,即hobby列表中的每一项;
# cat >with_subelement.yml<<END
---
- hosts: local
remote_user: root
gather_facts: no
vars:
users:
- name: WeiyiGeek
gender: male
hobby:
- Skateboard
- VideoGame
- name: Xiaodaigua
gender: female
hobby:
- Music
tasks:
- debug:
msg: "{{ item.0.name }} 's hobby is {{ item.1 }} , User's Gender is {{ item.0.gender}}" #关键点(变成子对象元素,既可以分别可以将两个字典进行整合)
with_subelements:
- "{{users}}"
- hobby #关键点,整合后实际是vars中 {{item}}.hobby
END
执行结果:
#TASK [debug]
ok: [local] => (item=[{u'gender': u'male', u'name': u'WeiyiGeek'}, u'Skateboard']) => {
"msg": "WeiyiGeek 's hobby is Skateboard , User's Gender is male"
}
ok: [local] => (item=[{u'gender': u'male', u'name': u'WeiyiGeek'}, u'VideoGame']) => {
"msg": "WeiyiGeek 's hobby is VideoGame , User's Gender is male"
}
ok: [local] => (item=[{u'gender': u'female', u'name': u'Xiaodaigua'}, u'Music']) => {
"msg": "Xiaodaigua 's hobby is Music , User's Gender is female"
}
描述:循环的获取到ansible主机中的文件的内容,注意不是远程目标主机中的文件;
基础示例:
# cat > with_file.yml<<END
---
- hosts: local
remote_user: root
gather_facts: no
tasks:
- debug:
msg: "{{ item }}"
with_file: #可以通过"with_file"关键字获取到ansible主机中的文件内容
- /tmp/demo1.txt #ansbile主机上的文件
- /tmp/demo2.txt
END
执行结果:
#TASK [debug]
ok: [local] => (item=.....) => {
"msg": "master\n10.10.107.222 192.168.1.99 \nMon Aug 5 16:29:39 CST 2019"
}
ok: [local] => (item=.....) => {
"msg": "Linux master 3.10.0-957.12.2.el7.x86_64 #1 SMP Tue May 14 21:24:32 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux"
}
描述: 是用来匹配文件名称的,我们可以通过”with_fileglob”关键字,在指定的目录中匹配符合模式的文件名; 注意: “with_fileglob”只会匹配指定目录中的文件,而不会匹配指定目录中的目录。
比如:我们定义了一个列表,这个列表中只有一个值是一个路径,路径中包含一个通配符,如”/testdir/*”应该代表了/testdir目录中的所有文件;
#常用通配符
- *
- ?
基础示例:
# cat >with_fileglob.yml<<END
---
- hosts: local
remote_user: root
gather_facts: no
tasks:
- debug:
msg: "{{ item }}"
with_fileglob: #可以通过"with_file"关键字获取到ansible主机中的文件内容
- /tmp/demo*.??? #ansbile主机上的文件
- /root/*
END
执行结果:
#TASK [debug]
ok: [local] => (item=/tmp/demo1.txt) => {"msg": "/tmp/demo1.txt"}
ok: [local] => (item=/tmp/demo2.txt) => {"msg": "/tmp/demo2.txt"}
ok: [local] => (item=/root/with_dict.yml) => {"msg": "/root/with_dict.yml"}
ok: [local] => (item=/root/with_subelement.yml) => {"msg": "/root/with_subelement.yml"}
ok: [local] => (item=/root/with_fileglob.yml) => {"msg": "/root/with_fileglob.yml"}
ok: [local] => (item=/root/with_sequence.yml) => {"msg": "/root/with_sequence.yml"}
ok: [local] => (item=/root/with_random_choice.yml) => {"msg": "/root/with_random_choice.yml"}
ok: [local] => (item=/root/with_file.yml) => {"msg": "/root/with_file.yml"}
执行上面的playbook后,会将满足通配符的文件都会被匹配到最终循环的被debug模块输出;