2

I'm having trouble repleacing a set of strings in a file with something else. I already checked on stackoverflow and on other sites but something is missing. I have a file that contains the following:

  12 [ fillcolor="#62cb62", fontcolor="#ffffff", label="HealthStatus 12", shape=hexagon ]
  520 [ fillcolor="#ffc0cb", fontcolor="#000000", label="A-SYNT 520", shape=parallelogram ]
  521 [ fillcolor="#ffc0cb", fontcolor="#000000", label="A-CONF 521", shape=parallelogram ]
  522 [ fillcolor="#ffc0cb", fontcolor="#000000", label="A-SAFE 522", shape=parallelogram ]

  12 -> 522 [ color="#000000" ]
  12 -> 521 [ color="#000000" ]
  12 -> 520 [ color="#000000" ]

I want to replace

12 [ with 12 HealthStatus [
520 [ with 520 A-SYNT [
521 [ with 521 A-CONF [
522 [ with 522 A-SAFE [
12 -> with 12 HealthStatus -> 

I already have in an array the numbers and the strings and I save them in the variables $proxyid and $proxyname .

Here's the code I'm using

rename($GVFILE, $GVFILE.'.bak');

open(OUT, '>'.$GVFILE) or die $!;
print_log ($IL, "## proxystack $#proxy_stack");
open(IN, '<'.$GVFILE.'.bak') or die $!;
for (my $cursor = 0; $cursor <= $#proxy_stack; $cursor++) {

  $proxy_length = 0;
  $proxyid = $proxy_stack[$cursor];
  $proxyname = $proxy_stack[$cursor]{'name'};
  print_log ($IL, "## debug $proxyid $proxyname");
  print_log ($IL, "## debug cursor $cursor");
  while(<IN>)
  {    
      $_ =~ s/$proxyid \[/$proxyid $proxyname \[/g;
      $_ =~ s/$proxyid -\>/$proxyid $proxyname -\>/g;
      print OUT $_;
  }
  
  
}
close(IN);
close(OUT);         

I'm sure I have everything in $proxy_stack since I'm printing it and I get all the elements I expect but only the first replacement is performed.

0

1 Answer 1

3

You are basically doing your loops in the wrong order: you are looping on @proxy_stack and then on <IN>, but you should be doing the opposite. Thus, what is happening is that in the first iteration of for (my $cursor = 0; $cursor <= $#proxy_stack; $cursor++), the while loop while(<IN>) reads the whole file, and performs the substitution for the first element of @proxy_stack. Then, it moves on to the second iteration of the for loop, but you've reached the end of IN, and thus the while(<IN>) loop doesn't loop at all.

This will work:

use strict;
use warnings;

my $GVFILE = "t.txt";
rename $GVFILE, "$GVFILE.bak";

open my $out, '>', $GVFILE or die $!;
open my $in, '<', "$GVFILE.bak" or die $!;

my %proxies = ('12' => 'HealthStatus',
               '520' => 'A-SYNT',
               '521' => 'A-CONF',
               '522' => 'A-SAFE');

while (<$in>) {
    for my $proxy (keys %proxies) {
        s/(\Q$proxy\E) (\[|->)/$1 $proxies{$1} $2/g;
    }
    print $out $_;
}

I've made a few modifications:

  • I've added use strict and use warnings, which you should always do. (and I've declared variables with my, which you should always do as well, but you probably don't have to think too much about it, as strict will remind you if you forget)

  • I used 3-argument open (as you should always do).

  • I used lexical file-handles instead of global ones (you should never use global file handles).

  • I've used a hashmap instead of your @proxy_stack array. This seemed simpler, but you might have a legitimate reason to use an array, in which case I'm sure you'll be able to modify my code to use an array instead.

  • I used a foreach loop to iterate over %proxies (which corresponds to @proxy_stack in your code). It's more idiomatic, and more concise.

Depending on you actual code, this might seem a bit simpler and more efficient (because the regex engines should optimize | in the regex):

my %proxies = ('12 [' => '12 HealthStatus [',
               '520 [' => '520 A-SYNT [',
               '521 [' => '521 A-CONF [',
               '522 [' => '522 A-SAFE [',
               '12 ->' => '12 HealthStatus -> ');
my $proxies_match = join "|", map { quotemeta } keys %proxies;


while (<$in>) {
    s/$proxies_match/$proxies{$&}/g;
    print $out $_;
}

If %proxies only have those 4 keys though, the impact of speed will be negligible. And if %proxies have much more keys (and each of them is a number followed by either -> or [), then this approach is a bit tedious.

Finally, Perl has a nice in-place file editing feature which can make you code a bit nicer to write, but this will depend on how large your script is (for a small script, it's probably ok; for a large one, it probably isn't):

$^I = '.bak'; # the extension for the backup file
              # This also will make "print" print into the input file
@ARGV = 't.txt'; # the file to be read when doing `<>`

my %proxies = ('12' => 'HealthStatus',
               '520' => ' A-SYNT',
               '521' => 'A-CONF',
               '522' => 'A-SAFE');

while (<>) {
    for my $proxy (keys %proxies) {
        s/(\Q$proxy\E) (\[|->)/$1 $proxies{$1} $2/g;
    }
    print;
}
Sign up to request clarification or add additional context in comments.

3 Comments

Thanks A LOT! I was looping into myself =) I'm not very expert with Perl but the hint about the order of the loops resolved everything.
@gLiDeRJaCk happy to be of assistance! Of course you're free to reuse your old code and just fixing the order of the loops, but I strongly suggest that you take into account the 5 bullet points I've suggested, and in particular, always add use strict; use warnings; to your scripts ;-)
Obviously I'll take into account the 5 bullets (I'm still learning perl, I'm less than a novice) but at least now I'm out the cul-de-sac :)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.