my @line_nums = (44, 87, 345);
my $line_num = shift(@line_nums);
my $more = 2;
while (<>) {
next if $. != $line_num;
print;
if ($more) {
--$more;
++$line_num;
shift(@line_nums) if @line_nums && $line_nums[0] == $line_num;
next;
}
last unless @line_nums;
$line_num = shift(@line_nums);
$more = 2;
}
Tested:
- Handles overlap. For example, asking for lines 1, 2 & 4 prints lines 1, 2, 3, 4, 5 & 6.
- Handles end of file. For example, asking for line 15 of a file with 16 lines prints lines 15 and 16 (and doesn't give an error for the missing line 17).
Features:
- Efficient. Only looks at one element of @line_nums per line of the input file.
- Efficient. Stops reading the input file if there are no more lines to print.
Limitations:
- Assumes @line_nums is sorted. This assumption is easy to remove.
To do:
- Read the line numbers one at a time, instead of having them all in memory.
| [reply] [d/l] [select] |
The following method stores the lines you wish to print in a hash as an action tabletm. This allows for a fairly simple approach to reading the second text file, and deciding line by line whether to print or not. This method is fairly efficient:
use strict;
use warnings;
my %find;
open INDEX, '<', shift( @ARGV ) or die $!;
while( <INDEX> ) {
@find{ $_ .. $_ + 2 } = ();
}
close INDEX;
while( <> ) {
next unless exists $find{$.};
print $_;
delete $find{$.};
last unless keys %find;
}
Advantages:
- The script won't error-out if you request an index beyond the end of the text file.
- Overlap is fine. Requesting lines 1, 2, and 4 will print 1, 2, 3, 4, 5, 6, not 1, 2, 2, 3, 4, 4, 5, 6.
- There's no need to sort the line index list.
- Terminates the search once there are no more indices in the list.
The above code is "one liner friendly", and can be expressed like this:
perl -ne "BEGIN{open I, '<', shift(@ARGV); while(<I>){ @find{$_ .. $_+
+2}=();}} next unless exists $find{$.}; print; delete $find{$.} last u
+nless keys %find;"
| [reply] [d/l] [select] |
you can use Tie::File and treat the file as an array, and take an array slice:
use Tie::File;
use Fcntl 'O_RDONLY';
my @array;
my $filename = '/etc/passwd';
tie @array, 'Tie::File', $filename, mode=>O_RDONLY or die;
open NUMS, "line_num_file" or die;
my @line_nums = map { s/\s+$//s; $_ } <NUMS>;
close NUMS;
my @lines = @array[ @line_numes ];
| [reply] [d/l] |
And as requested, here's a command-line version of that:
perl -MTie::File -ne 'BEGIN{tie(@f,"Tie::File",shift,mode=>0,autochomp
+=>0) or die "tie failed $!"} print@f[$_..$_+2]'
Give the filename to read first, and any other filenames given will contain line numbers; if only one filename is given, numbers come from STDIN. For example:
$ echo 0 |perl -MTie::File -ne 'BEGIN{tie(@f,"Tie::File",shift,mode=>0
+,autochomp=>0) or die "tie failed $!"} print@f[$_..$_+2]' /etc/passwd
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
$
| [reply] [d/l] [select] |
Use $. to determine the current file line number. I don't do command line, but the following should point you in the right direction:
use strict;
use warnings;
my $tempFileName = 'temp.txt';
open tempFile, '>', $tempFileName;
print tempFile <<TEMP;
Line one
Line two
Line three
Line four
Line five
TEMP
close tempFile;
open tempFile, '<', $tempFileName;
while (<tempFile>) {
print "$_" if $. >= 2 && $. <= 4;
}
close tempFile;
Prints:
Line two
Line three
Line four
DWIM is Perl's answer to Gödel
| [reply] [d/l] [select] |
If you have head and tail:
head -n 46 filename|tail -n 3
head -n 89 filename|tail -n 3
head -n 347 filename|tail -n 3
| [reply] [d/l] |
Come on, someone needs to golf it better than that!
perl -e '$cond = join " || ", map { "($_ .. \$. == $_ + 2)" } do { loc
+al @ARGV = shift; <> }; eval qq{ while(<>) { print if $cond } };' lin
+es test.txt
Which, when the whitespace is fully condensed, leaves you with:
perl -e'$cond=join"||",map{"($_..\$.==$_+2)"}do{local@ARGV=shift;<>};e
+val"($cond)&&print while<>"' lines test.txt
| [reply] [d/l] [select] |