Ansible报错'dict object'无stdout_lines属性,替换results仍未解决
作为Ansible新手,复制扩展第三方维护的Playbook后,通过以下调试命令运行:
/home/.../ansible-venv/bin/ansible-playbook -vvv playbooks/dump_data_into_csv.yml >/dump_data_into_csv.log 2>&1
- 注释
- hosts: myhosts,启用指定单个/多个主机的配置时,Playbook运行正常; - 启用
- hosts: myhosts配置时,执行到第64行的"Dump data to csv"任务时报错:The task includes an option with an undefined variable. The error was: 'dict object' has no attribute 'stdout_lines'
尝试将代码中的{% if host.grep_output.stdout_lines %}和{% for line in host.grep_output.stdout_lines %}替换为{% if host.grep_output.results %}和{% for line in host.grep_output.results %},但报错依旧。
相关Playbook代码如下:
- hosts: myhosts # - hosts: # - myhost-010_c1 # - myhost-010_c3 # - myhost-014_c4 order: sorted gather_facts: False vars: current_date: "{{ '%Y-%m-%d' | strftime }}" tasks: - name: Grep over syslog become: true ansible.builtin.shell: cmd: | tomorrow_date=$(date '+%Y-%m-%d' -d "$input_date + 1 day") six_days_ago_date=$(date '+%Y-%m-%d' -d "$input_date - 6 days") last_seven_days_syslogs=$(find /var/log/ -type f -name "syslog*" -newermt "$six_days_ago_date" ! -newermt "$tomorrow_date" | sort) sudo zgrep -aHF "my_pattern" $last_seven_days_syslogs | cut -d'[' -f 1 register: grep_output ignore_errors: true no_log: true - name: Dump data to csv # ^ this is line 64, column 7 copy: dest: /tmp/data_{{ '%Y-%m-%d' | strftime }}.csv content: | {{ ['idm', 'idc', 'mese', 'giorno', 'orario'] | map('trim') | join(';') }} {% for host in hosts_list %} {% set idm = host.inventory_hostname.split('_')[0].split('-')[1] %} {% set idm_padded = '%03d' % idm|int %} {% set idm_padded = '"' + idm_padded + '"' %} {% set idc = host.inventory_hostname.split('_')[1].upper() %} {% if host.grep_output.stdout_lines %} {% for line in host.grep_output.stdout_lines %} {% set month = line.strip().split(':')[1].replace(' ', ' ').split(' ')[0] %} {% set day = line.strip().replace(' ', ' ').split(' ')[1] %} {% set time = line.strip().replace(' ', ' ').split(' ')[2] %} {{ [idm_padded, idc, month, day, time] | map('trim') | join(';') }} {% endfor %} {% endif %} {% endfor %} vars: hosts_list: "{{ ansible_play_hosts | map('extract', hostvars) | list }}" delegate_to: localhost register: csv_content run_once: yes
问题出在**myhosts组中存在执行失败/不可达的主机**时,host.grep_output的结构发生了变化:
- 执行成功的主机,
grep_output包含stdout_lines字段; - 执行失败(比如主机不可达、命令报错且
ignore_errors: true)的主机,grep_output中没有stdout_lines,反而会有failed、msg等字段,直接访问stdout_lines就会触发变量未定义错误。
你替换成results的操作是错误的——results是任务使用loop循环时才会生成的结构,当前grep_output是单任务的注册结果,不存在results字段。
方案1:模板中增加状态判断
修改"Dump data to csv"任务的模板部分,先判断任务执行状态,再访问stdout_lines,同时增加行格式校验避免拆分出错:
{{ ['idm', 'idc', 'mese', 'giorno', 'orario'] | map('trim') | join(';') }} {% for host in hosts_list %} {% set idm = host.inventory_hostname.split('_')[0].split('-')[1] %} {% set idm_padded = '%03d' % idm|int %} {% set idm_padded = '"' + idm_padded + '"' %} {% set idc = host.inventory_hostname.split('_')[1].upper() %} {# 先判断任务是否成功,再检查stdout_lines是否存在且非空 #} {% if host.grep_output is defined and not host.grep_output.failed and host.grep_output.stdout_lines is defined and host.grep_output.stdout_lines %} {% for line in host.grep_output.stdout_lines %} {% set line_parts = line.strip().replace(' ', ' ').split() %} {# 校验行格式,避免拆分后字段不足 #} {% if line_parts | length >=3 %} {% set month = line_parts[0] %} {% set day = line_parts[1] %} {% set time = line_parts[2] %} {{ [idm_padded, idc, month, day, time] | map('trim') | join(';') }} {% endif %} {% endfor %} {% endif %} {% endfor %}
方案2:任务注册时设置默认值
修改"Grep over syslog"任务,用failed_when: false替代ignore_errors: true(更精准控制失败状态),并给失败主机设置默认空的stdout_lines:
- name: Grep over syslog become: true ansible.builtin.shell: cmd: | tomorrow_date=$(date '+%Y-%m-%d' -d "$input_date + 1 day") six_days_ago_date=$(date '+%Y-%m-%d' -d "$input_date - 6 days") last_seven_days_syslogs=$(find /var/log/ -type f -name "syslog*" -newermt "$six_days_ago_date" ! -newermt "$tomorrow_date" | sort) sudo zgrep -aHF "my_pattern" $last_seven_days_syslogs | cut -d'[' -f 1 register: grep_output failed_when: false no_log: true # 给失败的主机设置默认空的stdout_lines vars: grep_output: "{{ {'stdout_lines': []} if grep_output.failed else grep_output }}"
这样模板中只需判断host.grep_output.stdout_lines是否非空即可,无需额外判断任务状态。
内容的提问来源于stack exchange,提问作者Tms91

