Putting a line in a specific place in a file

S

samasama

Hi... I need to place a line in a specific part of the file. I don't
really know where to begin. Aside from feeding the file contents into
an array?


[base]
name=CentOS-$releasever - Base
mirrorlist=http://mirrorlist.centos.org/?release=$releasever&arch=$basearch&repo=os
#baseurl=http://mirror.centos.org/centos/$releasever/os/$basearch/
gpgcheck=1
gpgkey=http://mirror.centos.org/centos/RPM-GPG-KEY-centos4
exclude=httpd

I need to find the [base] entry and then put the exclude= below the
gpgkey= line.

Any help is vastly appreciated. Any docs or turtorials about writing a
parser would help too.

Thanks
 
A

anno4000

samasama said:
Hi... I need to place a line in a specific part of the file. I don't
really know where to begin. Aside from feeding the file contents into
an array?

That's a faq. Find it through "perldoc -q 'line in a file'". The
(terse) answer is to tie an array to the file so it *looks* like the
contents were fed into the array. The use pattern matching (and
possibly List::Util::first) to locate your place and add a line
after it.

Anno
[base]
name=CentOS-$releasever - Base
mirrorlist=http://mirrorlist.centos.org/?release=$releasever&arch=$basearch&repo=os
#baseurl=http://mirror.centos.org/centos/$releasever/os/$basearch/
gpgcheck=1
gpgkey=http://mirror.centos.org/centos/RPM-GPG-KEY-centos4
exclude=httpd

I need to find the [base] entry and then put the exclude= below the
gpgkey= line.

Any help is vastly appreciated. Any docs or turtorials about writing a
parser would help too.

Thanks
 
S

samasama

Looks like you are trying to edit an INI file. If so, have you looked
at CPAN for INI related modules? such as Config::INI::Simple.

--

Config file yeah... I need to learn how to do this by hand though.
The only part I'm stuck at is determing what line number my first
regex is on and how many lines
down to my next regex.
Something like:
use strict;
use Fcntl;

my $file = "/etc/yum.repos.d/CentOS-Base.repo";
sysopen( REPO_FILE, $file, O_RDONLY )
|| die "Can't open web03 passwd file!: $!\n";

my @file = <REPO_FILE>;

my ( $number1, $number2 );
foreach my $line (@file) {
if ( $line =~ /\[base\]/i ) {
print "Found $line on line $..\n";
}
}

Except $. gives me the total number of lines, not the line number $line
is on.

I think I'm going in the right direction? : )
 
S

samasama

That would be because you are not looping through the file when you
print $. . You've slurped the whole file into @file and are looping
through @file. It's no surprise that $. is equal to the last line
number of the input file.

You may want to loop through the file handle vs looping on @file. Or
loop through @file using
for my $i ( 0 .. $#file ) {
if ( $line =~ ...
}

Thanks, that does make sense : )
I did a while (<FH>) and am able to find the line it's line number, but
how would I go about finding the first instance of $line =~ /gpgkey/
below it? And get the line number.

Thanks
 
T

Tad McClellan

samasama said:
I did a while (<FH>) and am able to find the line it's line number, but
how would I go about finding the first instance of $line =~ /gpgkey/
below it? And get the line number.


while ( <FH> ) { # skip some lines
last if $. == $magic_line_number;
}

while ( <FH> ) { # start search "below it"
print "found on line $.\n" if /gpgkey/;
}
 
J

John W. Krahn

Tad said:
while ( <FH> ) { # skip some lines
last if $. == $magic_line_number;
}

while ( <FH> ) { # start search "below it"
print "found on line $.\n" if /gpgkey/;
}

while ( <FH> ) {
( $. == $magic_line_number .. /gpgkey/ ) =~ /E/
&& print "found on line $.\n";
}



John
 
S

samasama

while ( <FH> ) {
( $. == $magic_line_number .. /gpgkey/ ) =~ /E/
&& print "found on line $.\n";
}

I like that little number there... and it works too.
Now I need to figure it out : )

$. == the line matched earlier loop until gpgkey ?
 
S

samasama

I like that little number there... and it works too.
Now I need to figure it out : )

$. == the line matched earlier loop until gpgkey ?

I still don't really understand that line, but here's what I got...

use strict;
use Fcntl;

