3

In AWK, it is common to see this kind of structure for a script that runs on two files:

awk 'NR==FNR { print "first file"; next } { print "second file" }' file1 file2

Which uses the fact that there are two variables defined: FNR, which is the line number in the current file and NR which is the global count (equivalent to Perl's $.).

Is there something similar to this in Perl? I suppose that I could maybe use eof and a counter variable:

perl -nE 'if (! $fn) { say "first file" } else { say "second file" } ++$fn if eof' file1 file2

This works but it feels like I might be missing something.


To provide some context, I wrote this answer in which I manually define a hash but instead, I would like to populate the hash from the values in the first file, then do the substitutions on the second file. I suspect that there is a neat, idiomatic way of doing this in Perl.

Community
  • 1
  • 1
Tom Fenech
  • 72,334
  • 12
  • 107
  • 141

3 Answers3

2

It looks like what you're aiming for is to use the same loop for reading both files, and have a conditional inside the loop that chooses what to do with the data. I would avoid that idea because you are hiding what two distinct processes in the same stretch of code, making it less than clear what is going on.

But, in the case of just two files, you could compare the current file with the first element of @ARGV, like this

perl -nE 'if ($ARGV eq $ARGV[0]) { say "first file" } else { say "second file" }' file1 file2

Forgetting about one-line programs, which I hate with a passion, I would just explicitly open $ARGV[0] and $ARGV[1]. Perhaps naming them like this

use strict;
use warnings;
use 5.010;
use autodie;

my ($definitions, $data) = @ARGV;

open my $fh, '<', $definitions;
while (<$fh>) {
   # Build hash
}

open $fh, '<', $data;
while (<$fh>) {
   # Process file
}

But if you want to avail yourself of the automatic opening facilities then you can mess with @ARGV like this

use strict;
use warnings;

my ($definitions, $data) = @ARGV;

@ARGV = ($definitions);
while (<>) {
   # Build hash
}

@ARGV = ($data);
while (<>) {
   # Process file
}
Borodin
  • 126,100
  • 9
  • 70
  • 144
2

Unfortunately, perl doesn't have a similar NR==FNR construct to differentiate between two files. What you can do is use the BEGIN block to process one file and main body to process the other.

For example, to process a file with the following:

map.txt

a=apple
b=ball
c=cat
d=dog

alpha.txt

f
a
b
d

You can do:

perl -lne'
BEGIN { 
    $x = pop; 
    %h = map { chomp; ($k,$v) = split /=/; $k => $v } <>; 
    @ARGV = $x 
}
print join ":", $_, $h{$_} //= "Not Found"
' map.txt alpha.txt
f:Not Found
a:apple
b:ball
d:dog

Update:

I gave a pretty simple example, and now when I look at that, I can only say TIMTOWDI since you can do:

perl -F'=' -lane'
    if (@F == 2) { $h{$F[0]} = $F[1]; next }
    print join ":", $_, $h{$_} //= "Not Found"
' map.txt alpha.txt
f:Not Found
a:apple
b:ball
d:dog

However, I can say for sure, there is no NR==FNR construct for perl and you can probably process them in various different ways based on the files.

jaypal singh
  • 74,723
  • 23
  • 102
  • 147
0

You can also create your own $fnr and compare to $..

Given:

var='first line
second line'
echo "$var" >f1
echo "$var" >f2 
echo "$var" >f3

You can create a pseudo FNR by setting a variable in the BEGIN block and resetting at each eof:

perl -lnE 'BEGIN{$fnr=1;}
if ($fnr==$.) {
    say "first file: $ARGV, $fnr, $. $_";
}
else {
    say "$ARGV, $fnr, $. $_";
}
eof ? $fnr=1 : $fnr++;' f{1..3}

Prints:

first file: f1, 1, 1 first line
first file: f1, 2, 2 second line
f2, 1, 3 first line
f2, 2, 4 second line
f3, 1, 5 first line
f3, 2, 6 second line

Definitely not as elegant as awk but it works.


Note that Ruby has support for FNR==NR type logic.

dawg
  • 98,345
  • 23
  • 131
  • 206