0

With loop or other, I can't see how to use the replace or inline module to achieve that.

The orinal file contains this line

goals first_goal + second_goal

I only want to modify the line starting with goals (with leading spaces or not)
With these possible values : A, B, C, D, E, F

For example

goals C + F

My role needs to replace this line with these correspondances (the first letter matching is just to simplify my example) :

A -> always
B -> back
C -> car
D -> dance
E -> even
F -> fast

So, for this example, the converted file will contains the line :

goals car + fast

(option+ if possible : several single letters can lead to the same value, e.g. A -> always , V -> always, Z -> always)

Here things are voluntarily limited, I have many more value possibles.
What would be the best way to do this ? Thank you.

troubadour
  • 253
  • 3
  • 10

4 Answers4

0

Here's one solution:

Given the replace_goal.txt test file

goals A + C
goals F + D
goals B + E

The following playbook:

---
- hosts: localhost
  gather_facts: false

  vars:
    # This is to run from localhost. Replace with correct path on your target
    file_dir_path: "{{ playbook_dir }}"

    # Mapping of letters to replace with their replacement value
    goals:
      A: always
      B: back
      C: car
      D: dance
      E: even
      F: fast

  tasks:
    - name: Replace goal letters with names
      ansible.builtin.replace:
        path: "{{ file_dir_path }}/replace_goal.txt"
        regexp: '(\+? ){{ item.key | regex_escape}}( ?)'
        replace: '\g<1>{{ item.value }}\g<2>'
      loop: "{{ goals | dict2items }}"

Gives:

PLAY [localhost] ***********************************************************************************************************************************************************************************************************************

TASK [Replace goal letters with names] *************************************************************************************************************************************************************************************************
changed: [localhost] => (item={'key': 'A', 'value': 'always'})
changed: [localhost] => (item={'key': 'B', 'value': 'back'})
changed: [localhost] => (item={'key': 'C', 'value': 'car'})
changed: [localhost] => (item={'key': 'D', 'value': 'dance'})
changed: [localhost] => (item={'key': 'E', 'value': 'even'})
changed: [localhost] => (item={'key': 'F', 'value': 'fast'})

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

And the new file content is now:

goals always + car
goals fast + dance
goals back + even
Zeitounator
  • 38,476
  • 7
  • 53
  • 66
  • Thank you very much @Zeitounator, I'm going to apply this for my real case and will do a feedback – troubadour Nov 10 '22 at 15:27
  • Maybe too permissive @Zeitounator, because lines like `yyy C` are also modified. I need to match `'^goals' or '^[[:blank:]]+goals'` (in posix) – troubadour Nov 10 '22 at 16:35
  • Then [edit] your question and provide a correct [mre]. – Zeitounator Nov 10 '22 at 16:37
  • It's done @Zeitounator, I also add the option `oneLetter -> several values` which supposes here to declare the `goals` variable with additional keys with the same value, so several lines more in the declaration, why not ? . (( I guess that declaring a dict of an other hash-like to only declare one line per single letter (so with all its values, if possible), will make the code hard to read )) – troubadour Nov 10 '22 at 17:45
0

Given the file and the dictionary below for testing

shell> cat /tmp/replace_goal.txt 
first line
goals
goals A + C
goals F + D
goals B + E
goals B
lastline
goals:
  A: always
  B: back
  C: car
  D: dance
  E: even
  F: fast
  1. Declare the variables
_keys: "{{ goals.keys()|join('') }}"
_match: '^\s*goals [{{ _keys }}] \+ [{{ _keys }}]$'
_group: '^\s*goals (.*) \+ (.*)$'
  1. Read the file either in local
out:
  stdout_lines: "{{ lookup('file', '/tmp/replace_goal.txt').splitlines() }}"

or from the remote host

    - command: cat /tmp/replace_goal.txt
      register: out
  1. Update the file
    - copy:
        dest: /tmp/replace_goal.txt
        content: |-
          {% for line in out.stdout_lines %}
          {% if line is regex(_match) %}
          {% set p1 = line|regex_search(_group, '\\1')|first %}
          {% set p2 = line|regex_search(_group, '\\2')|first  %}
          goals {{ goals[p1] }} + {{ goals[p2] }}
          {% else %}
          {{ line }}
          {% endif %}
          {% endfor %}

