RHCE9学习指南 第31章 控制语句

一个play中可以包括多个task,如果不想所有的task全部都执行,只有满足某个条件了才执行这个task,不满足条件则不执行此task。本章讲解when和block-rescue两种判断语句。

31.1 判断语句when

when作为一个判断语句,出现在某个task下。格式如下。

tasks:
- name: aa
  模块1
  when: 条件1

如果条件1成立,则执行模块1,如果不成立则不执行。
注意:在when中引用变量时是不要加{{}}的。
本章实验都是在/home/lduan/demo3下操作,先把这个目录创建出来。

[lduan@server demo2]$ cd
[lduan@server ~]$ mkdir demo3
[lduan@server ~]$ cp ansible.cfg hosts demo3/
[lduan@server ~]$ cd demo3/
[lduan@server demo3]$

31.1.1 when判断中> 、< 、!=的使用

练习1:
写一个playbook,判断某条件是否成立,成立了才执行task,否则不执行,命令如下。

[lduan@server demo3]$ cat when-1.yaml 
---
- hosts: server2
  tasks:
  - name: task1
    debug: msg="111"
    when: 1 < 2 
[lduan@server demo3]$

这里有一个task,判断1< 2是否是成立的,如果成立则task1执行,会在屏幕上显示111,如果不成立则task1不执行,屏幕上就不会有111。这里明显是成立的,所以task1是会执行的。执行结果如下。

[lduan@server demo3]$ ansible-navigator run ./when-1.yaml -m stdout
    ...输出...
TASK [task1] 
ok: [server2] => {
    "msg": "111"
} 
    ...输出...
[lduan@server demo3]$ 

when后面可以有多个条件,用or 或and作为连接符
用or作为连接符的例子,如果用or作为连接符,只要有一个成立即可,只有所有的判断都不成立整体才不成立。
练习2:
修改when-1.yaml内容如下。

[lduan@server demo3]$ cat when-1.yaml
---
- hosts: server2
  tasks:
  - name: task1
    debug: msg="111"
    when: 1 < 2 or 2 > 3
[lduan@server demo3]$

此处用or作为连接符,只要成立一个就会成立,2 > 3 不成立但是1<2成立,所以整体上就是成立的,执行结果如下。

[lduan@server demo3]$ ansible-navigator run ./when-1.yaml -m stdout
    ...输出...
TASK [task1] 
ok: [server2] => {
    "msg": "111"
} 
    ...输出...
[lduan@server demo3]$ 

仍然会执行task1。
练习3:
修改when-1.yaml内容如下。

[lduan@server demo3]$ cat when-1.yaml
---
- hosts: server2
  tasks:
  - name: task1
    debug: msg="111"
    when: 1 > 2 or 2 > 3
[lduan@server demo3]$

此处用or作为连接符,1>2 不成立且2>3也不成立,所以整体上就是不成立的,所以是不会执行task1的,结果如下。

[lduan@server demo3]$ ansible-navigator run ./when-1.yaml -m stdout
    ...输出...
TASK [task1] 
skipping: [server2]
    ...输出...
[lduan@server demo3]$

也可以用and作为连接符,如果用and作为连接符,需要所有条件全部成立,只要有一个判断没成立整体上就是不成立的。
练习3:
修改when-1.yaml内容如下。

[lduan@server demo3]$ cat when-1.yaml
---
- hosts: server2
  tasks:
  - name: task1
    debug: msg="111"
    when: 2 > 1 and 2 > 3
[lduan@server demo3]$ 

这里虽然2>1是成立的,但是2>3不成立,所以整体上就不成立,因为and作为连接符,需要所有的判断都成立才可以,所以task1是不会执行的。执行结果如下。

[lduan@server demo3]$ ansible-navigator run ./when-1.yaml -m stdout
    ...输出...
TASK [task1] 
skipping: [server2]
    ...输出...
[lduan@server demo3]$

在判断中or和and是可以混用的,为了看得更清晰可以使用小括号。
练习4:
修改when-1.yaml内容如下。

[lduan@server demo3]$ cat when-1.yaml
---
- hosts: server2
  tasks:
  - name: task1
    debug: msg="111"
    when: ( 1>2 or 2!=1 ) and 2 > 3
[lduan@server demo3]$ 

这里( 1>2 or 2!=1 )作为一个整体,1>2不成立但是2!=1(!=是不等于的意思)成立,所以此处1 > 2 or 2!=1作为一个整体是成立的。and后面 2> 3不成立,所以整个when后面的判断是不成立的,所以此task1也会执行,执行结果如下。

