Search code examples
perlscopesubroutine

Variable not set properly in perl


I have the following code:

#!/usr/bin/perl -w
use warnings;
use diagnostics;

open $fh, "<", "$ARGV[0]" or die "Could not open file: $!";

sub getsub {
    my $sub = $_[0];
    print "sub entered for $sub\n";
    while (<$fh>) {
        if ( /\.subckt $sub/ .. /\.ends/ ) {
            print;
        }
    }
}

while (<$fh>) {
    if ( $_ =~ /^xa1/ ) {
        $line = $_;
        print "line found to be $line\n";
        while ( ( my $nxt = readline($fh) ) =~ /^\+/ ) {
            $line = $nxt;
            print "line changed to $line\n";
        }
        $line =~ s/\s+$//;
        print "last line is $line\n";
        my $sub = ( split '\s', $line )[-1];
        print "subcircuit found is $sub in $line\n";
        getsub($sub);
    }
}

Here I am trying to print some text between two patterns inside the getsub routine. But when I try to run this I enter the subroutine but doesn't enter the if block inside the subroutine. I am trying to run it on the following file:

.subckt a1 x y z
  xa a b c1
  xb c d e1
  xc f g h1
.ends

.subckt c1 x y z
  xa a b f
  xb c d e
  xc f g h
.ends

.subckt e1 x y z
  xa a b c1
  xb c d k1
  xc f g h1
.ends

xa1 a s f a1

I want to print the contents of the file between .subckt a1 and .ends.

I know this could be done by the one liners of perl on the command line, but I want to create some generalized script for different files so I need to go this way only. What is wrong with the above code.


Solution

  • What is wrong with the above code?

    Two big things

    1. Always include use strict; and use warnings; in EVERY script.

    2. Try not to use two while loops when processing a single file.

      Instead just use state variables if your file requires special processing logic at different parts of the file.

    In this case, to print out the lines of a file between two markers, you simply need the Range operator ..:

    #!/usr/bin/perl -w
    use strict;
    use warnings;
    use diagnostics;
    use autodie;
    
    my $file = shift;
    
    #open my $fh, "<", $file;
    my $fh = \*DATA;
    
    while (<$fh>) {
        if ( my $range = /^\.subckt a1/ .. /^\.ends/ ) {
            print if $range != 1 && $range !~ /E/;
        }
    }
    
    __DATA__
    .subckt a1 x y z
      xa a b c1
      xb c d e1
      xc f g h1
    .ends
    
    .subckt c1 x y z
      xa a b f
      xb c d e
      xc f g h
    .ends
    
    .subckt e1 x y z
      xa a b c1
      xb c d k1
      xc f g h1
    .ends
    
    xa1 a s f a1
    

    Outputs:

      xa a b c1
      xb c d e1
      xc f g h1
    

    Addendum to Answer questions from comment

    I have three questions:

    1. what does the value /E/ represents in for the $range function?

    Read the perldoc for the Range operator ..:

    ... The value returned by a flip-flip is either the empty 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.

    Therefore the /E/ is used to exclude the end of the range, so we don't print the line that contains .ends.

    1. And you used the input file as a part of the script, so what does __DATA__ do?

    I commented out the input file handle instead used a reference to *DATA instead.

    #open my $fh, "<", $file;
    my $fh = \*DATA;
    

    *DATA is a special file handle that contains everything after __DATA__ in the script. This can be convenient way to test a script and show how to do something without needing create an actual file to load for testing.

    1. Also, if I have a very big file should I go with this approach?

    Whenever one does file processing, they should aim to process the file line by line. That is what is done here, and therefore this would work fine for large files as well.