Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

foreach and special variable $_ not behaving as expected

Tags:

perl

I'm learning Perl and wrote a small script to open perl files and remove the comments

# Will remove this comment

my $name = ""; # Will not remove this comment

#!/usr/bin/perl -w <- wont remove this special comment

The name of files to be edited are passed as arguments via terminal

die "You need to a give atleast one file-name as an arguement\n" unless (@ARGV);

foreach (@ARGV) {
    $^I = "";
    (-w && open FILE, $_) || die "Oops: $!";
    /^\s*#[^!]/ || print while(<>);
    close FILE;
    print "Done! Please see file: $_\n";
}

Now when I ran it via Terminal: perl removeComments file1.pl file2.pl file3.pl

I got the output: Done! Please see file:

This script is working EXACTLY as I'm expecting but

Issue 1 : Why $_ didn't print the name of the file?

Issue 2 : Since the loop runs for 3 times, why Done! Please see file: was printed only once?

How you would write this script in as few lines as possible?

Please comment on my code as well, if you have time.

Thank you.

like image 354
GrSrv Avatar asked Jul 04 '13 06:07

GrSrv


2 Answers

The while stores the lines read by the diamond operator <> into $_, so you're writing over the variable that stores the file name.

On the other hand, you open the file with open but don't actually use the handle to read; it uses the empty diamond operator instead. The empty diamond operator makes an implicit loop over files in @ARGV, removing file names as it goes, so the foreach runs only once.

To fix the second issue you could use while(<FILE>), or rewrite the loop to take advantage of the implicit loop in <> and write the entire program as:

$^I = "";
/^\s*#[^!]/ || print while(<>);
like image 149
Joni Avatar answered Nov 02 '22 23:11

Joni


Here's a more readable approach.

#!/usr/bin/perl

# always!!
use warnings;
use strict;

use autodie;
use File::Copy;

# die with some usage message
die "usage: $0 [ files ]\n" if @ARGV < 1;


for my $filename (@ARGV) {
    # create tmp file name that we are going to write to
    my $new_filename = "$filename\.new";

    # open $filename for reading and $new_filename for writing
    open my $fh, "<", $filename;
    open my $new_fh, ">", $new_filename;

    # Iterate over each line in the original file: $filename,
    # if our regex matches, we bail out. Otherwise we print the line to
    # our temporary file.  
    while(my $line = <$fh>) {
       next if $line =~ /^\s*#[^!]/; 
       print $new_fh $line;
    }

    close $fh;
    close $new_fh;

    # use File::Copy's move function to rename our files. 
    move($filename, "$filename\.bak");
    move($new_filename, $filename);

    print "Done! Please see file: $filename\n";
}

Sample output:

$ ./test.pl a.pl b.pl 
Done! Please see file: a.pl
Done! Please see file: b.pl

$ cat a.pl
#!/usr/bin/perl

print "I don't do much\n"; # comments dont' belong here anyways

exit;

print "errrrrr";

$ cat a.pl.bak
#!/usr/bin/perl

# this doesn't do much
print "I don't do much\n"; # comments dont' belong here anyways

exit;

print "errrrrr";
like image 32
chrsblck Avatar answered Nov 02 '22 22:11

chrsblck