[lduan@server demo3]$ ansible-navigator run ./when-1.yaml -m stdout
    ...输出...
TASK [task1] 
skipping: [server2]
    ...输出...
[lduan@server demo3]$

这里常见的判断符包括以下6种。

(1)==:等于。
(2)!=:不等于。
(3)>:大于。
(4)>=:大于登录。
(5)<:小于。
(6)<=:小于等于。

练习5:
如果server2的系统主版本是7(rhel7/centos7),则打印111,否则不打印。playbook内容如下。

[lduan@server demo3]$ cat when-2.yaml 
---
- hosts: server2
  tasks:
  - name: task2
    debug: msg="111"
    when: ansible_distribution_major_version == "7"
[lduan@server demo3]$

因为server2的系统是rhel9,所以上述task2是不会执行的,是不会显示111的。

[lduan@server demo3]$ ansible-navigator run ./when-2.yaml -m stdout
    ...输出...
TASK [task2] 
skipping: [server2]
    ...输出...
[lduan@server demo3]$

注意:ansible_distribution_major_version的值是一个字符串,所以when判断中==后面的7是要加引号的。
练习6:
修改when-2.yaml的内容,如下所示。

[lduan@server demo3]$ cat when-2.yaml 
---
- hosts: server2
  tasks:
  - name: task2
    debug: msg="111"
    when: ansible_distribution_major_version == "9"
[lduan@server demo3]$

再次运行playbook,结果如下所示,会显示111。

[lduan@server demo3]$ ansible-navigator run ./when-2.yaml -m stdout
    ...输出..
TASK [task2] 
ok: [server2] => {
    "msg": "111"
}
    ...输出...
[lduan@server demo3]$

再次提醒:在when中引用变量时是不要加{{}}的。

31.1.2 when判断中in的用法

在when语句中,除可以使用上面的大于、小于等判断方法外,还可以用in,用法如下。

value  in  列表

如果此值在这个列表中,则判断成立,否则不成立。
练习7:
判断某值是否出现在列表中,编写when-3.yaml代码如下。

[lduan@server demo3]$ cat when-3.yaml 
---
- hosts: server2
  vars: 
    list1: [1,2,3,4]
  tasks:
  - name: task3
    debug: msg="333"
    when: 2 in list1
[lduan@server demo3]$

此处定义一个列表list1,里面有4个值,分别为1,2,3,4, 定义了一个task打印333,这个task要不要执行,就要看when后面的判断是否成立。如果在2出现在列表list1中,则执行,如果不在,则不执行,明显的2在列表list1中,所以此task会执行,即会在屏幕上显示333。执行此playbook结果如下。

[lduan@server demo3]$ ansible-navigator run ./when-3.yaml -m stdout
    ...输出...
TASK [task3] 
ok: [server2] => {
    "msg": "333"
}
    ...输出...
[lduan@server demo3]$ 

因为2在列表list1中,when判断成立,可以正确的执行task3,所以屏幕会打印333这个结果。修改when-3.yaml如下。

[lduan@server demo3]$ cat when-3.yaml
---
- hosts: server2
  vars: 
    list1: [1,2,3,4]
  tasks:
  - name: task3
    debug: msg="333"
    when: 2 not in list1
[lduan@server demo3]$

这里判断的是2不在list1中,但2是在list1中的,所以判断不成立,执行结果如下。

[lduan@server demo3]$ ansible-navigator run ./when-3.yaml -m stdout
    ...输出...
TASK [task3] 
skipping: [server2]
    ...输出...
[lduan@server demo3]$

因为when判断不成立,所以在屏幕不会打印333,回想前面的例子。

[lduan@server demo3]$ cat ../demo2/9-inventory1.yaml 
---
- hosts: db
  tasks:
  - name: 打印我在清单文件中的名称
    debug: msg={{inventory_hostname}}
    when: inventory_hostname in groups ['xx'] 
[lduan@server demo3]$

这里判断当前正在执行的主机是不是在属于主机组xx中,如果是,则执行debug,如果不是则不执行。

31.1.3 when判断中is的用法

is可以用于判断变量是否被定义,常见的判断包括以下3种。

(1)is defined:变量被定义。
(2)is undefined:等同于is not defined变量没被定义。
(3)is none:变量被定义了但是值为空。

看下面的例子。

[lduan@server demo3]$ cat when-4.yaml
---
- hosts: server2
  vars: 
    aa: 1
    bb: 
  tasks:
  - name: task1
    debug: msg="111"
    when: aa is undefined
  - name: task2
    debug: msg="222"
    when: bb is undefined
  - name: task3
    debug: msg="333"
    when: cc is not defined