my $file = "CentOS-Base.repo";
sysopen( REPO_FILE, $file, O_RDWR )
|| die "Can't open repo file !: $!\n";

my $base_line;
my $gpg_line;
while (<REPO_FILE>) {
chomp $_;
if ( $_ =~ /\[base\]/i ) {
$base_line = $.;
print "Found $_ on line $base_line\n";

}

if ( ( $. == $base_line .. /gpgkey\=/i ) =~ /E/ ) {
print "Found $_ on line $.\n";
$gpg_line = $.;
print REPO_FILE "\nfoo\n";
}

This finds the lines correctly but if I try to print after $gpg_line,
the line below gets fubar.
I know I could use Tie::File, but i really want to understand what's
going on and how to insert my line after $gpg_line correctly.
 
A

anno4000

[positioning a file handle]
This finds the lines correctly but if I try to print after $gpg_line,
the line below gets fubar.
I know I could use Tie::File, but i really want to understand what's
going on and how to insert my line after $gpg_line correctly.

Well, you can't.

A file is really a sequence of bytes, no more, no less. You can
append to the end of a file, but writing anywhere else implies
overwriting what's already there.

That's what happened to the "line below". It was partially overwritten
by the new data you wrote.

Editors and other tools that work on text files make it look like
a file was a sequence of lines that new lines could be inserted in.
That's software that makes it look like that. There is no insert
operation on the file level.

[side note]

Pity we can no longer refer to the FAQ for this question.

Perlfaq5 "How do I change one line in a file/delete a line..." used to
explain this problem in detail. These days it just refers to Tie::File,
another tool that makes files look like a sequence (a Perl array) of lines.

Anno
 
S

samasama

[positioning a file handle]
This finds the lines correctly but if I try to print after $gpg_line,
the line below gets fubar.
I know I could use Tie::File, but i really want to understand what's
going on and how to insert my line after $gpg_line correctly.

Well, you can't.

A file is really a sequence of bytes, no more, no less. You can
append to the end of a file, but writing anywhere else implies
overwriting what's already there.

Duh... Yeah I feel dumb : P I don't know why I thought I could just
print the line.
Pity we can no longer refer to the FAQ for this question.

Perlfaq5 "How do I change one line in a file/delete a line..." used to
explain this problem in detail. These days it just refers to Tie::File,
another tool that makes files look like a sequence (a Perl array) of lines.

Yeah, Tie::File is great, but I needed to learn the steps in writing my
own parser, for future projects.

While, I've figured out what I needed to do and I'll paste the last of
the code below... I still don't understand what if ( ( $. == $base_line
... /gpgkey\=/i ) =~ /E/ ) { translates too.
It looks like $. == $base_line go down using .. until we hit gpgkey,
and I have no idea what the /E/ means. I'd really like to understand
that.

Code:

use strict;
use Fcntl;
use File::Copy;

my $file = "CentOS-Base.repo";
sysopen( REPO_FILE, $file, O_RDWR )
|| die "Can't open repo file !: $!\n";

my ( $base_line, $gpg_line, $found_exclude, $excluded, $exclude_line,
$package_excluded );
my $package = $ARGV[0];

print "$package\n";
while (<REPO_FILE>) {
chomp $_;
if ( $_ =~ /\[base\]/i ) {
$base_line = $.;
print "Found $_ on line $base_line\n";

}

if ( ( $. == $base_line .. /gpgkey\=/i ) =~ /E/ ) {
print "Found $_ on line $.\n";
$gpg_line = $.;
}
if ( ( $. == $gpg_line .. /exclude\=.*/i ) =~ /E/ ) {
$exclude_line = $.;
$found_exclude = 1;
print "Found exclude $_ on line $.\n";
$package_excluded = 1 if ($_ =~ m/.*$package.*/);

$excluded = $_;

}
}

# reopen (maybe seek() would be better? )
sysopen( REPO_FILE, $file, O_RDWR )
|| die "Can't open repo file !: $!\n";

sysopen( TEMP_REPO_FILE, "/tmp/repo.XXX", O_CREAT | O_WRONLY )
|| die "Can't create temporary shadow file: $!\n";

while (<REPO_FILE>) {
if ($found_exclude && !$package_excluded) {
if ( $. == $exclude_line ) {
chomp $_;
$_ =~ s/$_/$_ $package\n/;
}
}
else {
if ( $. == $gpg_line ) {
print TEMP_REPO_FILE "exclude\=$package\n" unless
$found_exclude;
}
}
print TEMP_REPO_FILE $_;
}

close TEMP_REPO_FILE;
close REPO_FILE;
copy( "/tmp/repo.XXX", $file );
unlink("/tmp/repo.XXX");

Thanks
 
J

John W. Krahn

samasama said:
[positioning a file handle]
This finds the lines correctly but if I try to print after $gpg_line,
the line below gets fubar.
I know I could use Tie::File, but i really want to understand what's
going on and how to insert my line after $gpg_line correctly.
Well, you can't.

A file is really a sequence of bytes, no more, no less. You can
append to the end of a file, but writing anywhere else implies
overwriting what's already there.

Duh... Yeah I feel dumb : P I don't know why I thought I could just
print the line.
Pity we can no longer refer to the FAQ for this question.

Perlfaq5 "How do I change one line in a file/delete a line..." used to
explain this problem in detail. These days it just refers to Tie::File,
another tool that makes files look like a sequence (a Perl array) of lines.

Yeah, Tie::File is great, but I needed to learn the steps in writing my
own parser, for future projects.

While, I've figured out what I needed to do and I'll paste the last of
the code below... I still don't understand what if ( ( $. == $base_line
.. /gpgkey\=/i ) =~ /E/ ) { translates too.
It looks like $. == $base_line go down using .. until we hit gpgkey,
and I have no idea what the /E/ means. I'd really like to understand
that.

Perhaps a demonstration may help:

$ seq 10 18 | perl -lne' my $x = 3 .. /16/; print "Line Number: $. Contents:
$_ Flip-flop: $x" '
Line Number: 1 Contents: 10 Flip-flop:
Line Number: 2 Contents: 11 Flip-flop:
Line Number: 3 Contents: 12 Flip-flop: 1
Line Number: 4 Contents: 13 Flip-flop: 2
Line Number: 5 Contents: 14 Flip-flop: 3
Line Number: 6 Contents: 15 Flip-flop: 4
Line Number: 7 Contents: 16 Flip-flop: 5E0
Line Number: 8 Contents: 17 Flip-flop:
Line Number: 9 Contents: 18 Flip-flop:



John
 
S

samasama

Perhaps a demonstration may help:

$ seq 10 18 | perl -lne' my $x = 3 .. /16/; print "Line Number: $. Contents:
$_ Flip-flop: $x" '
Line Number: 1 Contents: 10 Flip-flop:
Line Number: 2 Contents: 11 Flip-flop:
Line Number: 3 Contents: 12 Flip-flop: 1
Line Number: 4 Contents: 13 Flip-flop: 2
Line Number: 5 Contents: 14 Flip-flop: 3
Line Number: 6 Contents: 15 Flip-flop: 4
Line Number: 7 Contents: 16 Flip-flop: 5E0
Line Number: 8 Contents: 17 Flip-flop:
Line Number: 9 Contents: 18 Flip-flop:

heh, I guess I'm tired? : ) I'm still confused...
 
M

Mumia W. (reading news)

Hi... I need to place a line in a specific part of the file. I don't
really know where to begin. Aside from feeding the file contents into
an array?


[base]
name=CentOS-$releasever - Base
mirrorlist=http://mirrorlist.centos.org/?release=$releasever&arch=$basearch&repo=os
#baseurl=http://mirror.centos.org/centos/$releasever/os/$basearch/
gpgcheck=1
gpgkey=http://mirror.centos.org/centos/RPM-GPG-KEY-centos4
exclude=httpd

I need to find the [base] entry and then put the exclude= below the
gpgkey= line.

Any help is vastly appreciated. Any docs or turtorials about writing a
parser would help too.

Thanks

Read through the entire input file--writing it to a temporary output
file as you go; keep track of what section you are in. When you get to
the place where you need to add a line, print that line.

This demonstrates the reading and writing parts of the above:

#!/usr/bin/perl

use strict;
use warnings;

my $section;

while (my $line = <DATA>) {
$section = $1 if $line =~ m/^\[(\w+)\]/;
if (defined $section) {
if ('management' eq $section) {
if ($line =~ /^\s*$/) {
print "Treasurer=Mary Watson\n";
}
}
}
print $line;
}


__DATA__
[financial]
Assets=368,114
Liabilities=108,103
OwnersEquity=260,011

[management]
President=John Howard
VicePresident=John Orlando

[organization]
Name=Terrance Enterprises
Location=6192 Oak Blvd. Suite 616, Sydney, Australia
 
D

Dr.Ruud

Mumia W. (reading news) schreef:

my $section;

while (my $line = <DATA>) {
$section = $1 if $line =~ m/^\[(\w+)\]/;

I guess you meant something like this:

$section = ($line =~ m/^\[(\w+)\]/) ? $1 : undef ;

or even

$section = ($line =~ m/^ \[ (\w+) \]/x)[0] ;
 
S

samasama

Yeah, got all this... Just trying to understand the line that John
threw down...
( $. == $gpg_line .. /exclude\=.*/i ) =~ /E/
Which he demonstrated the same method piping seq to perl above...
Though, I think that I only confused me more : )
 
S

samasama

while (<>) {
if (/BEGIN PATTERN/ .. /END PATTERN/) {
# line falls between BEGIN and END in the
# text, inclusive.
}
}


while (<>) {
if (FIRST_LINE_NUM .. LAST_LINE_NUM) {
# operate only between first and last line, inclusive.
}
}

That did help, but what about the =~ /E/ ?
I tried finding ref to that in perlre but couldn't... ?

Thanks a lot
 
J

John W. Krahn

samasama said:
That did help, but what about the =~ /E/ ?
I tried finding ref to that in perlre but couldn't... ?

$ seq 10 18 | perl -lne' my $x = 3 .. /16/; print "Line Number: $. Contents:
$_ Flip-flop: $x" '
Line Number: 1 Contents: 10 Flip-flop:
Line Number: 2 Contents: 11 Flip-flop:
Line Number: 3 Contents: 12 Flip-flop: 1
Line Number: 4 Contents: 13 Flip-flop: 2
Line Number: 5 Contents: 14 Flip-flop: 3
Line Number: 6 Contents: 15 Flip-flop: 4
Line Number: 7 Contents: 16 Flip-flop: 5E0
^
^
^
Line Number: 8 Contents: 17 Flip-flop:
Line Number: 9 Contents: 18 Flip-flop:

Notice the 'E' above? That is what the pattern is matching.



John
 
S

samasama

Line Number: 7 Contents: 16 Flip-flop: 5E0
^
^
^
Line Number: 8 Contents: 17 Flip-flop:
Line Number: 9 Contents: 18 Flip-flop:

Notice the 'E' above? That is what the pattern is matching.

Hmmm, but /16/ was the pattern ?

Does ( $. == $base_gpg_line .. /exclude\=.*/i ) return E0 ?
If so then the =~ /E/ makes sense to me.

Once again thanks
Signed, complete noob
 
S

samasama

Ahhh, the E made sense after reading this:
The range operator, which is really two different operators depending
on the context. In an array context, returns an array of values
counting (by ones) from the left value to the right value. This is
useful for writing "for (1..10)" loops and for doing slice operations
on arrays.

In a scalar context, .. returns a boolean value. The operator is
bistable, like a flip-flop, and emulates the line-range (comma)
operator of sed, awk, and various editors. Each .. operator maintains
its own boolean state. It is false as long as its left operand is
false. Once the left operand is true, the range operator stays true
until the right operand is true, AFTER which the range operator becomes
false again. (It doesn't become false till the next time the range
operator is evaluated. It can test the right operand and become false
on the same evaluation it became true (as in awk), but it still returns
true once. If you don't want it to test the right operand till the next
evaluation (as in sed), use three dots (...) instead of two.) The right
operand is not evaluated while the operator is in the "false" state,
and the left operand is not evaluated while the operator is in the
"true" state. The precedence is a little lower than || and &&. The
value returned is either the null string for false, or a sequence
number (beginning with 1) for true. The sequence number is reset for
each range encountered. The final sequence number in a range has the
string 'E0' appended to it, which doesn't affect its numeric value, but
gives you something to search for if you want to exclude the endpoint.
You can exclude the beginning point by waiting for the sequence number
to be greater than 1. If either operand of scalar .. is static, that
operand is implicitly compared to the $. variable, the current line
number.


Any comments about the rest of the code is very much welcome and
appreciated.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,756
Messages
2,569,535
Members
45,008
Latest member
obedient dusk

Latest Threads

Top