在不'selectattr'的情况下过滤 ansible 字典属性值



我需要解析文本文件中编号列表中的空白,然后选择第一个缺失的数字作为Ansible变量。

例如,使用如下的列表:

100
101
102
103
104
106
107
109
110

我的要求是从这个文件中识别序列100..111中缺失的第一个整数,在这种情况下,它需要选择105

我目前使用lineinfile来解析文本:

- lineinfile:
dest: target.txt
regexp: "{{ item }}"
state: absent
check_mode: yes
register: search
loop: "{{ range(100, 111) | list }}"

这给了我一个名为search的字典,其中包含每个数字的found值。我想过滤字典以选择第一个值为found: '0'的数字,但到目前为止还没有成功。

由于Ansible和Jinja2的过时版本(由于某些原因无法更新),我无法访问selectattrrejectattr过滤器,我知道这将是完成此任务的首选方式。我目前正在尝试使用json_query开发一个解决方案,但还没有任何运气。

我不在乎最后的代码有多麻烦。

你可以做一个简单的循环,条件是:

  • 变量尚未定义
  • found为零
- set_fact:
first_missing: "{{ item.item }}"
loop: "{{ search.results }}"
when: first_missing is not defined and item.found == 0
loop_control:
label: "{{ item.item }}"

这将产生:

TASK [set_fact] **************************************************************
skipping: [localhost] => (item=100) 
skipping: [localhost] => (item=101) 
skipping: [localhost] => (item=102) 
skipping: [localhost] => (item=103) 
skipping: [localhost] => (item=104) 
ok: [localhost] => (item=105)
skipping: [localhost] => (item=106) 
skipping: [localhost] => (item=107) 
skipping: [localhost] => (item=108) 
skipping: [localhost] => (item=109) 
skipping: [localhost] => (item=110)

并且在变量first_missing中有效地以105结束。


但是您的JMESPath查询的想法非常聪明,并且也可以做到,set_fact变得像:

一样简单:
- set_fact:
first_missing: >-
{{
search.results
| json_query('[?found == `0`].item | [0]')
}}

,

  • 方括号中的表达式?found == `0`允许您选择列表中found等于零的所有元素
  • .item只选择列表中字典的item属性
  • 和最后一位重置投影选择列表的第一项| [0]

第三种更快的解决方案是在copy模块上使用diff模式和check_mode模式,重新创建没有间隙的文件:

- copy:
content: >-
{% for i in range(100, 111) -%}
{{ i }}
{% endfor -%}
dest: target.txt
diff: yes
check_mode: yes
register: file

这将产生:

TASK [copy] *******************************************************************
--- before: target.txt
+++ after: /root/.ansible/tmp/ansible-local-36701z60diga/tmp2ao9r38b
@@ -3,7 +3,9 @@
102
103
104
+105
106
107
+108
109
110
changed: [localhost]

遗憾的是,注册的结果不像输出那么流畅,并且给出了文件的一个大的前后状态,因此您退回到类似于@Zeitounator:

提出的解决方案:
- set_fact:
first_missing: >-
file.diff.0.after.split()
| difference(file.diff.0.before.split())
| first

在我看来,你可以用一组非常简单的过滤器更简单地做到这一点:

  1. 使用file查找从文件中读取条目。如果文件是远程的,fetch首先将其从目标中取出,或者slurp将其内容放入var
  2. 中。
  3. split将内容从文件上的新行字符中获取一个列表
  4. int函数映射到每个列表元素,将解析后的字符串转换为整数
  5. 应用difference过滤器对你正在寻找的数字范围
  6. 保留结果列表的第一个元素(排序后的安全性)

简单地放在代码中,想当然地认为您的文件在target.txt

中本地可用。
- name: find first missing number
debug:
msg: "{{ range(100, 111) | list | difference(lookup('file', 'target.txt').split('n') | map('int')) | sort | first }}"

如果您的原始列表可以包含超出给定搜索范围的值,您可以在选择第一个结果之前简单地拒绝超出范围的值

- name: find first missing number
vars:
first: 100
last: 110
my_range: "{{ range(first, last+1) | list }}"
my_numbers: "{{ lookup('file', 'target.txt').split('n') | map('int') }}"
my_diff: "{{ my_range | difference(my_numbers) }}"
my_diff_in_range: "{{ my_diff | reject('<', first) | reject('>', last) }}"
my_result: "{{ my_diff_in_range | sort | first }}"
debug:
var: my_result

创建一个过滤器,例如

shell> cat filter_plugins/range_gaps.py
def range_gaps(a):
gaps = []
for i in range(len(a) - 1):
if a[i + 1] - a[i] > 1:
gaps.extend(range(a[i] + 1, a[i + 1]))
return (gaps)

class FilterModule(object):
def filters(self):
return {
'range_gaps': range_gaps,
}

测试它。

下面的任务
- debug:
msg: "{{ item|range_gaps|to_json }}"
loop:
- []
- [100]
- [100, 101]
- [100, 104]
- "{{ lookup('file', 'file.txt').splitlines()|map('int')|sort }}"

给(简略)

msg: '[]'
msg: '[]'
msg: '[]'
msg: '[101, 102, 103]'
msg: '[105, 108]'

最新更新