ansiblejinja2ansible-template

Sorting list in Ansible in natural alphanumeric order


Is there a way to sort a list in Ansible or Jinja in a natural way?

For example this is the list

test
test123
test12
test5
test1234test
test22te

And I need it to take in account the numbers as whole not as individual so test12 is under test5 and so on.


Solution

  • Given the list

      l1:
        - test
        - test123
        - test12
        - test5
        - test1234test
        - test22te
    

    Create a list with an attribute index of type integer, e.g.

        - set_fact:
            l2: "{{ l2|default([]) +
                    [{'index': (_index|length > 0)|ternary(_index|int, 0),
                      'name': item}] }}"
          loop: "{{ l1 }}"
          vars:
            _regex: '^test(\d*)\D*$'
            _replace: '\1'
            _index: "{{ item|regex_replace(_regex, _replace) }}"
        - debug:
            msg: "{{ l2|sort(attribute='index')|
                        map(attribute='name')|
                        list }}"
    

    gives

      msg:
      - test
      - test5
      - test12
      - test22te
      - test123
      - test1234test
    

    Without iteration, declare the variables

      _regex: '^test(\d*)\D*$'
      _replace: '\1'
      _index: "{{ l1|map('regex_replace', _regex, _replace)|map('int')|list }}"
      l2: "{{ dict(_index|zip(l1))|
                          dict2items|
                          sort(attribute='key')|
                          map(attribute='value')|
                          list }}"
    

    gives the same result

      l2:
      - test
      - test5
      - test12
      - test22te
      - test123
      - test1234test
    

    Example of a complete playbook for testing

    - hosts: localhost
    
      vars:
    
        l1:
          - test
          - test123
          - test12
          - test5
          - test1234test
          - test22te
    
        _regex: '^test(\d*)\D*$'
        _replace: '\1'
        _index: "{{ l1|map('regex_replace', _regex, _replace)|map('int')|list }}"
        l2: "{{ dict(_index|zip(l1))|
                            dict2items|
                            sort(attribute='key')|
                            map(attribute='value')|
                            list }}"
    
      tasks:
    
        - debug:
            var: _index
        - debug:
            var: l2