Parthian Shot
Parthian Shot

Reputation: 1432

how to select regex matches in jinja2?

toy example

Essentially, I want to do something like:

['hello', 'apple', 'rare', 'trim', 'three'] | select(match('.*a[rp].*'))

Which would yield:

['apple', 'rare']

what am I talking about?

The match filter and select filter. My issue arises from the fact that the select filter only supports unary "tests".

I'm on Ansible 1.9.x.

my actual use case

...is closer to:

lookup('dig', ip_address, qtype="PTR", wantList=True) | select(match("mx\\..*\\.example\\.com"))

So, I want to get all the PTR records associated with an IP and then filter out all the ones that don't fit a given regex. I'd also want to ensure that there's only one element in the resulting list, and output that element, but that's a different concern.

Upvotes: 14

Views: 63226

Answers (4)

MVCaraiman
MVCaraiman

Reputation: 629

I found the following trick if you want to filter a list in Ansible (get the list with null values and make difference with null list) :

---
- hosts: localhost
  connection: local
  vars:
    regexp: '.*a[rp].*'
    empty: [null]
  tasks:
    - set_fact: matches="{{ ['hello', 'apple', 'rare', 'trim', 'three'] | map('regex_search',regexp)  | list|difference(empty) }}"
    - debug: msg="{{ matches }}"

Here is the output :

ok: [localhost] => {
    "msg": [
        "apple", 
        "rare"
    ]
}

Upvotes: 4

Marko Kohtala
Marko Kohtala

Reputation: 932

You can use select like this:

['hello', 'apple', 'rare', 'trim', 'three'] | select('match', '.*a[rp]')

Which would yield:

['apple', 'rare']

The match uses the re.match implementation and hence matches at beginning of string. Therefore you need .* at beginning, but not at the end of the regexp.

Or you can use search to avoid .* altogether:

['hello', 'apple', 'rare', 'trim', 'three'] | select('search', 'a[rp]')

Upvotes: 5

Konstantin Suvorov
Konstantin Suvorov

Reputation: 68239

Will this do?

---
- hosts: localhost
  connection: local
  vars:
    my_list: ['hello', 'apple', 'rare', 'trim', 'three']
    my_pattern: '.*a[rp].*'
  tasks:
    - set_fact: matches="{{ my_list | map('regex_search',my_pattern) | select('string') | list }}"
      failed_when: matches | count > 1
    - debug: msg="I'm the only one - {{ matches[0] }}"

Update: how it works...

  • map applies filters – filters are not yes/no things, they applied to every item of input list and return list of modified items. I use regex_search filter, which search pattern in every item and return a match if found or None if there is no match. So on this step I get this list: [null, "apple", "rare", null, null].

  • Then we use select, which applies tests – tests are yes/no things, so they reduce the list based on selected test. I use string test, which is true when item of a list is string. So we get: ["apple", "rare"].

  • map and select give us some internal python types, so we convert to list by applying list filter after all.

Upvotes: 19

Parthian Shot
Parthian Shot

Reputation: 1432

This design pattern worked for me:

----
- hosts: localhost
  connection: local
  vars:
    my_list: ['hello', 'apple', 'rare', 'trim', "apropos", 'three']
    my_pattern: 'a[rp].*'
  tasks:
    - set_fact:
        matches: "{%- set tmp = [] -%}
                  {%- for elem in my_list | map('match', my_pattern) | list -%}
                    {%- if elem -%}
                      {{ tmp.append(my_list[loop.index - 1]) }}
                    {%- endif -%}
                  {%- endfor -%}
                  {{ tmp }}"
    - debug:
        var: matches
      failed_when: "(matches | length) > 1"

Upvotes: 3

Related Questions