I need to parse a text file for gaps in a numbered list, then select the first missing number as an Ansible variable.
For example, with a list that looks like this:
100
101
102
103
104
106
107
109
110
My requirement is to identify the first integer missing in the sequence 100..111
from this file, in this case, it would need to select 105
.
I am currently using lineinfile
to parse the text:
- lineinfile:
dest: target.txt
regexp: "{{ item }}"
state: absent
check_mode: yes
register: search
loop: "{{ range(100, 111) | list }}"
This gives me a dictionary called search
that contains, among other things, a found
value for each number. I'd like to filter the dictionary to select the first number occurring with value found: '0'
, but have so far been unsuccessful.
Due to an outdated version of Ansible and Jinja2 (that can't be updated for reasons), I don't have access to the selectattr
or rejectattr
filters, which I know would be the preferred way of accomplishing this. I'm currently trying to develop a workaround using json_query but haven't had any luck yet.
I don't care how cumbersome the final code is.
CodePudding user response:
You could do a simple loop, with the conditions:
- the variable is not yet defined
found
is zero
- set_fact:
first_missing: "{{ item.item }}"
loop: "{{ search.results }}"
when: first_missing is not defined and item.found == 0
loop_control:
label: "{{ item.item }}"
This would yield:
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)
And you effectively ends with 105
inside the variable first_missing
.
But your idea to have a JMESPath query is pretty clever, and can do it too, the set_fact
becomes as simple as:
- set_fact:
first_missing: >-
{{
search.results
| json_query('[?found == `0`].item | [0]')
}}
Where
- the expression in the square brackets
?found == `0`
allows you to select all the element of the list having afound
equal to zero .item
only select theitem
attribute of the dictionaries in the list- and the last bit resets the projection to select the first item of the list
| [0]
CodePudding user response:
You can do this a much simpler way IMO with a very simple set of filters:
- read your entries from your file with the
file
lookup. If the file is remote,fetch
it first from the target orslurp
its content into a var split
the content from the file on the new line char to obtain a list- map the
int
function to each list element to transform the parsed strings to integers - apply the
difference
filter against the range of numbers you are looking for - keep the fist element of the result list (after sorting for security)
Simply put as code taking for granted your file is locally available in target.txt
- name: find first missing number
debug:
msg: "{{ range(100, 111) | list | difference(lookup('file', 'target.txt').split('\n') | map('int')) | sort | first }}"
If your original list can contain values outside of the given explored range, you can simply reject the values out-of-range prior to selecting the first result
- 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