use strict; use warnings; use Data::Dumper::Simple; my $stop = qr/[\s:]/; # modify as appropriate! my @lines; my $pattern; ## test one.. @lines = split /\n/, <<'EOD'; Error 123 on SystemA file not found error Error 123:on SystemB file not found error Error 123 on SystemC file not found error EOD $pattern = get_pattern( \@lines ); print Dumper($pattern); ## test two.. @lines = split /\n/, <<'EOD'; Error 124 on User1:FileA no space left Error 124 on User2:FileB no space left Error 124 on User3:FileC no space left EOD $pattern = get_pattern( \@lines ); print Dumper($pattern); sub get_pattern { my @lines = @{ +shift }; # this should take a copy.. my @known_words = split /($stop)/, shift @lines; my @pattern = @known_words; # assume everything matches.. my %modified = (); # track indices my $count = 0; # $x incrementor LINE: foreach my $line (@lines) { my @words = split /($stop)/, $line; unless ( @words == @known_words ) { warn "ignoring (word count does not match first line): $line"; next LINE; } WORD: foreach my $i ( 0 .. $#words ) { next WORD if $modified{$i}; # already noted this spot my $this = $words[$i]; next WORD if $this =~ $stop; # questionable.. are all stops 'equal'? my $that = $known_words[$i]; next WORD if $this eq $that; # everything looks ok so far $pattern[$i] = '$' . ++$count; $modified{$i}++; } } return join '', @pattern; }