How can I print a matching line, one line immediately above it and one line immediately below?

后端 未结 8 1050
你的背包
你的背包 2020-12-16 07:23

From a related question asked by Bi, I\'ve learnt how to print a matching line together with the line immediately below it. The code looks really simple:

#!p         


        
相关标签:
8条回答
  • 2020-12-16 08:01

    It will probably be easier just to use grep for this as it allows printing of lines before and after a match. Use -B and -A to print context before and after the match respectively. See http://ss64.com/bash/grep.html

    0 讨论(0)
  • 2020-12-16 08:11

    I am going to ignore the title of your question and focus on some of the code you posted because it is positively harmful to let this code stand without explaining what is wrong with it. You say:

    code that can print matching lines with the lines immediately above them. The code that would partially suit my purpose is something like this

    I am going to go through that code. First, you should always include

    use strict;
    use warnings;
    

    in your scripts, especially since you are just learning Perl.

    @array;
    

    This is a pointless statement. With strict, you can declare @array using:

    my @array;
    

    Prefer the three-argument form of open unless there is a specific benefit in a particular situation to not using it. Use lexical filehandles because bareword filehandles are package global and can be the source of mysterious bugs. Finally, always check if open succeeded before proceeding. So, instead of:

    open(FH, "FILE");
    

    write:

    my $filename = 'something';
    open my $fh, '<', $filename
        or die "Cannot open '$filename': $!";
    

    If you use autodie, you can get away with:

    open my $fh, '<', 'something';
    

    Moving on:

    while ( <FH> ) {
      chomp;
      $my_line = "$_";
    

    First, read the FAQ (you should have done so before starting to write programs). See What's wrong with always quoting "$vars"?. Second, if you are going to assign the line that you just read to $my_line, you should do it in the while statement so you do not needlessly touch $_. Finally, you can be strict compliant without typing any more characters:

    while ( my $line =  <$fh> ) {
        chomp $line;
    

    Refer to the previous FAQ again.

      if ("$my_line" =~ /Pattern/) {
    

    Why interpolate $my_line once more?

          foreach( @array ){
              print "$_\n";
          }
    

    Either use an explicit loop variable or turn this into:

    print "$_\n" for @array;
    

    So, you interpolate $my_line again and add the newline that was removed by chomp earlier. There is no reason to do so:

          print "$my_line\n"
    

    And now we come to the line that motivated me to dissect the code you posted in the first place:

      if ( "$#array" > "0" ) {
    

    $#array is a number. 0 is a number. > is used to check if the number on the LHS is greater than the number on the RHS. Therefore, there is no need to convert both operands to strings.

    Further, $#array is the last index of @array and its meaning depends on the value of $[. I cannot figure out what this statement is supposed to be checking.

    Now, your original problem statement was

    print matching lines with the lines immediately above them

    The natural question, of course, is how many lines "immediately above" the match you want to print.

    #!/usr/bin/perl
    
    use strict;
    use warnings;
    
    use Readonly;
    Readonly::Scalar my $KEEP_BEFORE => 4;
    
    my $filename = $ARGV[0];
    my $pattern  = qr/$ARGV[1]/;
    
    open my $input_fh, '<', $filename
        or die "Cannot open '$filename': $!";
    
    my @before;
    
    while ( my $line = <$input_fh> ) {
        $line = sprintf '%6d: %s', $., $line;
        print @before, $line, "\n" if $line =~ $pattern;
        push @before, $line;
        shift @before if @before > $KEEP_BEFORE;
    }
    
    close $input_fh;
    
    0 讨论(0)
  • 2020-12-16 08:12

    Given the following input file:

    (1:first) Yes, this one.
    (2) This one as well (XXX).
    (3) And this one.
    Not this one.
    Not this one.
    Not this one.
    (4) Yes, this one.
    (5) This one as well (XXX).
    (6) AND this one as well (XXX).
    (7:last) And this one.
    Not this one.
    

    this little snippet:

    open(FH, "<qq.in");
    $this_line = "";
    $do_next = 0;
    while(<FH>) {
        $last_line = $this_line;
        $this_line = $_;
        if ($this_line =~ /XXX/) {
            print $last_line if (!$do_next);
            print $this_line;
            $do_next = 1;
        } else {
            print $this_line if ($do_next);
            $last_line = "";
            $do_next = 0;
        }
    }
    close (FH);
    

    produces the following, which is what I think you were after:

    (1:first) Yes, this one.
    (2) This one as well (XXX).
    (3) And this one.
    (4) Yes, this one.
    (5) This one as well (XXX).
    (6) AND this one as well (XXX).
    (7:last) And this one.
    

    It basically works by remembering the last line read and, when it finds the pattern, it outputs it and the pattern line. Then it continues to output pattern lines plus one more (with the $do_next variable).

    There's also a little bit of trickery in there to ensure no line is printed twice.

    0 讨论(0)
  • 2020-12-16 08:12

    You always want to store the last line that you saw in case the next line has your pattern and you need to print it. Using an array like you did in the second code snippet is probably overkill.

    my $last = "";
    while (my $line = <FH>) {
      if ($line =~ /Pattern/) {
        print $last;
        print $line;
        print scalar <FH>;  # next line
      }
      $last = $line;
    }
    
    0 讨论(0)
  • 2020-12-16 08:19
    grep -A 1 -B 1 "search line"
    
    0 讨论(0)
  • 2020-12-16 08:20

    Here's a modernized version of Pax's excellent answer:

    use strict;
    use warnings;
    
    open( my $fh, '<', 'qq.in') 
        or die "Error opening file - $!\n";
    
    my $this_line = "";
    my $do_next = 0;
    
    while(<$fh>) {
        my $last_line = $this_line;
        $this_line = $_;
    
        if ($this_line =~ /XXX/) {
            print $last_line unless $do_next;
            print $this_line;
            $do_next = 1;
        } else {
            print $this_line if $do_next;
            $last_line = "";
            $do_next = 0;
        }
    }
    close ($fh);
    

    See Why is three-argument open calls with lexical filehandles a Perl best practice? for an discussion of the reasons for the most important changes.

    Important changes:

    • 3 argument open.
    • lexical filehandle
    • added strict and warnings pragmas.
    • variables declared with lexical scope.

    Minor changes (issues of style and personal taste):

    • removed unneeded parens from post-fix if
    • converted an if-not contstruct into unless.

    If you find this answer useful, be sure to up-vote Pax's original.

    0 讨论(0)
提交回复
热议问题