tsk1979 has asked for the wisdom of the Perl Monks concerning the following question:
Right now I have a script which reads in two files, first file containing a list of line numbers, and second file is a text file. This script prints the lines and 2 following lines as specified by the line number.
For example the line number script can be
44
87
345
This script will display line numbers 44,45,46 87,88,89 and 345 346 347.
I was looking for a way to do this without using the perl script, ie just using a single command line.
Any leads?
Re: Printing specific line numbers
by ikegami (Patriarch) on Jun 01, 2006 at 18:31 UTC
|
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] |
Re: Printing specific line numbers
by davido (Cardinal) on Jun 01, 2006 at 19:57 UTC
|
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] |
Re: Printing specific line numbers
by davidrw (Prior) on Jun 01, 2006 at 19:12 UTC
|
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] |
Re: Printing specific line numbers
by GrandFather (Saint) on Jun 01, 2006 at 18:22 UTC
|
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] |
Re: Printing specific line numbers
by socketdave (Curate) on Jun 01, 2006 at 18:18 UTC
|
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] |
Re: Printing specific line numbers
by Anonymous Monk on Jun 01, 2006 at 23:23 UTC
|
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] |
|
|