使用ansible执行shell命令的正确姿势

背景

ansible 本身提供了很多内置模块用于完成各种功能。
这样做的好处,一是屏蔽系统差异,比如包管理,你不需要再去操心yum还是apt。(当然包名其实也不省心。)
二是面向终态的设计,避免了过程式的问题。
好处很多,可有些时候你就是不想用。
放弃吧,让 ansible shell 模块拯救你于水火。

基本用法

ad-hoc直接运行,瞬间上手。常用于一次性任务或者测试:
ansible localhost -m shell -a 'ls'

play用法

支持一条、多条命令,可以通过 ignore_errors 忽略返回值,可以引用 ansible 变量。

一条命令

最基本最简单的用法,shell 后直接跟要执行的命令即可。
可以使用 && 将多条命令连接为一条命令。

- name: Exec single shell command # 执行单条命令
shell: echo "1"
- name: Exec multi commands using && # 通过&&连接执行多条命令
shell: echo "1" && echo "2"

注意:如果直接写多行,按照 ansible 语法,会被拼为一条。

- name: Exec multi line command # 这样执行的结果会打印: 1 ls umask
shell:
echo "1"
ls
umask

多条命令

除了上面说的,使用&&将多条命令拼接为一条命令外,还有以下办法:

- name: Exec multi line command with |
shell: | # 注意这里
echo "1"
ls
wrong_command
umask
- name: Exec multi line command with ; # 使用分号间隔多条命令
shell:
echo "1";
ls;
wrong_command;
umask

注意:
这两种方法,都相当于bash中执行了多条命令。所以命令的stdout、stderr都会显示出来,但中间的 wrong_command 的 rc 就被吞掉了。
所以如果需要中间任意一条命令出错就退出,那么尽量使用 && 的方式。

返回值处理

返回值:

  • rc: shell命令返回的code,相当于shell里面的 $?
  • stdout: 标准输出
  • stderr: 标准错误输出

常见用法是比较 stdout 或 stderr 是否为空。

忽略错误

默认rc不为0则报错退出,类似 set -x
可以通过以下方法忽略错误:

  • 设置ignore_errors为true
  • commands|| /bin/true
  • commands|| cat
  • command||:

注意:上述方法所有错误被忽略,比如grep结果为0和错误的命令。需要通过其它方法鉴别。

测试代码

---
- hosts: localhost
gather_facts: F # 跳过gather_facts环节
tasks:
- name: Exec demo shell command # 1条
shell: ls

# 忽略错误
- name: Exec shell and ignore errors
shell: echo "ignore_errors" && exit 1
ignore_errors: true
- name: Exec shell and ignore errors with /bin/true
shell: echo "ignore_errors with /bin/true" && wrong_command || /bin/true
- name: Exec shell and ignore errors with cat
shell: echo "ignore_errors with cat" && wrong_command || cat

# 执行多条命令,中间命令的结果会被吞
- name: Exec multi line command with |
shell: |
echo "multi line command with |"
echo "2"
wrong_command
echo "3"
- name: Exec multi line command with ;
shell:
echo "Exec multi line command with ;";
echo "2";
wrong_command;
echo "3"
# 执行多条命令,中间命令的结果会返回
- name: Exec multi shell command with &&
shell: echo "Exec multi line command with &&"&&wrong_command&& echo "2"

# 检查 test.log 是否存在,不存在则创建
- name: Exec and check
shell: ls|grep test.log
register: test_log
ignore_errors: true
- name: Check test_log
shell: touch test.log
when: test_log.stdout == ""

执行测试

ansible-playbook -v test.yml

注意事项

grep

grep 是最容易出错的,因为 grep 后的条数为0的话,rc不为0,默认会导致报错退出。
而很多场景下,这与我们期望相悖。
所以一般使用 grep 的场景都需要考虑下如何忽略错误。

shell比较语法

如果文件不存在,下面命令的返回值为非0:
[[ -e file_not_exist.log ]] && echo "exist"

下面命令的返回值始终为0:
[[ -e file_not_exist.log ]] && echo "exist" || echo "not exist"

幂等

ansible本身的思想,是面向终态,非过程式的。
而我们使用 shell 脚本的时候,尤其容易引入不幂等的操作。
那如何弥补呢?尽量先检查再执行。比如添加记录到/etc/hosts,不要简单的echo ‘xxx’ >> /etc/hosts,而应该先检查下是否已经存在再添加。