r/ansible•
JoeReally

LineInFile: How to replace value in a line (or append if missing)

My limited regex has failed me. I am guessing I need a look-around of some kind but I have not reached that level yet.

I want to replace a value in a line if it exists, and append the value to that line if it does not. (This is a one-line file).

Here is what I have so far:

  tasks:
    - name: Enable Screen Timeout
      lineinfile:
        path: /boot/cmdline.txt.bak
        regexp: '^(.*?\\s)?(consoleblank=\\d*)(.*)$'
        line: '\1consoleblank=900\3'
        backrefs: true
      become: yes

Test examples:

abc=123 d=4 w=6 xyz=789
abc=123 d=4 consoleblank=0
abc=123 d=4 consoleblank=0 w=6 xyz=789
consoleblank=0 w=6 xyz=789

Am I being too aggressive with this one task? Do I need to use multiple tasks (like a replace and a lineinfile)? Please advise. Thanks.

Posted Jan 30, 2020, 8:03 PM UTC

09
madlichking13•

Look at the literal Replace module. I had issues with things like that. Replace did the trick.

2
JoeReallyOP•

But replace wouldn’t help if there isn’t already a value there. Right?

1
madlichking13•

Empty space can be replaced with not empty space if your checking for it in your regex. It may be worth doing two replaces though. One checking for the existing value and one checking for the empty space. Either way only one triggers.

2
JoeReallyOP•

But it wouldn’t be empty. It wouldn’t be there in the list of values, but there wouldn’t be a “gap” of some kind. And I don’t see a way to use replace module when the regex is NOT met.

1
madlichking13•

So for the first part, about replacing if present. Replace will work. That is easy and sets us up for the second part. The second part could likely be resolved by registered a variable during the first replace. Something like...

tasks:
    - name: Enable Screen Timeout
      replace:
        path: /boot/cmdline.txt.bak
        regexp: '^(.*?\\s)?(consoleblank=\\d*)(.*)$'
        replace: '\1consoleblank=900\3'
      become: yes
      register: foobar

I just copied your example, so your check might actually need to be different just to replace the expected value.

Then to append if not present at all, go with something like lineinfile to replace the whole thing with what you want, based on if the first task actually changed anything or not.

    - name: Enable Screen Timeout
      lineinfile:
        path: /boot/cmdline.txt.bak
        regexp: '^(.*?\\s)?(consoleblank=\\d*)(.*)$'
        line: '\1consoleblank=900\3'
        backrefs: true
      become: yes
      when: foobar.changed != Changed

The when statement will need to be tested and probably properly formatted, but the idea is there. We only want the second task to trigger when the first fails to change anything (i.e. Does not find the value to replace in the first place)

​

Sorry if this is a bit all over the place. Trying to write it up in between my kid wanting my full attention lol

​

I hope this makes sense and may help.

2
CalebC14•

I've always had trouble with lineinfile. One alternative approach maybe to move a file or a template with the entry you are looking to add or edit already in place.

1
JoeReallyOP•

That’s not an option with this file as it has generated content that is specific to each device (a partition id). Plus, I’m curious to know the right way for future situations.

1
yazny•

I thought this problem was kind of interesting so I messed around with another method, one that doesn't use lineinfile or replace, but is a bit more complicated...

You can convert the cmdline.txt into a dict, and then update that with any values you want. If the value already exists, it will be updated, if not, it'll be added to the dict. Then you can just write out the dict as key=value pairs to the cmdline.txt file.

vars:
  cmdline_options:
    consoleblank: 900
    w: 10

tasks:
  - name: Read cmdline.txt
    set_fact:
      cmdline: "{{ lookup('file', 'cmdline.txt') }}"

  - name: Create dict from options
    set_fact:
      cmdline_dict: |
        {% set d = {} %}
        {% for item in cmdline.split(' ') %}
        {% set k, v = item.split('=') %}
        {% set _ = d.update({k: v}) %}
        {% endfor %}
        {{ d }}

  - name: Update options
    set_fact:
      cmdline_dict: "{{ cmdline_dict|combine(cmdline_options) }}"

  - name: Create new cmdline
    set_fact:
      cmdline: |
        {% for k, v in cmdline_dict.items() %} {{ k }}={{ v }}{% endfor %}

  - name: Save cmdline to file
    debug:
      msg: "{{ cmdline|trim }}"

This assumes that there are no weird characters in the cmdline.txt file and I'm sure it could be optimized.

However, it would give you the ability to add/change other options (for example, adding w: 10 to cmdline_options would update the w option as well) relatively easily.

1