Question

I am new to perl and I would like to replicate my code written in awk into perl. I want to replicate a particular line number of times depending on string matched at position.

Sample input file:

Dummy lines Ix Dummy lines
Dummy lines Ix Dummy lines
text Ix A(Ay) test text
Dummy lines Ix Dummy lines
Dummy lines Ix Dummy lines

While reading this input file I would check for "Ix" string in the second position and repeat it number of times. (3 times as shown in the Sample output)

Sample Output file

Dummy lines Ix Dummy lines
Dummy lines Ix Dummy lines
text I1 A(A3) test text
text I2 A(A2) test text
text I3 A(A1) test text
Dummy lines Ix Dummy lines
Dummy lines Ix Dummy lines

Any help would be appriciated.

regards


My failing code is:

#!/usr/bin/perl -w
use strict;
my $loop=0;
my $max=3;
my $in_file="$ARGV[0]";

open (PH, "$in_file") or die "check file";
while (<PH>) {
chomp;
if ($_ =~ /Ix/) {
    $loop=0;
    while ($loop < $max) {
        my $local = $max-$loop;
        $_ =~ s/Ix/I$loop/;
        $_ =~ s/Ay/A$local/;
        print "$_\n";
        $loop++;
        }
    } else {
        print "$_\n";
    }
}
close(PH);

I am looking for somthing like "$2 ~ /Ix/" in awk that I could place in my if-condition. I am not sure if perl allows you to do that. Apart from the above, I am not able to use current value of $loop in the while itself.

Was it helpful?

Solution

Your biggest problem is that you edit $_, so the second pass through the loop, there is no longer a "Ix" nor "Ay" in the string.

$loop=0;
while ($loop < $max) {
    my $local = $max-$loop;
    my $line = $_;
    $line =~ s/Ix/I$loop/;
    $line =~ s/Ay/A$local/;
    print "$line\n";
    $loop++;
}

You might also consider using a for loop:

   for my $loop (0 .. ($max - 1)) {
        my $local = ($max - $loop);
        my $line = $_;
        $line =~ s/Ix/I$loop/;
        $line =~ s/Ay/A$local/;
        print "$line\n";
    }

If these are space-delimited "columns" and you'd like to only edit the values in "columns" 2 and 3 (your mention of $2 in awk seems to suggest that?) you can split the input as well: the split command intentionally emulates awk

From perldoc -f split:

        As another special case, "split" emulates the default behavior of
        the command line tool awk when the PATTERN is either omitted or a
        literal string composed of a single space character (such as ' '
        or "\x20", but not e.g. "/ /"). In this case, any leading
        whitespace in EXPR is removed before splitting occurs, and the
        PATTERN is instead treated as if it were "/\s+/"; in particular,
        this means that any contiguous whitespace (not just a single space
        character) is used as a separator. However, this special treatment
        can be avoided by specifying the pattern "/ /" instead of the
        string " ", thereby allowing only a single space character to be a
        separator.

        If omitted, PATTERN defaults to a single space, " ", triggering
        the previously described awk emulation.

which brings us to:

local $" = ' ';                                                                                  #" (syntax highlighting bug on SO)

my @input = split;
if ($input[1] =~ /Ix/) {
    for my $loop (0 .. ($max - 1)) {
        my $local = ($max - $loop);
        my @line = @input;
        $line[1] =~ s/Ix/I$loop/;
        $line[2] =~ s/Ay/A$local/;
        print "@line\n";
    }
} else {
    print "$_\n";
}

The special variable $" specifically means that "@line" will be printed with a ' ' between each element of the array, so you get your "columns" back in the output.

One last hint: your die can print a meaningful error message just by including $!:

#!/usr/bin/perl -w
use strict;
my $loop=0;
my $max=3;
my $in_file="$ARGV[0]";

local $" = ' ';                                                                                  #" (syntax highlighting bug on SO)

open (PH, "$in_file") or die "check file: $!";
while (<PH>) {
    chomp;
    my @input = split;
    if ($input[1] =~ /Ix/) {
        for my $loop (0 .. ($max - 1)) {
            my $local = ($max - $loop);
            my @line = @input;
            $line[1] =~ s/Ix/I$loop/;
            $line[2] =~ s/Ay/A$local/;
            print "@line\n";
        }
    } else {
        print "$_\n";
    }
}
close(PH);

Edit:

As @Kenosis pointed out in comments, the sample output you provided has the I counter running 1…3 rather than 0…2. In your loop, you were initializing the counter to 0 and incrementing it only after printing, so I had (mis-)interpreted that as your intention.

Fortunately, changing this is easy:

   for my $loop (1 .. $max) {
      my $local = (1+ $max - $loop);
      …

As also pointed out, using lexical (my) variables for file handles is generally safer/better for various technical reasons, as well;

  open my $ph, '<', $infile or die "Can't read $infile: $!";

  …

  while (<$ph>) {
  …

… although the older style filehandles that you used (bareword identifiers) still does work. This also shows the "3-argument open", which prevents a number of possible security holes (or bizarre, head-scratchingly crazy behaviours) in the "2-arg" form, with the < prepended to the filename.

And, as @Kenosis notes, $" does happen to default to ' ', but I tend to redefine it "just in case" (I personally have a lot of Perl code that redefines it as things like ',' or "\t" for various reasons, and you can locally reset it to be sure of what your output will look like.)

OTHER TIPS

Here's another option:

use strict;
use warnings;

while (<>) {
    if (/^\S+?\s+Ix\s+/) {
        my ( %h, $c ) = ( I => 1, A => -3 );
        $_ = join '',
          map { s/\b(I)x|(A)y\b/$c = $1 ? $1 : $2; $c . abs $h{$c}++/ge; $_ }
          my @lines = ($_) x 3;
    }

    print;
}

Usage: perl script.pl inFile [>outFIle]

The last, optional parameter directs output to a file.

Output on your dataset:

Dummy lines Ix Dummy lines
Dummy lines Ix Dummy lines
text I1 A(A3) test text
text I2 A(A2) test text
text I3 A(A1) test text
Dummy lines Ix Dummy lines
Dummy lines Ix Dummy lines

Hope this helps!

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top