优先选 ansible.builtin.command(除非需管道/重定向/变量展开);command 更安全轻量,shell 易出错且有子shell陷阱。

ansible.builtin.shell 和 ansible.builtin.command 选哪个?
shell 和 command 看似都能执行命令,但底层行为完全不同。前者调用 /bin/sh(或指定 shell),支持管道、重定向、变量展开;后者绕过 shell,直接 execve,不解析 $PATH、不支持 | 或 &&。
常见错误现象:用 command 跑 ls /tmp | grep log 报错 unrecognized arguments: |;或者用 shell 执行 cd /opt && ./deploy.sh,结果发现 cd 不生效——因为每条 shell 任务都是独立子 shell。
使用场景:
- 用
command:调用绝对路径二进制(如/usr/bin/touch)、避免 shell 注入、需要确定性执行环境 - 用
shell:必须用管道/重定向/环境变量、依赖当前用户 shell 配置(比如~/.bashrc里的 alias)
性能影响:command 更轻量,无 shell 解析开销;shell 多一层进程 fork,且可能引入意外的 shell 行为(比如 glob 展开)。
- command: /bin/echo "hello" - shell: echo $HOME | tr 'a-z' 'A-Z'
如何让 loop 变量在 when 条件里安全使用?
Ansible 的 loop(或旧式 with_items)和 when 组合时,容易踩「变量未定义」或「类型不匹配」的坑。比如遍历一个字典列表,但某一项缺 key,when: item.port == 8080 就会报 error while evaluating conditional。
根本原因是 Ansible 默认开启 jinja2_strict_undefined,访问不存在的 key 直接失败。
解决办法只有两个:
- 用
item.port | default(80)显式兜底 - 改用
item.get('port', 80)(推荐,更接近 Python 习惯)
别写 when: item.port is defined and item.port == 8080——它不能防止 item 本身是 None 或字符串。
另外注意:loop 中的 item 类型完全取决于你传进去的数据,不是所有情况都是 dict。如果源是 [1, 2, "abc"],item.port 永远是错的。
- name: restart only nginx services with port 80
service:
name: "{{ item.name }}"
state: restarted
loop: "{{ services }}"
when: item.port | default(80) == 80
ansible.builtin.include_tasks vs. ansible.builtin.import_tasks 怎么选?
关键区别就一条:import_tasks 是静态包含,在 playbook 解析阶段就加载并展开全部任务;include_tasks 是动态包含,运行时才读取文件、才决定是否执行(支持 when、loop 控制)。
iWebShop基于iWebSI框架开发,在获得iWebSI技术平台库支持的条件下,iWebShop可以轻松满足用户量级百万至千万级的大型电子商务网站的性能要求。站点的集群与分布式技术(分布式计算与存储/高可用性/负载均衡)被屏蔽在SI 平台之内,基于iWebShop并且按照SI平台库扩展规范开发的新增功能模块,也将同时获得这种超级计算与处理的能力。作为开源的LAMP电子商务系统,iWebShop
常见错误现象:
- 在
include_tasks的文件里用了loop,但外层 task 写了when: false,结果文件还是被读取了(只是任务不执行)——这没问题; - 但反过来,用
import_tasks+when,条件不满足时整个 import 会被跳过,连语法检查都做不了,容易掩盖 YAML 错误。
使用场景:
- 用
import_tasks:通用初始化任务(如共用的权限设置)、需要被tags或--start-at-task定位的稳定流程 - 用
include_tasks:根据变量动态加载逻辑(如不同 OS 加载不同包安装任务)、配合loop分批执行、调试时想临时跳过某段
兼容性注意:Ansible 2.12+ 已弃用 include(不带 _tasks 后缀),只保留这两个明确语义的模块。
为什么 register 的变量在后续 play 里用不了?
Ansible 的变量作用域默认是「play 级」,register 存的变量只在当前 play 内有效。跨 play 访问会得到 UndefinedVariable。
这不是 bug,是设计使然:避免隐式依赖、提升可读性、防止状态污染。
如果你真需要跨 play 共享,只有两个合规方式:
- 用
set_fact+cacheable: true(需启用 fact cache,如fact_caching: jsonfile) - 把值写到文件(
copy或lineinfile),下个 play 再读(适合简单字符串)
别用 hostvars[inventory_hostname] 强取——它只对当前 play 的 hostvars 有效,跨 play 依然空。
还有一个易忽略点:register 的变量名如果含破折号(如 my-result),Jinja2 里必须写成 hostvars[inventory_hostname]['my-result'],不能点号访问。
- name: get uptime command: uptime register: uptime_result <ul><li><p>name: use it in same play — OK debug: var=uptime_result.stdout</p></li><li><p>name: use it in next play — FAIL unless set_fact or cache debug: var=uptime_result</p>
复杂点在于:Ansible 不提供“全局变量”机制,所有跨 play 数据传递都得显式落地。很多人卡在这儿,不是不会写,是没意识到这是故意的设计约束。









