tags:

views:

75

answers:

3

Hi my test.pl as below

#!C:\Perl\bin\perl.exe
use strict;
use warnings;


my $numArgs = $#ARGV + 1;
print "thanks, you gave me $numArgs command-line arguments.\n";



while (my $line = <DATA> ) { 
    foreach my $argnum (0 .. $#ARGV) {
        if ($line =~ /$ARGV[$argnum]/)
            {
                print $line;
            }
    }
} 

__DATA__ 
A
B
Hello World :-)
Hello World !

when I passed one arg, it works well. Such as I run test.pl A or test.pl B or **test.pl Hello"

when I passed two args, it works some time only.

Successful: When I run test.pl A B or test.pl A Hello or **test.pl B Hello"

Failed: when I run test.pl Hello World*

Produced and output duplicate lines:

D:\learning\perl>t.pl Hello World
thanks, you gave me 2 command-line arguments.
Hello World :-)
Hello World :-)
Hello World !
Hello World !

D:\learning\perl>

How to fix it? Thank you for reading and replies.

[update] I don't want to print duplicate lines.

+3  A: 

I don't see the problem, your script processes the __DATA__ and tests all input words against it: since "Hello" and "World" match twice each, it prints 4 rows.

If you don't want it to write multiple lines, just add last; after the print statement.

kemp
Hi Kemp. yes, as you said. But I don't want to print duplicate lines. thank you.
Nano HE
Then add a `last;` just after the `print` statement.
kemp
It works greatly. thank you!
Nano HE
+3  A: 

The reason you're getting the duplicate output is because the regex $line =~ /Hello/ matches both "Hello World" lines and $line =~ /World/ also matches both "Hello World" lines. To prevent that, you'll need to add something to remember which lines from the __DATA__ section have already been printed so that you can skip printing them if they match another argument.

Also, some very minor stylistic cleanup:

#!C:\Perl\bin\perl.exe
use strict;
use warnings;

my $numArgs = @ARGV;
print "thanks, you gave me $numArgs command-line arguments.\n";

while (my $line = <DATA> ) { 
    foreach my $arg (@ARGV) {
        if ($line =~ /$arg/)
            {
                print $line;
            }
    }
} 

__DATA__ 
A
B
Hello World :-)
Hello World !
  • Using an array in scalar context returns its size, so $size = @arr is preferred over $size = $#arr + 1

  • If you're not going to use a counter for anything other than indexing through an array (for $i (0..$#arr) { $elem = $arr[$i]; ... }), then it's simpler and more straightforward to just loop over the array instead (for $elem (@arr) { ... }).

Your foreach loop could also be replaced with a grep statement, but I'll leave that as an exercise for the reader.

Dave Sherohman
+1 Hi Dave, Thanks a lot for your perfect dissection.
Nano HE
Hi Dave, I found a bug. If i run `test.pl World Hello`,(swap *Hello World* to *World Hello*) The output still wrong. :-)
Nano HE
+1  A: 

Assuming you want to print each line from DATA only once if one or more patterns match, you can use grep. Note that use of \Q to quote regex metacharacters in the command line arguments and the use of the @patterns array to precompile the patterns.

Read if grep { $line =~ $_ } @patterns out loud: If $line matches one or more patterns ;-)

#!/usr/bin/perl

use strict; use warnings;

printf "Thanks, you gave me %d command line arguments.\n", scalar @ARGV;

my @patterns = map { qr/\Q$_/ } @ARGV;

while ( my $line = <DATA> ) {
    print $line if grep { $line =~ $_ } @patterns;
}

__DATA__
A
B
Hello World :-)
Hello World !

Here are some comments on your script to help you learn:

my $numArgs = $#ARGV + 1;
print "thanks, you gave me $numArgs command-line arguments.\n";

The command line arguments are in @ARGV (please do read the documentation). In scalar context, @ARGV evaluates to the number of elements in that array. Therefore, you can simply use:

printf "Thanks, you gave me %d command line arguments.\n", scalar @ARGV;

Further, you can iterate directly over the elements of @ARGV in your foreach loop instead of indexed access.

while (my $line = <DATA> ) { 
    foreach my $arg ( @ARGV ) {
        if ( $line =~ /$arg/ ) {
            print $line;
        }
    }
} 

Now, what happens to your program if I pass ( to it on the command line? Or, even World? What should happen?

Sinan Ünür
Selam Sinan, Teşekkür ederim
Nano HE
BTW, when I passed `(` to my program above, produced and generated an error `Unmatched ( in regex; marked by <-- HERE in m/( <-- HERE / at D:\learning\perl\t .pl line 13, <DATA> line 1.` , Otherwise World? works well. Your script above fixed this bug.
Nano HE
@Nano HE Rica ederim ;-) The main question is, do you want `World?` to match the input given that there is no string `World?` in `__DATA__`. It is matching because `d?` means the `d` is optional in the pattern. Using `\Q` in the pattern before interpolating `$_` into the `qr` means the `?` would be quoted and now `?` would have to match literally.
Sinan Ünür