0

My boss needs to change a particular routing file on some dozens (hundreds) of hosts by adding a line like:

10.11.0.0/16   via 172.16.2.XX dev tun0

... where XX is based on the octet preceding the "dev" keyword on the first line of the same file.

He wants it to be an automated in-place edit. The first lines of the existing file look like:

10.12.123.0/22 via 172.16.2.24 dev tun0
10.13.234.0/23 via 172.16.2.22 dev tun0

So the results should look like:

10.12.123.0/22 via 172.16.2.24 dev tun0
10.13.234.0/23 via 172.16.2.22 dev tun0
10.11.0.0/16   via 172.16.2.24 dev tun0

... where the last line has simply been added and the last octet in that line has been copied from the last octet on the first line.

Jim Dennis
  • 17,054
  • 13
  • 68
  • 116

3 Answers3

1

Sure, you could cram this into a one liner, but why punish yourself (and the poor sod who has to maintain this down the road). Things like -i work in programs. Here's the basic pattern you're looking for.

#!/usr/bin/env perl -n -i

print $_;
if( /...whatever you want to match.../ ) {
    print "...whatever extra line you want to add...";
}

-n says to iterate line by line, as if there's a while loop around the program. Unlike -p it doesn't automatically print the line. Sure, you could append to $_, but this gives us better control.

-i says to edit the file in place rather than just print to STDOUT.

Schwern
  • 153,029
  • 25
  • 195
  • 336
  • For this case the command is intended to run, ad hoc, over SaltStack (or Ansible, Fabric, classh, or whatever). The intention is to quickly make a particular configuration change (add a line based, in part, on a different line/pattern). Writing it as a file, distributing that and executing it adds at least two extra steps to the process. – Jim Dennis Jan 20 '15 at 18:22
  • @JimDennis I don't know the services you mention. I *think* you're saying that finding and cutting and pasting a one-liner is faster than uploading a file to a server. I'm not going to argue that since I imagine either way takes a few seconds, but editing the smashed together code of the one-liner will be error prone. If you do this so often that any of that matters, it should be part of a process with a named and documented program. The program can be made to take arguments for different patterns, etc... Ultimately, you'll want this automated instead of "something my boss remembers to do". – Schwern Jan 20 '15 at 18:41
1

It seems you're missing reset of line counter $. for each input file, and close(ARGV) does just that,

perl -i.bak -pe'
  $octet = $1 if /(\d+)\s+dev/ and $. ==1;
  $_ .= "10.11.0.0/16 via 172.16.2.$octet dev tun0\n", close(ARGV) if eof;
' "$filenames"
mpapec
  • 50,217
  • 8
  • 67
  • 127
0

Here's my attempt ... which does seem to work for a single file:

perl -pi.bak -e '$octet = $1 if /(\d+)\s+dev/ and $. == 1;\
  $line="10.11.0.0/16 via 172.16.2.$octet dev tun0\n";\
  $_ = $_ . $line if eof;' "$filenames"

Here's a safer variation which only appends the intended line if a matching pattern is found on the first line of each file. (It also resets $. as described by Сухой27):

perl -pi.bak -e '$line ="10.11.0.0/16 via 172.16.2.$1 dev tun0\n"\
  if /(\d+)\s+dev/ && $. == 1;\
  close(ARGV), $_ = $_ . $line if eof;' "$filenames"

(If no match is found for the regular expression than $line is empty and appending an empty string to $_ is harmless).

Jim Dennis
  • 17,054
  • 13
  • 68
  • 116
  • I suppose I should consider adding something like 'and !/^10.11.0.0/' ... to the 'if eof' condition and make this command idempotent? – Jim Dennis Jan 19 '15 at 23:38
  • I'm posting this primarily as a way to "answer your own question" ... but also to open it up for commentary. Mostly I want to be able to find this trick again later since I rarely use Perl any more and have to fight with its syntax when doing an in-place edit involving more then a simple s/xxx/yyy/ substitution. – Jim Dennis Jan 19 '15 at 23:50