[lduan@server demo3]$

首先定义了2个变量:aa和bb,其中bb的值为空,此处并没有定义cc。后面定义了3个task:

如果aa被定义了,则打印111,这里aa定义了,所以判断成立,task1会执行。
如果bb没有被定义,则显示222,这里bb是被定义了,所以判断不成立,task2不会执行。
如果cc没有被定义,则显示333,这里cc没有定义,所以判断成立,task3不执行。

这里is undefined 和 is not define是一个意思。
查看执行的结果。

[lduan@server demo3]$ ansible-navigator run ./when-4.yaml -m stdout
    ...输出...
TASK [task1] 
ok: [server2] => {
    "msg": "111"
}
TASK [task2] 
skipping: [server2]
TASK [task3] 
ok: [server2] => {
    "msg": "333"
}
    ...输出...
[lduan@server demo3]$

练习7:
写一个playbook,命令如下。

[lduan@server demo3]$ cat when-5.yaml
---
- hosts: server2
  tasks:
  - name: 执行一个系统命令
    shell: "ls /aa.txt"
    register: aa
    ignore_errors: yes
  - name: task2
    fail: msg="命令执行错了001"
    when: aa.rc != 0
  - name: task3
    debug: msg="OK123"
[lduan@server demo3]$

执行此playbook,命令如下。

[lduan@server demo3]$ ansible-navigator run ./when-5.yaml -m stdout
    ...输出...
TASK [执行一个系统命令] 
fatal: [server2]: FAILED! => {"changed": true, "cmd": "ls /aa.txt", "delta": "0:00:00.003036", "end": "2021-07-30 12:51:32.762689", "msg": "non-zero return code", "rc": 2, "start": "2021-07-30 12:51:32.759653", "stderr": "ls: 无法访问'/aa.txt': 没有那个文件或目录", "stderr_lines": ["ls: 无法访问'/aa.txt': 没有那个文件或目录"], "stdout": "", "stdout_lines": []}
...ignoring
TASK [task2] 
fatal: [server2]: FAILED! => {"changed": false, "msg": "命令执行错了001"}
    ...输出...
[lduan@server demo3]$

31.2 判断block-rescue

对于when来说,只能做一个判断,成立就执行不成立就不执行。block和rescue一般同用,类似于shell判断语句中的if-else,在block下面可以包含多个模块,来判断这多个模块是否执行成功了。
block-rescue的用法如下。

block:
  - 模块1
  - 模块2
  - 模块3
rescue:
  - 模块x
  - 模块y

先执行block中的模块1,如果没报错,则继续执行模块2,如果block中所有模块都执行成功了,则跳过rescue中所有模块,直接执行下一个task中的模块,如图:31-1所示。
file
图31-1 block-rescue用法
这里有2个task1和task2,在task1里block里有3个模块rescue里有2个模块。如果block1中所有模块都正确执行了,则不执行rescue中的模块,直接执行task2。
如果block中任一模块执行失败,block中其他后续的模块都不再执行,然后会跳转执行rescue中的模块,如图31-2所示。
file
图31-2 运行失败
这里block1中的模块1执行完成之后会执行模块2,模块2如果报错,则不会执行模块3,直接跳转到rescue中,执行模块x。rescue中所有模块全部正确执行完毕之后,则执行task2。
如果rescue中某个模块执行失败,则退出整个playbook,如图31-3所示。
file
图31-3 退出整个playbook
这个block中的模块2执行失败,则跳转到rescue中执行模块x,如果模块x执行失败了话,将退出整个playbook,即task2也不会执行了。
如果某个报错模块有ignore_errors: yes选项,则会此模块的错误不算数,继续进行下一个模块,如图31-4所示。
file
图31-4 报错模块有ignore_errors: yes选项
这里block中模块2失败了,但是因为加了ignore_errors: yes,所以忽略这个报错模块,继续执行模块3。
练习1:
按上面的描述写一个playbook,命令如下。

[lduan@server demo3]$ cat block-1.yaml
---
- hosts: server2 
  tasks:
  - name: tasks1
    block:
    - name: 11
      debug: msg="1111"

    - name: 22
      shell: "ls /aa.txt"

    - name: 33
      debug: msg="3333"

    rescue:
    - name: xx
      debug: msg="xxxx"

    - name: yy
      debug: msg="yyyy"

  - name: task2
    debug: msg="zzzz"
[lduan@server demo3]$   

这里在task1的block中运行了3个模块,第一个模块可以正确执行,第二个模块是执行一个系统命令ls /aa.txt, 但是在server2中是不存在/aa.txt这个文件的,所以这个模块会执行失败。block中第三个模块不再执行,直接跳转到rescue中模块。rescue中的2个模块均可正确执行,然后执行task2。
所以,屏幕的结果会显示1111, xxxx, yyyy, zzzz。
运行结果如下。

[lduan@server demo3]$ ansible-navigator run ./block-1.yaml -m stdout
    ...输出...
TASK [debug] 
ok: [server2] => {
    "msg": "1111"
}
TASK [shell] 
fatal: [server2]: FAILED! => {"changed": true, "cmd": "ls /aa.txt", ... "stderr": "ls: 无法访问'/aa.txt': 没有那个文件或目录", "stderr_lines": ["ls: 无法访问'/aa.txt': 没有那个文件或目录"], "stdout": "", "stdout_lines": []}
TASK [xx] 
ok: [server2] => {
    "msg": "xxxx"
}
TASK [yy] 
ok: [server2] => {
    "msg": "yyyy"
}

TASK [task2] 
ok: [server2] => {
    "msg": "zzzz"
}
    ...输出...
[lduan@server demo3]$

练习2:
修改block-1.yaml文件如下。

[lduan@server demo3]$ cat block-1.yaml
---
- hosts: server2 
  tasks:
  - name: tasks1
    block:
    - name: 11
      debug: msg="1111"

    - name: 22
      shell: "ls /aa.txt"
      ignore_errors: yes

    - name: 33
      debug: msg="3333"

    rescue:
    - name: xx
      debug: msg="xxxx"

    - name: yy
      debug: msg="yyyy"

  - name: task2
    debug: msg="zzzz"
[lduan@server demo3]$ 

这里与上面的例子比,在block中的第二个模块中增加了一个ignore_errors: yes,这样block中的第二个模块即使报错了,也会忽略这个报错继续执行第三个模块。然后执行task2的内容,所以在屏幕上是能看到1111,3333,zzzz的。执行结果如下。

[lduan@server demo3]$ ansible-navigator run ./block-1.yaml -m stdout
    ...输出...
