Append new line if not exist using Simplelines.lns
Hi,
I have a file in this format:
# comment sample 1
# comment sample 2
line 1 value # comment line 1
line 2 value # comment line 2
Include /some/file
Include /some/other/file
I am trying to append a new line at the end of file if that line doesn't already exist using Simplelines and I don't know what I am missing.
Can you please give me an example of how to do this? I am using the right lens?
Thank you!
Best to show what you've tried and what you got.
Hi @MatthewHannigan,
I found this tutorial https://github.com/hercules-team/augeas/wiki/Adding-nodes-to-the-tree and i was able to add a new line using this:
# augtool -At "Simplelines.lns incl /root/tmp"
augtool> print /files/root/tmp
/files/root/tmp
/files/root/tmp/#comment[1] = "comment sample 1"
/files/root/tmp/#comment[2] = "comment sample 2"
/files/root/tmp/1 = "line 1 value # comment line 1"
/files/root/tmp/2 = "line 2 value # comment line 2"
augtool> ins 01 before /files/root/tmp/1
augtool> set /files/root/tmp/01 'Include /some/file'
augtool> print /files/root/tmp
/files/root/tmp
/files/root/tmp/#comment[1] = "comment sample 1"
/files/root/tmp/#comment[2] = "comment sample 2"
/files/root/tmp/01 = "Include /some/file"
/files/root/tmp/1 = "line 1 value # comment line 1"
/files/root/tmp/2 = "line 2 value # comment line 2"
augtool> save
Saved 1 file(s)
augtool> quit
# cat /root/tmp
# comment sample 1
# comment sample 2
Include /some/file
line 1 value # comment line 1
line 2 value # comment line 2
Now I am trying to use the 'match' method to find if the line is already there:
augtool> match /files/root/tmp/*[. = 'Include']
(no matches)
augtool> match /files/root/tmp/*[. =~ 'Include']
error: Invalid path expression
error: type error
/files/root/tmp/*[. =~ 'Include']|=|
augtool>
What I am trying to do is, to add some 'Include /path/to/some/files' lines on the file, but only if the line is not already there, no matter at what position.
The match function will return a code so I can use it on this chef resource: https://supermarket.chef.io/cookbooks/augeas For example:
augeas 'add_included_files' do
changes ['ins 01 before /files/root/tmp/1', 'set /files/root/tmp/01 Include /some/file']
lens 'Simplelines.lns'
incl '/root/tmp'
run_if "match /files/root/tmp/*[. != 'Include /some/file']"
end
Can you please guide me how to use the matc function, or to tell me another way to check if a line is not already on a file?
Thank you very much.
You are very close ! If this was about returning a successful match if the line Include /some/file was there, you could write
run_if "match /files/root/tmp/*[. = 'Include /some/file']"
But that's reversed from what you want: you want to run if there is no node with Include /some/file. If there was a run_unless in Chef, you'd be done. We therefore need to find a way to match if the line is not there, which is unfortunately a little more involved.
The construct /files/root/tmp/*[ . = 'Include /some/file'] loops over all nodes underneath /files/root/tmp and returns those that match the condition in [ ... ]. The construct /files/root/tmp/*[ . != 'Include /some/file'] is not the negation of this: that expression matches all nodes whose content is not Include /some/file, and so you'll get matches as long as there are lines other than Include /some/file in the file.
One way to resolve this is to do the following:
run_if "match /files/root/tmp[count(*[. = 'Include /some/file']) = 0]"
The argument to count is a list of nodes; in this case, we count all the nodes underneath /files/root/tmp that have Include /some/file and produce a match when that count is 0. The match is /files/root/tmp, but that doesn't matter since we are only interested in whether something matches at all or not for run_if.
BTW, your changes will stick the new line into the beginning of the file. If you want to append them, you'd do
changes ['ins 01 after /files/root/tmp/*[last()]', 'set /files/root/tmp/01 Include /some/file']
One other small thing: in newer versions of Augeas (since 1.10), you can write ['something'] as a shorthand for [. = 'something'] - but that's merely cosmetic.
Thank you @lutter !
Your command runs fine for me. I still have issues to use that on chef resource.
I've opened an issue on their side: https://github.com/nhuff/chef-augeas/issues/12 and hope somebody will guide me in the right direction.
I just looked through the Chef code, and from reading the code (i.e., I haven't tried to run anything ;) ), it looks like you can actually check for the size of a match. IOW, something like this should work, too:
run_if "match /files/root/tmp/*[ . = 'Include /some/file'] size == 0"
Hi @lutter, many thanks! This is it, everything works now.
Now, if you don't mind, I will like to know how to combine all of these in a single augtool command. I mean to not enter on augtool shell at all. If this is possible.. I will like to use this in future from command line too, instead of using sed/perl or something else.
augeas 'include_some_file' do
changes ['ins 01 before /files/root/tmp/1', 'set /files/root/tmp/01 Include /some/file']
lens 'Simplelines.lns'
incl '/root/tmp'
run_if "match /files/root/tmp/*[. = 'Include /some/file'] size == 0"
end
Thank you very much again for your help.
You can't do it straightup in augtool since that has no if statement. But you can do it as a shell script: this gist shows how that could be done.
Since using augtool in the if statement in that script is sorta brittle, I created PR #563 to do this kind of check with augmatch - but that's of course not yet in a released version of augeas.
See also: #68