[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

Re: Replace line in file based on pattern



I'm rearranging the order of the quoted sections.

On Sun, Jan 02, 2022 at 08:14:47PM -0500, The Wanderer wrote:
> That's very interesting, although not all that accessible to the
> relative newcomer to the field. It does leave me sad about the apparent
> conclusion that there is no safe way to edit a file programmatically
> (i.e., not via an interactive editor, and possibly not at all) in a way
> which preserves the inode; the reasons why I thought of that as
> desirable seem less clearly so on further analysis, but it's still
> unfortunate for a thing to not be possible to do.

There are several things going on here.  I'll address two of them.

First: in order to "preserve the inode", you would need to open the
file in either read+write mode, or clobber+write mode, and then start
overwriting the file once you have the content in memory.  This is
inherently dangerous, because if the writing is interrupted before
completion, the file is now corrupted (in the read+write case), or
truncated (in the clobber+write case).

The writing can be interrupted if the process is terminated, or if the
system crashes.  Even with uninterruptible power supplies, there's no
100% sure way to prevent a system crash, so there is always a risk.

Second: the idea that a program should be altering (overwriting) the
contents of configuration files *at all* is sketchy.  It's a really
bad design.  The types of files that people usually want to alter with
sed are designed to be edited by humans.  They are *not* amenable to
programmatic alteration, which is why the answers that people come up
with are so contorted.

If you really need to alter a textual configuration file on a bunch of
servers, I suggest editing the file by hand on one machine, making a
diff out of that, and then using patch to apply that diff on all the
other machines.  Or use ansible, chef, puppet, etc.  This is not a new
or unique problem.  Stop reinventing wheels.

A much better approach is what people have started doing with ".d"
directories.  Instead of a single monolithic text configuration file
that requires a human brain to comprehend, you put individual pieces
of configuration in separate files.  If you want to change the phlogiston
level, you just put the new value in the foobar.d/phlogiston file.  No
need for a parser to find the phlogiston variable in a text file.

Of course, the program being configured has to be written to support
such a thing.

> > unicorn:~$ printf %s\\n foo foo foo | awk '!done && /foo/{$0="bar"; done=1} 1'
> > bar
> > foo
> > foo
> 
> I keep forgetting that printf is even a thing in shell, and while awk
> did occur to my mind, I don't know it well enough to do anything useful
> in it. I can only kind-of parse the syntax you gave there, and that only
> because I just saw very similar-looking syntax given for sed in the
> Stack Overflow answer linked above.

An awk program consists of a series of alternating conditions and actions.
Each line of input is matched against the conditions, in order.  If any
of the conditions matches the input line, then the corresponding action
block is executed.

In my example, there are two condition/action pairs:

1) Condition: !done && /foo/
   Action: {$0="bar"; done=1}

2) Condition: 1 (always true)
   Action: omitted, so the default action of "print" is used.

The second pair could be written out more explicitly, but I used the
extremely condensed form.

So, what this does is look for lines containing the regex "foo", which is
equivalent to simply matching the substring "foo".  If such a line is found,
and if the "done" variable is NOT set to a nonzero value, then we change
the whole input line to "bar", and then set the done variable to 1.  This
prevents the condition from triggering a second time.

The second condition matches every line, so that we always print something.
Either we print "bar" (if the first action was triggered), or else we
print a copy of the original input line.

All together, what this does is "the first time we match foo, print bar
instead; otherwise copy the input line", which is what was requested.


Reply to: