我需要解析文本文件中编号列表中的空白,然后选择第一个缺失的数字作为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的过时版本(由于某些原因无法更新),我无法访问selectattr
或rejectattr
过滤器,我知道这将是完成此任务的首选方式。我目前正在尝试使用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
在我看来,你可以用一组非常简单的过滤器更简单地做到这一点:
- 使用
file
查找从文件中读取条目。如果文件是远程的,fetch
首先将其从目标中取出,或者slurp
将其内容放入var 中。 split
将内容从文件上的新行字符中获取一个列表- 将
int
函数映射到每个列表元素,将解析后的字符串转换为整数 - 应用
difference
过滤器对你正在寻找的数字范围 - 保留结果列表的第一个元素(排序后的安全性)
简单地放在代码中,想当然地认为您的文件在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]'