gives

TASK [copy] ****************************************************************
--- before: /tmp/replace_goal.txt
+++ after: /home/vlado/.ansible/tmp/ansible-local-5233878lj8lei0/tmpwhajexvl
@@ -1,7 +1,7 @@
 first line
 goals
-goals A + C
-goals F + D
-goals B + E
+goals always + car
+goals fast + dance
+goals back + even
 goals B
 lastline

Example of a complete playbook for testing

- hosts: localhost

  vars:

    goals:
      A: always
      B: back
      C: car
      D: dance
      E: even
      F: fast

    out:
      stdout_lines: "{{ lookup('file', '/tmp/replace_goal.txt').splitlines() }}"

    _keys: "{{ goals.keys()|join('') }}"
    _match: '^\s*goals [{{ _keys }}] \+ [{{ _keys }}]$'
    _group: '^\s*goals (.*) \+ (.*)$'

  tasks:

#    - command: cat /tmp/replace_goal.txt
#      register: out
    - copy:
        dest: /tmp/replace_goal.txt
        content: |-
          {% for line in out.stdout_lines %}
          {% if line is regex(_match) %}
          {% set p1 = line|regex_search(_group, '\\1')|first %}
          {% set p2 = line|regex_search(_group, '\\2')|first  %}
          goals {{ goals[p1] }} + {{ goals[p2] }}
          {% else %}
          {{ line }}
          {% endif %}
          {% endfor %}
Vladimir Botka
  • 58,131
  • 4
  • 32
  • 63
  • Thank you Vladimir, I just continue with the replace module proposed by Zeitounator for the moment, I enriched it for my goal but there's still a little thinq I don't understand. I'll my solution in few minutes. – troubadour Nov 14 '22 at 16:07
0

Continuing with the 'replace' module, I could enrich you solution Zeitounator but a little thing can't work :

map is now simplified : goals: A: always D: dance
original file : goals A+D
regexp '^(\s*goals\s+){{ item.key | regex_escape}}\s*(\+*.*)'
replace '\1 {{ item.value }} \2'
good result => goals always +D
Now the part after the + :
original file : goals A+D
regexp '^(\s*goals\s+\S+\s*\+)\s*{{ item.key | regex_escape}}(.*)'
same replace
good result => goals A+ dance
So, I can use now use an alternation, A will be matched first and D just after in the loop :
regexp '^(\s*goals\s+){{ item.key | regex_escape}}\s*(\+*.*)|^(\s*goals\s+\S+\s*\+)\s*{{ item.key | regex_escape}}(.*)'
With the original file, I get on stdout :
changed: [localhost] => (item={'key': 'A', 'value': 'always'}) changed: [localhost] => (item={'key': 'D', 'value': 'dance'})
but the result file is : dance ?

For me, ansible first build the line goals always +D and replace D at the second loop with the alternation to the right, no ?

troubadour
  • 253
  • 3
  • 10
0

Ansible does not seem to well buffer the first line replacement when a regex alternation is used, but it works with 2 tasks, one for each regexp :

tasks:
    - name: Replace goal left
      ansible.builtin.replace:
        path: "{{ file_dir_path }}/replace_goal.txt"
        regexp: '^(\s*goals\s+){{ item.key | regex_escape}}\s*(\+*.*)'
        replace: '\1 {{ item.value }} \2'
      loop: "{{ goals | dict2items }}"
    - name: Replace goal right
      ansible.builtin.replace:
        path: "{{ file_dir_path }}/replace_goal.txt"
        regexp: '^(\s*goals\s+\S+\s*\+)\s*{{ item.key | regex_escape}}(.*)'
        replace: '\1 {{ item.value }} \2'
      loop: "{{ goals | dict2items }}"
troubadour
  • 253
  • 3
  • 10