Putting a line in a specific place in a file

Discussion in 'Perl Misc' started by samasama, Nov 1, 2006.

  1. samasama

    samasama Guest

    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

    --
    samasama
    samasama, Nov 1, 2006
    #1
    1. Advertising

  2. samasama

    -berlin.de Guest

    samasama <> wrote in comp.lang.perl.misc:
    > 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
    >
    > --
    > samasama
    >
    -berlin.de, Nov 1, 2006
    #2
    1. Advertising

  3. samasama

    samasama Guest


    >
    > 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? : )

    --
    samasama
    samasama, Nov 1, 2006
    #3
  4. samasama

    samasama Guest


    > 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

    --
    samasama
    samasama, Nov 1, 2006
    #4
  5. samasama <> wrote:

    > 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/;
    }


    --
    Tad McClellan SGML consulting
    Perl programming
    Fort Worth, Texas
    Tad McClellan, Nov 1, 2006
    #5
  6. Tad McClellan wrote:
    > samasama <> wrote:
    >
    >>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/;
    > }


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



    John
    --
    Perl isn't a toolbox, but a small machine shop where you can special-order
    certain sorts of tools at low cost and in short order. -- Larry Wall
    John W. Krahn, Nov 2, 2006
    #6
  7. samasama

    samasama Guest


    > 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 ?

    --
    samasama
    samasama, Nov 2, 2006
    #7
  8. samasama

    samasama Guest


    >
    > I like that little number there... and it works too.
    > Now I need to figure it out : )
    >
    > $. == the line matched earlier loop until gpgkey ?
    >
    > --
    > samasama


    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.

    --
    samasama
    samasama, Nov 6, 2006
    #8
  9. samasama

    -berlin.de Guest

    samasama <> wrote in comp.lang.perl.misc:

    [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
    -berlin.de, Nov 6, 2006
    #9
  10. samasama

    samasama Guest

    -berlin.de wrote:
    > samasama <> wrote in comp.lang.perl.misc:
    >
    > [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

    --
    samasama
    samasama, Nov 6, 2006
    #10
  11. samasama wrote:
    > -berlin.de wrote:
    >>samasama <> wrote in comp.lang.perl.misc:
    >>
    >>[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
    --
    Perl isn't a toolbox, but a small machine shop where you can special-order
    certain sorts of tools at low cost and in short order. -- Larry Wall
    John W. Krahn, Nov 6, 2006
    #11
  12. samasama

    samasama Guest


    >
    > 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...
    samasama, Nov 7, 2006
    #12
  13. On 11/01/2006 07:39 AM, samasama wrote:
    > 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
    >
    > --
    > samasama
    >


    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


    --
    Mumia W. (reading news), Nov 7, 2006
    #13
  14. samasama

    Dr.Ruud Guest

    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] ;

    > if (defined $section) {


    --
    Affijn, Ruud

    "Gewoon is een tijger."
    Dr.Ruud, Nov 7, 2006
    #14
  15. samasama

    samasama Guest

    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 : )


    --
    samasama
    samasama, Nov 7, 2006
    #15
  16. samasama

    samasama Guest


    > 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

    --
    samasama
    samasama, Nov 7, 2006
    #16
  17. samasama wrote:
    >
    >>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... ?


    $ 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
    --
    Perl isn't a toolbox, but a small machine shop where you can special-order
    certain sorts of tools at low cost and in short order. -- Larry Wall
    John W. Krahn, Nov 7, 2006
    #17
  18. samasama

    samasama Guest


    > 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

    --
    samasama
    samasama, Nov 7, 2006
    #18
  19. samasama

    samasama Guest

    samasama wrote:
    > > 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.
    > >
    > >


    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.

    --
    samasama
    samasama, Nov 7, 2006
    #19
    1. Advertising

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

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Saurabh
    Replies:
    6
    Views:
    4,528
    Chris Smith
    May 30, 2004
  2. Hugo
    Replies:
    10
    Views:
    1,305
    Matt Humphrey
    Oct 18, 2004
  3. Vernon Wenberg III
    Replies:
    4
    Views:
    230
    Tim Williams
    Oct 7, 2007
  4. scad
    Replies:
    23
    Views:
    1,162
    Alf P. Steinbach
    May 17, 2009
  5. mazdotnet
    Replies:
    2
    Views:
    396
    Alexey Smirnov
    Oct 2, 2009
Loading...

Share This Page