Skip to content

Selecting and Filtering Collections


This is the densest chapter in this section, and arguably the most useful — select, reject, selectattr, rejectattr, and map are what “complex data manipulation” actually looks like in practice: filtering a list down to what you care about, then pulling out just the piece you need.

Note

A few of these filters take a word like 'even' or 'equalto' as an argument — these are called Jinja2 tests, and they get a proper, full treatment in the next section, alongside when:. For this chapter, treat them as ready-made comparison keywords these filters accept, without worrying about the broader concept yet.

select And reject: Filtering A List Of Values

select keeps only the items in a list that pass a given test:

vars:
  numbers: [1, 2, 3, 4, 5, 6]
tasks:
  - name: Select even numbers
    ansible.builtin.debug:
      msg: "{{ numbers | select('even') | list }}"
[2, 4, 6]

reject keeps the opposite — everything that fails the test:

msg: "{{ numbers | reject('even') | list }}"
[1, 3, 5]

Note the trailing | list on both — like zip and dict2items | keys() from earlier chapters, these filters produce something that needs to be explicitly materialized into a real list.

selectattr And rejectattr: Filtering A List Of Dictionaries

select/reject work on plain values. selectattr/rejectattr do the same thing for a list of dictionaries, testing one specific key in each:

vars:
  servers:
    - name: web1
      status: running
    - name: web2
      status: stopped
    - name: db1
      status: running
tasks:
  - name: Select running servers
    ansible.builtin.debug:
      msg: "{{ servers | selectattr('status', 'equalto', 'running') | list }}"
[{"name": "web1", "status": "running"}, {"name": "db1", "status": "running"}]
msg: "{{ servers | rejectattr('status', 'equalto', 'running') | list }}"
[{"name": "web2", "status": "stopped"}]

map: Transforming Every Item In A List

map applies something to every item in a list at once. Its most common form pulls a single key out of a list of dictionaries:

msg: "{{ servers | map(attribute='name') | list }}"
["web1", "web2", "db1"]

It also works with an ordinary filter name, applied to every item:

msg: "{{ ['hello', 'world'] | map('upper') | list }}"
["HELLO", "WORLD"]

Putting It Together: selectattr Then map

This combination is where the real value shows up — filter down to what you want, then extract just the piece you actually need:

msg: "{{ servers | selectattr('status', 'equalto', 'running') | map(attribute='name') | list }}"
["web1", "db1"]

One line: “give me the names of every running server.” This kind of chained filter pipeline is exactly what this section has been building toward — reshaping a raw data structure into exactly the shape a later task actually needs.

A Gotcha: length Right After A Filter Might Fail

msg: "{{ numbers | select('even') | length }}"

This might raises an error — something like object of type 'generator' has no len(). select (and reject, selectattr, rejectattr) hand back a generator, not a concrete list, and generators don’t support length directly. The fix is the same | list you’ve been chaining onto these filters already, placed before | length:

msg: "{{ numbers | select('even') | list | length }}"
3

Best Practices

  • Chain | list after select/reject/selectattr/rejectattr before using the result for anything beyond immediately displaying it — counting it, indexing it, or passing it to a module parameter all need a real list, not a generator.
  • Combine selectattr with map(attribute=...) for the extremely common “filter, then extract one field” pattern — it’s one line instead of a much more awkward manual approach.
  • Don’t worry about fully understanding test names like 'even' and 'equalto' yet — they’re used here purely operationally, and get their proper introduction in the next section.
Last updated on