Occasionally, I get the opportunity to impress collegues with an elegant one-liner in our beloved language. This time, though, the advocacy didn't work out too well so far... ;)

The task is to go through a number of config files and fix up a certain entry if it exists, or else (if the entry doesn't exist) append it to one of the files (last file processed is fine, but so is any other).

It's important the entry does not exist in multiple files as a result of the fixing, and it's preferable to leave it where it was, in case it was found. It can be assumed the entry exists exactly only one or zero times in the original input files. Also, files should be edited in place.

Somewhat simplified:

Case 1 — entry "opt=foo" already exists, fix it with s/opt=.*/opt=bar/:

input: desired output: file1: file1: line1 line1 opt=foo opt=bar line3 line3 file2: file2: line1 line1 line2 line2 line3 line3

Case 2 — entry doesn't exist, append "opt=bar" to last file:

input: desired output: file1: file1: line1 line1 line2 line2 line3 line3 file2: file2: line1 line1 line2 line2 line3 line3 opt=bar

The first idea was to use an END block to conditionally output the last line

$ perl -i -pe '$f||=s/opt=.*/opt=bar/; END { print "opt=bar\n" if !$f +}' file1 file2

This doesn't work, however, because the print goes to stdout, instead of the file being edited in-place. (print ARGV ... doesn't work either; $! says "Bad file descriptor", presumably because ARGV is already closed at that point.)

So, next idea was to use eof to be able to move the test into the the loop implied by -p.  However, while this in principle works fine with eof (which is true at the end of every file), it doesn't work with eof(), which should be true only once after all files have been processed — which is what would really be needed to avoid multiple insertions of the entry in question.

Test script:

#!/bin/bash #echo -e "line1\nopt=foo\nline3" >file1 # case 1 echo -e "line1\nline2\nline3" >file1 # case 2 echo -e "line1\nline2\nline3" >file2 echo --- cat before --- cat file1 file2 echo --- perl output to stdout --- perl -i -pe '$f||=s/opt=.*/opt=bar/; $_.="opt=bar\n" if eof() && !$f' +file1 file2 echo --- cat after --- cat file1 file2

Output:

--- cat before --- line1 line2 line3 line1 line2 line3 --- perl output to stdout --- line3 opt=bar --- cat after --- line1 line2 line3 line1 line2

Desired output:

--- cat before --- line1 line2 line3 line1 line2 line3 --- perl output to stdout --- --- cat after --- line1 line2 line3 line1 line2 line3 opt=bar

Interestingly, not even case 1 (where the eof() wouldn't really be needed) works. Here, the last line ("line3") is also being written to stdout instead of to the file, in case eof() is being tested...

Can anyone explain what's going on here?  Is this behavior documented somewhere?  (Of course, there are many more or less clumsy ways to solve this another way, but I'm primarily interested in why this approach doesn't do what I'd expected.)

Update: Solution: in cases like these, replace eof() with !@ARGV && eof, which doesn't have the undesirable side effect...


In reply to perl -i -pe ... with eof() testing by Anonyrnous Monk

Title:
Use:  <p> text here (a paragraph) </p>
and:  <code> code here </code>
to format your post, it's "PerlMonks-approved HTML":



  • Posts are HTML formatted. Put <p> </p> tags around your paragraphs. Put <code> </code> tags around your code and data!
  • Titles consisting of a single word are discouraged, and in most cases are disallowed outright.
  • Read Where should I post X? if you're not absolutely sure you're posting in the right place.
  • Please read these before you post! —
  • Posts may use any of the Perl Monks Approved HTML tags:
    a, abbr, b, big, blockquote, br, caption, center, col, colgroup, dd, del, details, div, dl, dt, em, font, h1, h2, h3, h4, h5, h6, hr, i, ins, li, ol, p, pre, readmore, small, span, spoiler, strike, strong, sub, summary, sup, table, tbody, td, tfoot, th, thead, tr, tt, u, ul, wbr
  • You may need to use entities for some characters, as follows. (Exception: Within code tags, you can put the characters literally.)
            For:     Use:
    & &amp;
    < &lt;
    > &gt;
    [ &#91;
    ] &#93;
  • Link using PerlMonks shortcuts! What shortcuts can I use for linking?
  • See Writeup Formatting Tips and other pages linked from there for more info.