Как я могу напечатать определенные строки из файла в Unix? - PullRequest
4 голосов
/ 23 июля 2010

Я хочу напечатать определенные строки из текстового файла в Unix. Номера строк, которые будут напечатаны, перечислены в другом текстовом файле (по одному в каждой строке).

Есть ли быстрый способ сделать это с помощью Perl или сценария оболочки?

Ответы [ 10 ]

3 голосов
/ 23 июля 2010
$ 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
3 голосов
/ 23 июля 2010

Предполагается, что номера строк для печати сортируются.

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;
}
2 голосов
/ 23 июля 2010

Вы можете избежать ограничений некоторых других ответов (требований для отсортированных строк), просто используя eof в контексте базового блока while(<>). Это скажет вам, когда вы перестали читать номера строк и начали читать данные. Обратите внимание, что вам нужно сбросить $. при переключении.

# 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{$.};    
    }
}
1 голос
/ 25 июля 2010

Вот способ сделать это в Perl, не отсекая ничего, чтобы объем памяти программы не зависел от размеров обоих файлов (предполагается, что номера строк, которые будут напечатаны, отсортированы):

#!/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;

Создать исходный файл:

C:\>  perl -le "print for aa .. zz" > src

Создать файл фильтра:

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

Выход:

C:\> f src filter
ad
af
aj
al
am
as
aw
ax
bb
br
bw
bx

Чтобы работать с несортированным файлом фильтра, вы можете изменить цикл while:

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>;
    }
}

Это потратило бы много времени, если содержимое файла фильтра было довольно случайным, поскольку оно продолжало бы перематываться в начало исходного файла. В этом случае я бы рекомендовал использовать Tie :: File .

1 голос
/ 23 июля 2010

кошка-н фу | присоединиться к foo2 - | cut -d "" -f2-

где foo - ваш файл с строками для печати, а foo2 - ваш файл с номерами строк

0 голосов
/ 25 июля 2010

Вот способ сделать это, используя 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;
0 голосов
/ 23 июля 2010
$ 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

Если это слишком много для однострочника, используйте

#! /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 допускает творческие возможности, такие как

$ ./lines-between 'tac lines-between|'
  print if $. == $start .. $. == $stop;
while (<>) {

0 голосов
/ 23 июля 2010

Это короткое решение с использованием bash и sed

sed -n -e "$(cat num |sed 's/$/p/')" file

Где num - файл чисел, а file - входной файл (протестировано на 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
0 голосов
/ 23 июля 2010

Я бы сделал это так:

#!/bin/bash
numbersfile=numbers
datafile=data

while read lineno < $numbersfile; do
    sed -n "${lineno}p" datafile
done

Недостатком моего подхода является то, что он порождает много процессов, поэтому он будет медленнее, чем другие варианты. Это бесконечно более читабельно, хотя.

0 голосов
/ 23 июля 2010

Я бы так не поступил с большими файлами, но (не проверено):

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;
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...