ok: [server2] => {
    "msg": "1111"
}
TASK [shell] 
fatal: [server2]: FAILED! => {"changed": true, "cmd": "ls /aa.txt",...stderr": "ls: 无法访问'/aa.txt': 没有那个文件或目录", "stderr_lines": ["ls: 无法访问'/aa.txt': 没有那个文件或目录"], "stdout": "", "stdout_lines": []}
...ignoring
TASK [debug] 
ok: [server2] => {
    "msg": "3333"
}
TASK [task2] 
ok: [server2] => {
    "msg": "zzzz"
}
    ...输出...
[lduan@server demo3]$

31.3 循环语句

在shell中for循环的用法如下。

for i in A B C ... ; do
    命令  $i
done

这里首先把A带给i,然后执行do和done之间的命令,然后把B带给i执行do和done之间的命令,以此类推,直到把in后面所有的值执行完毕。for后面变量的命名可以随便取。
在回顾下前面介绍的列表,如下所示。

employee:
- uname: lisi
  age:22
  sex: man

- uname: wangwu
  age: 24
  sex: man

- uname: xiaohua
  age: 21

这里列表employee中有3个元素,分别记录了lisi、wangwu、xiaohua的信息。我们把这三个元素当成刚讲的for循环中的A, B, C。先把第一个元素带给一个变量,执行某个操作,完了之后再把第二个元素带给变量。
用for循环A,B,C,在playbook中用loop来循环列表中的元素。
在for循环中,指定一个变量如i,然后分别将A、B、C赋值给i。
在loop中,使用一个固定的变量item,然后把每个元素赋值给item,如图31-5所示。
file
图31-5 第一次循环
第二次循环,如图31-6所示。
file
图31-6 第2次循环
练习1:
定义一个users列表,然后循环这个列表中的每个元素,命令如下。

[lduan@server demo3]$ cat loop-1.yaml 
---
- hosts: server2
  vars:
    users:
    - uname: tom
      age: 20
      sex: man
    - uname: bob
      age: 22
      sex: man
    - uname: mary
      age: 20
      sex: woman

  tasks:
  - name: task1
    debug: msg={{ item }}
    loop: "{{ users }}"
[lduan@server demo3]$ 

这里定义了一个列表users,里面包含了3个用户的信息,在task1中用loop开始循环这个列表。loop后面写列表名时,需要使用引号引起来,这里的关键字loop可以换成关键字with_items。

这里先把users的第一个元素赋值给item,然后用debug打印,然后把users的第二个元素带给item,用debug打印,直到把所有的元素都赋值给item。
playbook执行的结果如下。

[lduan@server demo3]$ ansible-playbook loop-1.yaml
    ...输出...
TASK [task1] 
ok: [server2] => (item={'uname': 'tom', 'age': 20, 'sex': 'man'}) => {
    "msg": {
        "age": 20,
        "sex": "man",
        "uname": "tom"
    }
}
ok: [server2] => (item={'uname': 'bob', 'age': 22, 'sex': 'man'}) => {
    "msg": {
        "age": 22,
        "sex": "man",
        "uname": "bob"
    }
}
ok: [server2] => (item={'uname': 'mary', 'age': 20, 'sex': 'woman'}) => {
    "msg": {
        "age": 20,
        "sex": "woman",
        "uname": "mary"
    }
}
    ...输出...
[lduan@server demo3]$ 

如果不想打印每个元素的所有条目,只想打印每个元素的uname呢?答案可以通过练习2解决。
练习2:
修改的内容loop-1.yaml如下。

[lduan@server demo3]$ cat loop-1.yaml 
---
- hosts: server2
  vars:
    users:
    ...内容不变...
  tasks:
  - name: task1
    debug: msg={{ item.uname }}
    loop: "{{ users }}"
[lduan@server demo3]$ 

列表的每个元素都是一个字典,所以item就是字典,所以要获取这个字典中的uname变量,就用item.uname就可以了。
运行此playbook的结果如下。

[lduan@server demo3]$ ansible-navigator -m stdout run loop-1.yaml
    ...输出...
TASK [task1] 
ok: [server2] => (item={'uname': 'tom', 'age': 20, 'sex': 'man'}) => {
    "msg": "tom"
}
ok: [server2] => (item={'uname': 'bob', 'age': 22, 'sex': 'man'}) => {
    "msg": "bob"
}
ok: [server2] => (item={'uname': 'mary', 'age': 20, 'sex': 'woman'}) => {
    "msg": "mary"
} 
    ...输出...
[lduan@server demo3]$

练习3:
如果想打印所有性别为男的那些用户名,修改的内容loop-1.yaml如下。

[lduan@server demo3]$ cat loop-1.yaml 
---
- hosts: server2
  vars:
    users:
    ...内容不变...
  tasks:
  - name: task1
    debug: msg={{ item.uname }}
    when: item.sex == "man"
    loop: "{{ users }}"
[lduan@server demo3]$ 

在此playbook中,我们用when加了一个判断。循环列表时,先把第一个元素带给item,先判断item.sex的值是否为man,如果是则判断成立,然后执行debug模块。如果item.sex的值不是则判断不成立,不执行debug模块。

第一次循环结束之后,然后开始第二次循环,把第二个元素带给item之后,做相同的判断。
运行结果如下。

[lduan@server demo3]$ ansible-navigator -m stdout run loop-1.yaml
    ...输出...
TASK [task1] 
ok: [server2] => (item={'uname': 'tom', 'age': 20, 'sex': 'man'}) => {
    "msg": "tom"
}
ok: [server2] => (item={'uname': 'bob', 'age': 22, 'sex': 'man'}) => {
    "msg": "bob"
}
skipping: [server2] => (item={'uname': 'mary', 'age': 20, 'sex': 'woman'})  
    ...输出...
[lduan@server demo3]$

这里就把所有性别为男的用户名打印出来了。

作业

1.写一个名称为chap31-1.yaml的playbook,要求判断server2系统是否是RHEL9的系统(包括centos)。如果是,则打印server2的IP,如果不是则打印server2的hostname。

2.写一个名称为chap31-2.yaml的playbook,要求查看/etc/aa.txt的内容。如果/etc/aa.txt存在,则显示这个文件的内容,如果不存在,则显示“文件不存在”。

3.定义一个变量文件varsfile1,里面包括一个uses列表内容如下。

[lduan@server ~]$ cat varfile1
users:
- uname: tom
  age: 20
  sex: man
- uname: bob
  age: 22
  sex: man
- uname: mary
  age: 20
  sex: woman
[lduan@server ~]$

写一个名称为chap32-1.yaml的playbook,打印出users列表中所有性别为man的用户的名称(uname字段)打印出来。

相关新闻

发表回复

Please Login to Comment

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理

                                                                                                                                    RHCE9学习指南全部更新完成,点击阅读