8

I have a variable file in ansible like below

check:
       - file1.tar.gz
       - file2.tar.gz

while iterating it in tasks i am using {{item}}

with_items: - "{{check}}",

Is there a way to extract the filenames without extension while iterating? i.e i need file1 from file1.tar.gz and file2 from file2.tar.gz

doc_noob
  • 555
  • 2
  • 10
  • 20
  • 1
    Be aware, that in general, there is no such thing as a file extension. In `file1.tar.gz` every character belongs to the file name. In the end, it depends on what _you_ agree with others what is considered a file extension. Without an agreement, no one will be able to determine the file extension of `filename.separated.by.dots.tar.gz.pdf.bmp.jpg`. – stackprotector Apr 12 '22 at 09:27

2 Answers2

11

Ansible has as splitext filter but unfortunately it only splits the extension after the last dot.

The only solution to reliably achieve your requirement IMO is to extract the characters before the first dot using either the split() python method available on string objects or the regex_replace filter

The regexp solution is a bit of an overkill for your current requirement. Meanwhile it is very flexible as you can easily adapt it to more complex situations (matching a semantic version in the name, look for a particular pattern). Moreover, since it is a filter (vs a python native method for .split()), you can either use it:

  • while you loop, applying the filter on each item
  • before you loop applying it to the entire list with the map filter.

Here is an example for each solution in the below playbook:

---
- name: Extract file name without extension(s)
  hosts: localhost
  gather_facts: false

  vars:
    check:
      - file1.tar
      - file2.tar.gz
      - file3.tar.bz2.back
      - a_weird_file.name.with.too.many.dots

    file_regex: >-
      ^([^\.]*).*

  tasks:
    - name: use the split() function
      debug:
        msg: >-
          {{ item.split('.') | first }}
      loop: "{{ check }}"

    - name: Apply regex filter while looping
      debug:
        msg: >-
          {{ item | regex_replace(file_regex, '\1') }}
      loop: "{{ check }}"

    - name: Apply regex filter on list before loop
      debug:
        var: item
      loop: >-
        {{ check | map('regex_replace', file_regex, '\1') | list }}

Here is the result.

Note: for my own knowledge, I used the profile_task callback plugin when running the playbook. You will see that on this small list of files, each method has an equivalent performance

$ ansible-playbook playbook.yml 

PLAY [Extract file name without extension(s)] *************************************************

TASK [use the split() function] ************************************************************
Friday 02 April 2021  18:53:14 +0200 (0:00:00.019)       0:00:00.019 ********** 
ok: [localhost] => (item=file1.tar) => {
    "msg": "file1"
}
ok: [localhost] => (item=file2.tar.gz) => {
    "msg": "file2"
}
ok: [localhost] => (item=file3.tar.bz2.back) => {
    "msg": "file3"
}
ok: [localhost] => (item=a_weird_file.name.with.too.many.dots) => {
    "msg": "a_weird_file"
}

TASK [Apply regex filter while looping] ****************************************************
Friday 02 April 2021  18:53:14 +0200 (0:00:00.056)       0:00:00.075 ********** 
ok: [localhost] => (item=file1.tar) => {
    "msg": "file1"
}
ok: [localhost] => (item=file2.tar.gz) => {
    "msg": "file2"
}
ok: [localhost] => (item=file3.tar.bz2.back) => {
    "msg": "file3"
}
ok: [localhost] => (item=a_weird_file.name.with.too.many.dots) => {
    "msg": "a_weird_file"
}

TASK [Apply regex filter on list before loop] **********************************************
Friday 02 April 2021  18:53:14 +0200 (0:00:00.056)       0:00:00.132 ********** 
ok: [localhost] => (item=file1) => {
    "ansible_loop_var": "item",
    "item": "file1"
}
ok: [localhost] => (item=file2) => {
    "ansible_loop_var": "item",
    "item": "file2"
}
ok: [localhost] => (item=file3) => {
    "ansible_loop_var": "item",
    "item": "file3"
}
ok: [localhost] => (item=a_weird_file) => {
    "ansible_loop_var": "item",
    "item": "a_weird_file"
}

PLAY RECAP *********************************************************************************
localhost                  : ok=3    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

Friday 02 April 2021  18:53:14 +0200 (0:00:00.057)       0:00:00.189 ********** 
=============================================================================== 
Apply regex filter on list before loop --------------------------------- 0.06s
Apply regex filter while looping --------------------------------------- 0.06s
use the split() function ----------------------------------------------- 0.06s 
Zeitounator
  • 38,476
  • 7
  • 53
  • 66
  • 2
    Good example. The only caveat would be if your file names contain multiple periods (ex: file-v1.2.3.tar.gz). A user might have to make a list of your common file extensions and remove those when they appear at the end of the full filename. – dan_linder May 21 '20 at 16:08
  • 1
    ...or adapt the regex so that it takes into account the `v` + semantic versioning in your specific example. – Zeitounator May 21 '20 at 23:02
6

Try this I used this in my code like:

  vars:
    check:
      - file1.tar.gz
      - file2.tar.gz

  tasks:
    - name: Apply filter while looping
      debug:
        msg: >-
          {{ item | splitext | first | splitext | first }}
      loop: "{{ check }}"
Sunil Shakya
  • 8,097
  • 2
  • 17
  • 20
  • OP's requirement was to get the name before the first dot. The above will work when the file contains 2 or less dots in the name, but will fail with e.g. `file3.tar.bz2.bak` – Zeitounator Apr 02 '21 at 16:08