两类模块的差别
命令模块执行一个可执行程序,参数以列表形式传入,不经过 shell,因此管道、通配符展开、重定向与环境变量赋值都不生效,适合参数固定、无需 shell 特性的场景。shell 模块通过系统的 shell 执行整段字符串,支持管道与重定向,但也因此更容易写出不可重复、易受注入影响的内容。两者在执行报告里默认都被视为「已变更」,不会判断目标是否已经是期望状态,这是与状态型模块最重要的差别。
基本信息
| 命令模块 | 直接执行程序,不经过 shell |
|---|---|
| shell 模块 | 通过 shell 执行,支持管道与重定向 |
| 共同限制 | 默认不具备幂等判断,总是报告已变更 |
| 优先选择 | 装包、写文件、管服务等用状态型模块 |
包装成幂等的办法
- 存在性判断:目标任务已完成时直接跳过,避免重复执行。
- 标注变更条件:命令本身不具备变更语义时,用条件表达式描述真实变更。
- 标注失败条件:有些命令用非零返回码表达「无变化」,需要显式改判。
- 校验结果:执行后检查实际状态,而不是只看返回码。
- 限制重试:无幂等保证的命令不要开启自动重试。
- 说明原因:在任务注释里写清为什么不用专用模块,便于评审。
同一件事的两种写法
同一件事的两种写法
# 命令模块 + 存在性判断:可重复执行
- name: 初始化数据目录(仅当未初始化时)
ansible.builtin.command:
cmd: /opt/app/bin/init-db --path /var/lib/app
creates: /var/lib/app/initialized.flag # 已存在则跳过,保证幂等
# shell 模块:需要管道时必须显式声明,并补齐判断
- name: 统计并写入上报文件
ansible.builtin.shell:
cmd: |
journalctl -u app --since '1 hour ago' | grep -c ERROR > /var/tmp/app_err.cnt
args:
executable: /bin/bash # 明确使用哪个 shell
changed_when: false # 仅采集,不算变更选择判读
| 需求 | 建议模块 | 原因 |
|---|---|---|
| 安装软件包 | 包管理模块 | 自带幂等与版本判断 |
| 下发配置 | 模板或复制模块 | 有差异比对与备份 |
| 启停服务 | 服务模块 | 状态型语义 |
| 执行不支持的工具命令 | 命令模块加判断 | 无专用模块时的兜底 |
| 需要管道与重定向 | shell 模块加判断 | 命令模块不支持 shell 特性 |
| 仅采集信息 | 命令模块并标注不算变更 | 避免误报变更 |
把命令模块的变更语义标对很重要:仅做查询的任务若报告已变更,会让执行报告的变更数量失去参考价值,掩盖真正的变更。
shell 模块会把整段字符串交给 shell 解释,变量来自外部输入时存在注入风险;能用命令模块表达时不要使用 shell。