HowTo: Add string to an existing quoted string in file using lineinfile

Background

I needed to find a way to add new paths to a quoted line in the following file:
File: /etc/updatedb.com
Line: PRUNEPATHS = “/afs /media /net /sfs /tmp /udev /var/cache/ccache /var/spool/cups /var/spool/squid /var/tmp”

Should be simple. This is something I can do with awk/sed all day long. Well it turned out to not be that simple in Ansible, if I wanted to do it using the existing modules. So here is what I did, and it worked great. Of course if anyone knows how to simplify this I am all ears:

Play
(Explanation to follow)

  • name: Configure updatedb to ignore the /db path
    lineinfile:
    dest=/etc/updatedb.conf
    backrefs=True
    state=present
    regexp=‘(^PRUNEPATHS\s+=\s+)(?:“)([\w+\s/]+)(?<!/db)(?:”)’
    line=‘\1"\2 /db"’

Explanation:
I wanted a regex that would match the entire line if /db was not in it, and then create 2 capture groups from the data. Because I wanted to insert /db if it was missing, within the quoted section, I felt it was better to construct the line from parts removing the " characters. As you see from the line=, I add them back in.

Breakdown of Regex
(^PRUNEPATHS\s+=\s+) # I wanted to match "PRUNEPATHS = " and capture in capture group 1

(?:") # I wanted to search on the string with the " so it would match but did not want to capture the ". (?:…) does this.

([\w+\s/]+) # I wanted to match on any of the characters and formats this line would be in and assign this to capture group 2. Here I match on one or more alpha, spaces and / and any number of the previous combos, again only in the line starting with the first part of the regex. This effectively will match anything in format “/afs /media /net /sfs /tmp /udev /var/cache/ccache /var/spool/cups /var/spool/squid /var/tmp” that also meets the first element of the regex

(?<!/db) # This is a negative look behind assertion. I wanted to not match if the line contained /db anywhere as there is no need to add it if the line has it already. I did this here because at this stage the regex has the entire line minus the " in buffer, so makes sense to do the NLBA.

(?:") # Match on the final quote but don’t capture it.

What this gives me
A match result of the following completely constructed and ordered based on whats in the file: PRUNEPATHS = “/afs /media /net /sfs /tmp /udev /var/cache/ccache /var/spool/cups /var/spool/squid /var/tmp”
2 capture groups (1,2) and here is what they contain (minus the quotes):

1: "PRUNEPATHS = "
2: “/afs /media /net /sfs /tmp /udev /var/cache/ccache /var/spool/cups /var/spool/squid /var/tmp”

From there I construct the line that lineinfile will create
\1 # capture group 1 contents
" # escaped quote
\2 # capture group 2 contents
/db # Actual text I am appending
" # escaped quote

The line is changed from:
PRUNEPATHS = “/afs /media /net /sfs /tmp /udev /var/cache/ccache /var/spool/cups /var/spool/squid /var/tmp”

to:

PRUNEPATHS = “/afs /media /net /sfs /tmp /udev /var/cache/ccache /var/spool/cups /var/spool/squid /var/tmp /db”

Hopefully someone finds this useful.
Bill Clark

Almost always, I’ll recommend using the template module to just install the definitive remote file you want to be there.

The only reason to use lineinfile is when you have users logging on to the system (humans! eww! gross!) and need to be surgical.

It was basically designed for simple line insertions, the backrefs stuff is there, but if you feel more comfortable doing

shell: sed …

That’s ok! Don’t think it’s always bad thing.

It only is if you are trying to make your playbooks return “changed: 0” when they don’t change anything, and often, realistically, that’s not always a great concern.

But yes - the template module - 99% of the time - it’s the way to go.

Agree in most cases. With a lot of my servers though, there is the treat of “teh humans” so I needed to be surgical. Plus depending on how the host was imaged, sometimes this particular file may have different content.

That may be the level of complexity you are dealing with in this particular case.

It was originally written for basic insertions, though the backref stuff came later.

You can use updatedb -e. You can see it in action over here.