Perl : Adding 2 files line by line - perl

I am a beginner in perl, so please bear with me.
I have 2 files:
1
2
3
and
2
4
5
6
I want to create a new file that is the sum of the above 2 files:
output file:
3
6
8
6
What I am doing right now is reading the files as arrays and adding them element by element.
To add the arrays I am using the following:
$asum[#asum] = $array1[#asum] + $array2[#asum] while defined $array1[#asum] or defined $array2[#asum];
But this is giving the following error:
Argument "M-oM-;M-?3" isn't numeric in addition (+) at perl_ii.pl line 30.
Argument "M-oM-;M-?1" isn't numeric in addition (+) at perl_ii.pl line 30.
Use of uninitialized value in addition (+) at perl_ii.pl line 30.
I am using the following code to read files as arrays:
use strict;
use warnings;
my #array1;
open(my $fh, "<", "file1.txt") or die "Failed to open file1\n";
while(<$fh>) {
chomp;
push #array1, $_;
}
close $fh;
my #array2;
open(my $fh1, "<", "file2.txt") or die "Failed to open file2\n";
while(<$fh1>) {
chomp;
push #array2, $_;
}
close $fh1 ;
Anyone could tell me how to fix this, or give a better approach altogether?

Here is another Perl solution that makes use of the diamond, <>, file read operator. This reads in files specified on the command line, (rather than explicitly opening them within the program). Sorry, I can't find the part of the docs that explains this for a read.
The command line for this program would look like:
perl myprogram.pl file1 file2 > outputfile
Where file1 and file2 are the 2 input files and outputfile is the file you want to print the results of the addition.
#!/usr/bin/perl
use strict;
use warnings;
my #sums;
my $i = 0;
while (my $num = <>) {
$sums[$i++] += $num;
$i = 0 if eof;
}
print "$_\n" for #sums;
Note: $i is reset to zero at the end of file, (in this case after the first file is read). Actually, it is also reset to 0 after the second file is read. This has no effect on the program however, because there are no files to be read after the second file in your example.

The following solution makes the memory footprint of the program independent of the sizes of the files. Instead, now the memory footprint only depends on the number of files:
#!/usr/bin/env perl
use strict;
use warnings;
use Carp qw( croak );
use List::Util qw( sum );
use Path::Tiny;
run(#ARGV);
sub run {
my #readers = map make_reader($_), #_;
while (my #obs = grep defined, map $_->(), #readers) {
print sum(#obs), "\n";
}
return;
}
sub make_reader {
my $fname = shift;
my $fhandle = path( $fname )->openr;
my $is_readable = 1;
sub {
return unless $is_readable;
my $line = <$fhandle>;
return $line if defined $line;
close $fhandle
or croak "Cannot close '$fname': $!";
$is_readable = 0;
return;
}
}

You have two different problems with your script now:
First error
Argument "M-oM-;M-?3" isn't numeric in addition (+) at perl_ii.pl line
30
happens because your input files are saved in Unicode and first line is read with "\xFF\xFE" BOM bytes.
To fix it simply, just resave the files as ANSI text. If Unicode is required, then remove these bytes from first string you read from file.
Second error
Use of uninitialized value in addition (+) at perl_ii.pl line 30.
happens because you access 4th element in first array that doesn't exist. Remember, you select maximal input array length as index limit. To fix it just add following condition for input element:
$asum[#asum] = (#asum < #array1 ? $array1[#asum] : 0) + (#asum < #array2 ? $array2[#asum] : 0) while defined $array1[#asum] or defined $array2[#asum];

The logic of reading your two files is the same, and I suggest using a subroutine for that and calling it twice:
#!/usr/bin/env perl
use strict;
use warnings;
my #array1 = read_into_array('file1.txt');
my #array2 = read_into_array('file2.txt');
sub read_into_array
{
my $filename = shift;
my #array;
open(my $fh, "<", $filename) or die "Failed to open $filename: $!\n";
while(<$fh>) {
chomp;
push #array, $_;
}
close $fh;
return #array;
}
But that's just an observation I made and not a solution to your problem. As CodeFuller already said, you should re-save your files as plain ASCII instead of UTF-8.
The second problem, Use of uninitialized value in addition (+), can also be solved with the Logical Defined Or operator // which was introduced in Perl 5.10:
my #asum;
$asum[#asum] = ($array1[#asum] // 0)
+ ($array2[#asum] // 0)
while defined $array1[#asum] or defined $array2[#asum];
No, this is not a comment, but an operator very similar to ||. The difference is that it triggers when the left-hand-side (lhs) is undef while the || triggers when the lhs is falsy (i.e. 0, '' or undef). Thus
$array1[#asum] // 0
gives 0 if $array1[#asum] is undef. It's the same as
defined($array1[#asum]) ? $array1[#asum] : 0

A different approach altogether:
$ paste -d '+' file1 file2 | sed 's/^+//;s/+$//' | bc
3
6
8
6
The paste command prints the files next to each other, separated by a + sign:
$ paste -d '+' file1 file2
1+2
2+4
3+5
+6
The sed command removes leading and trailing + signs, because those trip up bc:
$ paste -d '+' file1 file2 | sed 's/^+//;s/+$//'
1+2
2+4
3+5
6
And bc finally calculates the sums.

Here’s a rendition of Sinan’s approach in a more Perlish form:
#!/usr/bin/env perl
use 5.010; use strict; use warnings;
use autodie;
use List::Util 'sum';
my #fh = map { open my $fh, '<', $_; $fh } #ARGV;
while ( my #value = grep { defined } map { scalar readline $_ } #fh ) {
say sum #value;
#fh = grep { not eof $_ } #fh if #value < #fh;
}

Related

Can a Perl program know the line number where __DATA__ begins?

Is there a way to get the line number (and maybe filename) where a __DATA__ token was coded? Or some other way to know the actual line number in the original source file where a line of data read from the DATA filehandle came from?
Note that $. counts from 1 when reading from the DATA filehandle. So if the line number of the __DATA__ token were added to $. it would be what I'm looking for.
For example:
#!/usr/bin/perl
while (<DATA>) {
my $n = $. + WHAT??;
die "Invalid data at line $n\n" if /bad/;
}
__DATA__
something good
something bad
I want this to say "Invalid data at line 9", not "line 2" (which is what you get if $. is used by itself).
In systems that support /proc/<pid> virtual filesystems (e.g., Linux), you can do:
# find the file where <DATA> handle is read from
my $DATA_FILE = readlink("/proc/$$/fd/" . fileno(*DATA));
# find the line where DATA begins
open my $THIS, "<", $DATA_FILE;
my #THIS = <$THIS>;
my ($DATA_LINE) = grep { $THIS[$_] =~ /^__DATA__\b/ } 0 .. $#THIS;
File don't actually have lines; they're just sequences of bytes. The OS doesn't even offer the capability of getting a line from a file, so it has no concept of line numbers.
Perl, on the other hand, does keep track of a line number for each handle. It is accessed via $..
However, the Perl handle DATA is created from a file descriptor that's already been moved to the start of the data —it's the file descriptor that Perl itself uses to load and parse the file— so there's no record of how many lines have already been read. So the line 1 of DATA is the first line after __DATA__.
To correct the line count, one must seek back to the start of the file, and read it line by line until the file handle is back at the same position it started.
#!/usr/bin/perl
use strict;
use warnings qw( all );
use Fcntl qw( SEEK_SET );
# Determines the line number at the current file position without using «$.».
# Corrects the value of «$.» and returns the line number.
# Sets «$.» to «1» and returns «undef» if unable to determine the line number.
# The handle is left pointing to the same position as when this was called, or this dies.
sub fix_line_number {
my ($fh) = #_;
( my $initial_pos = tell($fh) ) >= 0
or return undef;
seek($fh, 0, SEEK_SET)
or return undef;
$. = 1;
while (<$fh>) {
( my $pos = tell($fh) ) >= 0
or last;
if ($pos >= $initial_pos) {
if ($pos > $initial_pos) {
seek($fh, $initial_pos, SEEK_SET)
or die("Can't reset handle: $!\n");
}
return $.;
}
}
seek($fh, $initial_pos, SEEK_SET)
or die("Can't reset handle: $!\n");
$. = 1;
return undef;
}
my $prefix = fix_line_number(\*DATA) ? "" : "+";
while (<DATA>) {
printf "%s:%s: %s", __FILE__, "$prefix$.", $_;
}
__DATA__
foo
bar
baz
Output:
$ ./a.pl
./a.pl:48: foo
./a.pl:49: bar
./a.pl:50: baz
$ perl <( cat a.pl )
/dev/fd/63:+1: foo
/dev/fd/63:+2: bar
/dev/fd/63:+3: baz
Perl keeps track of the file and line at which each symbol is created. A symbol is normally created when the parser/compiler first encounters it. But if __DATA__ is encountered before DATA is otherwise created, this will create the symbol. We can take advantage of this to set the line number associated with the file handle in DATA.
For the case where the Package::DATA handle is not used in Package.pm itself, the line number of the __DATA__ token could be obtained via B::GV->LINE on the DATA handle:
$ cat Foo.pm
package Foo;
1;
__DATA__
good
bad
$ perl -I. -MFoo -MB -e '
my $ln = B::svref_2object(\*Foo::DATA)->LINE;
warn "__DATA__ at line $ln\n";
Foo::DATA->input_line_number($ln);
while(<Foo::DATA>){ die "no good" unless /good/ }
'
__DATA__ at line 4
no good at -e line 1, <DATA> line 6.
In the case where the DATA handle is referenced in the file itself, a possible kludge would be to use an #INC hook:
$ cat DH.pm
package DH;
unshift #INC, sub {
my ($sub, $fname) = #_;
for(#INC){
if(open my $fh, '<', my $fpath = "$_/$fname"){
$INC{$fname} = $fpath;
return \'', $fh, sub {
our (%ln, %pos);
if($_){ $pos{$fname} += length; ++$ln{$fname} }
}
}
}
};
$ cat Bar.pm
package Bar;
print while <DATA>;
1;
__DATA__
good
bad
$ perl -I. -MDH -MBar -e '
my $fn = "Bar.pm";
warn "__DATA__ at line $DH::ln{$fn} pos $DH::pos{$fn}\n";
seek Bar::DATA, $DH::pos{$fn}, 0;
Bar::DATA->input_line_number($DH::ln{$fn});
while (<Bar::DATA>){ die "no good" unless /good/ }
'
good
bad
__DATA__ at line 6 pos 47
no good at -e line 6, <DATA> line 8.
Just for the sake of completion, in the case where you do have control over the file, all could be easily done with:
print "$.: $_" while <DATA>;
BEGIN { our $ln = __LINE__ + 1; DATA->input_line_number($ln) }
__DATA__
...
You can also use the first B::GV solution, provided that you reference the DATA handle via an eval:
use B;
my ($ln, $data) = eval q{B::svref_2object(\*DATA)->LINE, \*DATA}; die $# if $#;
$data->input_line_number($ln);
print "$.: $_" while <$data>;
__DATA__
...
None of these solutions assumes that the source file are seekable (except if you want to read the DATA more than once, as I did in the second example), or try to reparse your files, etc.
Comparing the end of the file to itself in reverse might do what you want:
#!/usr/bin/perl
open my $f, "<", $0;
my #lines;
my #dataLines;
push #lines ,$_ while <$f>;
close $f;
push #dataLines, $_ while <DATA>;
my #revLines= reverse #lines;
my #revDataLines=reverse #dataLines;
my $count=#lines;
my $offset=0;
$offset++ while ($revLines[$offset] eq $revDataLines[$offset]);
$count-=$offset;
print "__DATA__ section is at line $count\n";
__DATA__
Hello there
"Some other __DATA__
lkjasdlkjasdfklj
ljkasdf
Running give a output of :
__DATA__ section is at line 19
The above script reads itself (using $0 for file name) into the #lines array and reads the DATA file into the #dataLines array.
The arrays are reversed and then compared element by element until they are different. The number of lines are tracked in $offset and this is subtracted from the $count variable which is the number of lines in the file.
The result is the line number the DATA section starts at. Hope that helps.
Thank you #mosvy for the clever and general idea.
Below is a consolidated solution which works anywhere. It uses a symbolic reference instead of eval to avoid mentioning "DATA" at compile time, but otherwise uses the same ideas as mosvy.
The important point is that code in a package containing __DATA__ must not refer to the DATA symbol by name so that that symbol won't be created until the compiler sees the __DATA__ token. The way to avoid mentioning DATA is to use a filehandle ref created at run-time.
# Get the DATA filehandle for a package (default: the caller's),
# fixed so that "$." provides the actual line number in the
# original source file where the last-read line of data came
# from, rather than counting from 1.
#
# In scalar context, returns the fixed filehandle.
# In list context, returns ($fh, $filename)
#
# For this to work, a package containing __DATA__ must not
# explicitly refer to the DATA symbol by name, so that the
# DATA symbol (glob) will not yet be created when the compiler
# encounters the __DATA__ token.
#
# Therefore, use the filehandle ref returned by this
# function instead of DATA!
#
sub get_DATA_fh(;$) {
my $pkg = $_[0] // caller;
# Using a symbolic reference to avoid mentioning "DATA" at
# compile time, in case we are reading our own module's __DATA__
my $fh = do{ no strict 'refs'; *{"${pkg}::DATA"} };
use B;
$fh->input_line_number( B::svref_2object(\$fh)->LINE );
wantarray ? ($fh, B::svref_2object(\$fh)->FILE) : $fh
}
Usage examples:
my $fh = get_DATA_fh; # read my own __DATA__
while (<$fh>) { print "$. : $_"; }
or
my ($fh,$fname) = get_DATA_fh("Otherpackage");
while (<$fh>) {
print " $fname line $. : $_";
}

How to find lines containing a match between two files in perl?

I'm a novice at using perl. What I want to do is compare two files. One is my index file that I am calling "temp." I am attempting to use this to search through a main file that I am calling "array." The index file has only numbers in it. There are lines in my array that have those numbers. I've been trying to find the intersection between those two files, but my code is not working. Here's what I've been trying to do.
#!/usr/bin/perl
print "Enter the input file:";
my $filename=<STDIN>;
open (FILE, "$filename") || die "Cannot open file: $!";
my #array=<FILE>;
close(FILE);
print "Enter the index file:";
my $temp=<STDIN>;
open (TEMP, "$temp") || die "Cannot open file: $!";
my #temp=<TEMP>;
close(TEMP);
my %seen= ();
foreach (#array) {
$seen{$_}=1;
}
my #intersection=grep($seen{$_}, #temp);
foreach (#intersection) {
print "$_\n";
}
If I can't use intersection, then what else can I do to move each line that has a match between the two files?
For those of you asking for the main file and the index file:
Main file:
1 CP TRT
...
14 C1 MPE
15 C2 MPE
...
20 CA1 MPE
Index file
20
24
22
17
18
...
I want to put those lines that contain one of the numbers in my index file into a new array. So using this example, only
20 CA1 MPE would be placed into a new array.
My main file and index file are both longer than what I've shown, but that hopefully gives you an idea on what I'm trying to do.
I am assuming something like this?
use strict;
use warnings;
use Data::Dumper;
# creating arrays instead of reading from file just for demo
# based on the assumption that your files are 1 number per line
# and no need for any particular parsing
my #array = qw/1 2 3 20 60 50 4 5 6 7/;
my #index = qw/10 12 5 3 2/;
my #intersection = ();
my %hash1 = map{$_ => 1} #array;
foreach (#index)
{
if (defined $hash1{$_})
{
push #intersection, $_;
}
}
print Dumper(\#intersection);
==== Out ====
$VAR1 = [
'5',
'3',
'2'
];
A few things:
Always have use strict; and use warnings; in your program. This will catch a lot of possible errors.
Always chomp after reading input. Perl automatically adds \n to the end of lines read. chomp removes the \n.
Learn a more modern form of Perl.
Use nemonic variable names. $temp doesn't cut it.
Use spaces to help make your code more readable.
You never stated the errors you were getting. I assume it has to do with the fact that the input from your main file doesn't match your index file.
I use a hash to create an index that the index file can use via my ($index) = split /\s+/, $line;:
#! /usr/bin/env perl
#
use strict;
use warnings;
use autodie;
use feature qw(say);
print "Input file name: ";
my $input_file = <STDIN>;
chomp $input_file; # Chomp Input!
print "Index file name: ";
my $index_file = <STDIN>;
chomp $index_file; # Chomp Input!
open my $input_fh, "<", $input_file;
my %hash;
while ( my $line = <$input_fh> ) {
chomp $line;
#
# Using split to find the item to index on
#
my ($index) = split /\s+/, $line;
$hash{$index} = $line;
}
close $input_fh;
open my $index_fh, "<", $index_file;
while ( my $index = <$index_fh> ) {
chomp $index;
#
# Now index can look up lines
#
if( exists $hash{$index} ) {
say qq(Index: $index Line: "$hash{$index}");
}
else {
say qq(Index "$index" doesn't exist in file.);
}
}
#!/usr/bin/perl
use strict;
use warnings;
use autodie;
#ARGV = 'main_file';
open(my $fh_idx, '<', 'index_file');
chomp(my #idx = <$fh_idx>);
close($fh_idx);
while (defined(my $r = <>)) {
print $r if grep { $r =~ /^[ \t]*$_/ } #idx;
}
You may wish to replace those hardcoded file names for <STDIN>.
FYI: The defined call inside a while condition might be "optional".

Perl how to read a line from a file handle that is an array element

My perl script takes any number of files, opens them, and reads one line from each. But it's not working. Here's my code.
#!/bin/perl
$numfile = scalar #ARGV;
for ($i = 0; $i < $numfile; ++$i)
{
open $fh[$i],"<",$ARGV[$i];
$line[$i] = <$fh[$i]>;
}
for ($i = 0; $i < $numfile; ++$i)
{
print "$i => $line[$i]";
}
Any ideas why this doesn't work? Is it illegal to store file handles in an array?
I expect this to print the first line of each file. Instead I get
0 => GLOB(0x36d190)1 =>
I am using perl v5.18.2
Use readline instead of <>.
perlop says:
If what's within the angle brackets is neither a filehandle nor a simple scalar variable containing a filehandle name, typeglob,
or typeglob reference, it is interpreted as a filename pattern to be globbed
Your <> is being interpreted as a file glob instead of a readline.
Use the following to explicitly specify your intent:
$line[$i] = readline $fh[$i];
[Not an answer, but a comment that doesn't fit as a comment]
Always use use strict; use warnings;. You are severely handicapping yourself without them.
You don't need to use multiple handles since you never need more than one at a time.
Some other cleanups:
#!/bin/perl
use strict;
use warnings;
my #first_lines;
for my $qfn (#ARGV)
{
open(my $fh, '<', $qfn)
or die("Can't open $qfn: $!\n");
push #first_lines, scalar( <$fh> );
}
for my $i (0..$#first_lines)
{
print "$i => $first_lines[$i]";
}

Print matching lines without using a hash

i want to match two different file for similar line in both the files without using hash function. but i am getting no clue of doing that. can you please help
here is my input:
file1:
delhi
bombay
kolkata
shimla
ghuhati
File2:
london
delhi
jammu
punjab
shimla
output:
delhi
shimla
Stub of code:
#/usr/bin/perl
use strict;
use warnings;
my #line1 = <file1>;
my #line2 = <file2>;
while (<file1>) {
do stuff;
}
You really need a very good reason not to use a hash to solve this problem. Is it a homework assignment?
This will do what you ask. It reads all of file1 into an array, then reads through file2 one line at a time, using grep to check whether the name appears in the array.
use strict;
use warnings;
use autodie;
my #file1 = do {
open my $fh, '<', 'file1.txt';
<$fh>;
};
chomp #file1;
open my $fh, '<', 'file2.txt';
while (my $line = <$fh>) {
chomp $line;
print "$line\n" if grep { $_ eq $line } #file1;
}
output
delhi
shimla
Of course you can do it without using hash, in a very inefficient way:
my #line1 = <file1>;
my #line2 = <file2>;
foreach (#line1) {
if (isElement($_, \#line2)) {
print;
}
}
sub isElement {
my ($ele, $arr) = #_;
foreach (#$arr) {
if ($ele eq $_) {
return 1;
}
}
return 0;
}
You don't want to use hashes? Can I use modules?
The easiest way may be to avoid using Perl, but use the Unix sort and comm utilities:
$ sort -u file1.txt > file1.sort.txt
$ sort -u file2.txt > file2.sort.txt
# comm -12 file1.sort.txt file2.sort.txt
The comm utility prints out three columns: The first column are lines found only in the first file. The second column are lines found only in the second file, and the last column are lines found in both files. The -12 parameter suppresses the printing of columns #1 and #2 giving you only the lines in common.
You could use a double loop where you compare each value with each other one as Lee Duhem did, but that's pretty inefficient. The time it takes increases by the square of the number of items.

How can I print specific lines from a file in Unix?

I want to print certain lines from a text file in Unix. The line numbers to be printed are listed in another text file (one on each line).
Is there a quick way to do this with Perl or a shell script?
Assuming the line numbers to be printed are sorted.
open my $fh, '<', 'line_numbers' or die $!;
my #ln = <$fh>;
open my $tx, '<', 'text_file' or die $!;
foreach my $ln (#ln) {
my $line;
do {
$line = <$tx>;
} until $. == $ln and defined $line;
print $line if defined $line;
}
$ cat numbers
1
4
6
$ cat file
one
two
three
four
five
six
seven
$ awk 'FNR==NR{num[$1];next}(FNR in num)' numbers file
one
four
six
You can avoid the limitations of the some of the other answers (requirements for sorted lines), simply by using eof within the context of a basic while(<>) block. That will tell you when you've stopped reading line numbers and started reading data. Note that you need to reset $. when the switch occurs.
# Usage: perl script.pl LINE_NUMS_FILE DATA_FILE
use strict;
use warnings;
my %keep;
my $reading_line_nums = 1;
while (<>){
if ($reading_line_nums){
chomp;
$keep{$_} = 1;
$reading_line_nums = $. = 0 if eof;
}
else {
print if exists $keep{$.};
}
}
cat -n foo | join foo2 - | cut -d" " -f2-
where foo is your file with lines to print and foo2 is your file of line numbers
Here is a way to do this in Perl without slurping anything so that the memory footprint of the program is independent of the sizes of both files (it does assume that the line numbers to be printed are sorted):
#!/usr/bin/perl
use strict; use warnings;
use autodie;
#ARGV == 2
or die "Supply src_file and filter_file as arguments\n";
my ($src_file, $filter_file) = #ARGV;
open my $src_h, '<', $src_file;
open my $filter_h, '<', $filter_file;
my $to_print = <$filter_h>;
while ( my $src_line = <$src_h> ) {
last unless defined $to_print;
if ( $. == $to_print ) {
print $src_line;
$to_print = <$filter_h>;
}
}
close $filter_h;
close $src_h;
Generate the source file:
C:\> perl -le "print for aa .. zz" > src
Generate the filter file:
C:\> perl -le "print for grep { rand > 0.75 } 1 .. 52" > filter
C:\> cat filter
4
6
10
12
13
19
23
24
28
44
49
50
Output:
C:\> f src filter
ad
af
aj
al
am
as
aw
ax
bb
br
bw
bx
To deal with an unsorted filter file, you can modified the while loop:
while ( my $src_line = <$src_h> ) {
last unless defined $to_print;
if ( $. > $to_print ) {
seek $src_h, 0, 0;
$. = 0;
}
if ( $. == $to_print ) {
print $src_line;
$to_print = <$filter_h>;
}
}
This would waste a lot of time if the contents of the filter file are fairly random because it would keep rewinding to the beginning of the source file. In that case, I would recommend using Tie::File.
I wouldn't do it this way with large files, but (untested):
open(my $fh1, "<", "line_number_file.txt") or die "Err: $!";
chomp(my #line_numbers = <$fh1>);
$_-- for #line_numbers;
close $fh1;
open(my $fh2, "<", "text_file.txt") or die "Err: $!";
my #lines = <$fh2>;
print #lines[#line_numbers];
close $fh2;
I'd do it like this:
#!/bin/bash
numbersfile=numbers
datafile=data
while read lineno < $numbersfile; do
sed -n "${lineno}p" datafile
done
Downside to my approach is that it will spawn a lot of processes so it will be slower than other options. It's infinitely more readable though.
This is a short solution using bash and sed
sed -n -e "$(cat num |sed 's/$/p/')" file
Where num is the file of numbers and file is the input file ( Tested on OS/X Snow leopard)
$ cat num
1
3
5
$ cat file
Line One
Line Two
Line Three
Line Four
Line Five
$ sed -n -e "$(cat num |sed 's/$/p/')" file
Line One
Line Three
Line Five
$ cat input
every
good
bird
does
fly
$ cat lines
2
4
$ perl -ne 'BEGIN{($a,$b) = `cat lines`} print if $.==$a .. $.==$b' input
good
bird
does
If that's too much for a one-liner, use
#! /usr/bin/perl
use warnings;
use strict;
sub start_stop {
my($path) = #_;
open my $fh, "<", $path
or die "$0: open $path: $!";
local $/;
return ($1,$2) if <$fh> =~ /\s*(\d+)\s*(\d+)/;
die "$0: $path: could not find start and stop line numbers";
}
my($start,$stop) = start_stop "lines";
while (<>) {
print if $. == $start .. $. == $stop;
}
Perl's magic open allows for creative possibilities such as
$ ./lines-between 'tac lines-between|'
print if $. == $start .. $. == $stop;
while (<>) {
Here is a way to do this using Tie::File:
#!/usr/bin/perl
use strict; use warnings;
use autodie;
use Tie::File;
#ARGV == 2
or die "Supply src_file and filter_file as arguments\n";
my ($src_file, $filter_file) = #ARGV;
tie my #source, 'Tie::File', $src_file, autochomp => 0
or die "Cannot tie source '$src_file': $!";
open my $filter_h, '<', $filter_file;
while ( my $to_print = <$filter_h> ) {
print $source[$to_print - 1];
}
close $filter_h;
untie #source;