Как я могу напечатать соответствующую строку, одну строку непосредственно над ней и одну строку сразу под ней? - PullRequest
4 голосов
/ 06 октября 2009

Из соответствующего вопроса, заданного Би, я узнал, как напечатать соответствующую строку вместе со строкой непосредственно под ней. Код выглядит действительно просто:

#!perl
open(FH,'FILE');
while ($line = <FH>) {
    if ($line =~ /Pattern/) {
        print "$line";
        print scalar <FH>;
    }
}

Затем я искал в Google другой код, который может печатать совпадающие строки со строками непосредственно над ними. Код, который частично соответствует моей цели, выглядит примерно так:

#!perl

@array;
open(FH, "FILE");
while ( <FH> ) {
  chomp;
  $my_line = "$_";
  if ("$my_line" =~ /Pattern/) {
      foreach( @array ){
          print "$_\n";
      }
      print "$my_line\n"
  }
  push(@array,$my_line);
  if ( "$#array" > "0" ) {
    shift(@array);
  }
};

Проблема в том, что я до сих пор не могу понять, как сделать их вместе. Кажется, мой мозг отключается. У кого-нибудь есть какие-либо идеи?

Спасибо за любую помощь.

UPDATE:

Я думаю, что я отчасти тронут. Вы, ребята, так полезны! Возможно, немного не по теме, но я действительно чувствую желание сказать больше.

Мне была нужна программа Windows, способная искать содержимое нескольких файлов и отображать соответствующую информацию без необходимости отдельно открывать каждый файл. Я попробовал поиск в Google, и два приложения, Agent Ransack и Devas, оказались полезными, но они отображают только строки, содержащие сопоставленный запрос, и я хочу также посмотреть соседние строки. Тогда идея импровизировать программы возникла у меня в голове. Несколько лет назад я был впечатлен Perl-скриптом, который мог генерировать формат Wikipedia для Tomeraider, чтобы я мог легко искать Wiki на своем Lifedrive, и я также где-то читал в сети, что Perl легко изучить, особенно для такого парня, как я не имеет опыта работы с языками программирования. Затем я начал учить себя Perl пару дней назад. Моим первым шагом было научиться выполнять ту же работу, что и «Agent Ransack», и с Perl это оказалось не так сложно. Сначала я узнал, как искать содержимое одного файла и отображать совпадающие строки через модификацию примера, использованного в книге под названием «Perl by Example», но я застрял там. Я стал совершенно не в курсе, как работать с несколькими файлами. Подобных примеров не было найдено в книге или, возможно, потому, что я был слишком нетерпелив. А потом я снова попробовал поискать в Google, и меня привели сюда, и я задал свой первый вопрос: «Как мне найти несколько файлов для поиска строкового паттерна в Perl?» вот и надо сказать этот кровавый форум УДИВИТЕЛЬНЫЙ;). Затем я просмотрел несколько примеров сценариев, а вчера я придумал следующий код, и он вполне соответствует моей первоначальной цели:

Коды выглядят так:

#!perl

$hits=0;
print "INPUT YOUR QUERY:";
chop ($query = <STDIN>);
$dir = 'f:/corpus/'; 
@files = <$dir/*>;
foreach $file (@files) {
open   (txt, "$file");

while($line = <txt>) {
if ($line =~ /$query/i) {   
$hits++;
print "$file \n $line";     
print scalar <txt>;
}
}
}
close(txt);
print "$hits RESULTS FOUND FOR THIS SEARCH\n";

В папке «corpus» у меня много текстовых файлов, включая файлы srt pdf doc, которые содержат следующее содержимое:

Потом я бросил тело.

J'ai mis le corps dans une décharge.

Я знаю, у тебя есть провод.

Je sais que tu as un micro.

Теперь я скажу вам правду.

Все в порядке.

По сути, мне просто нужно найти английскую фразу и посмотреть на французский эквивалент, поэтому сценарий, который я закончил вчера, вполне удовлетворителен, за исключением того, что было бы лучше, если бы мой сценарий мог отображать вышеуказанную строку в случае, если я хочу найти Французская фраза и английская проверка. Поэтому я пытаюсь улучшить код. На самом деле я знал, что «скаляр печати» глючит, но он аккуратен и выполняет печать следующей строки, по крайней мере, большую часть времени). Я даже ожидал ДРУГОЙ ОДНОЙ волшебной линии, которая печатает предыдущую строку вместо следующей :) Perl кажется забавным. Я думаю, что буду проводить больше времени, пытаясь лучше понять это. И по предложению daotoad я изучу коды, щедро предложенные вами, ребята. Еще раз спасибо вам, ребята!

Ответы [ 8 ]

10 голосов
/ 06 октября 2009

Вероятно, будет проще использовать grep для этого, поскольку он позволяет печатать строки до и после матча. Используйте -B и -A для печати контекста до и после совпадения соответственно. Смотри http://ss64.com/bash/grep.html

7 голосов
/ 06 октября 2009

Вот модернизированная версия превосходного ответа Пакс:

use strict;
use warnings;

open( my $fh, '<', 'qq.in') 
    or die "Error opening file - $!\n";

my $this_line = "";
my $do_next = 0;

while(<$fh>) {
    my $last_line = $this_line;
    $this_line = $_;

    if ($this_line =~ /XXX/) {
        print $last_line unless $do_next;
        print $this_line;
        $do_next = 1;
    } else {
        print $this_line if $do_next;
        $last_line = "";
        $do_next = 0;
    }
}
close ($fh);

См. Почему открытые вызовы с тремя аргументами с лексическими файловыми дескрипторами являются лучшей практикой Perl? для обсуждения причин наиболее важных изменений.

Важные изменения:

  • 3 аргумента open.
  • лексическая файловая ручка
  • добавлено strict и warnings прагм.
  • переменные объявлены с лексической областью действия.

Незначительные изменения (проблемы стиля и личного вкуса):

  • удалены ненужные парены из пост-исправления if
  • преобразовал контракт if-not в unless.

Если этот ответ окажется полезным, обязательно проголосуйте за оригинал Пакса.

5 голосов
/ 06 октября 2009

Учитывая следующий входной файл:

(1:first) Yes, this one.
(2) This one as well (XXX).
(3) And this one.
Not this one.
Not this one.
Not this one.
(4) Yes, this one.
(5) This one as well (XXX).
(6) AND this one as well (XXX).
(7:last) And this one.
Not this one.

этот маленький фрагмент:

open(FH, "<qq.in");
$this_line = "";
$do_next = 0;
while(<FH>) {
    $last_line = $this_line;
    $this_line = $_;
    if ($this_line =~ /XXX/) {
        print $last_line if (!$do_next);
        print $this_line;
        $do_next = 1;
    } else {
        print $this_line if ($do_next);
        $last_line = "";
        $do_next = 0;
    }
}
close (FH);

выдает следующее, о чем я думаю, что вы после:

(1:first) Yes, this one.
(2) This one as well (XXX).
(3) And this one.
(4) Yes, this one.
(5) This one as well (XXX).
(6) AND this one as well (XXX).
(7:last) And this one.

Он в основном работает, запоминая последнюю прочитанную строку и, когда находит шаблон, выводит его и строку шаблона. Затем он продолжает выводить линии образца плюс еще одну (с переменной $do_next).

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

5 голосов
/ 06 октября 2009

Вы всегда хотите сохранить последнюю строку, которую вы видели, в случае, если на следующей строке есть ваш шаблон, и вам нужно напечатать его. Использование массива, как вы сделали во втором фрагменте кода, вероятно, излишне.

my $last = "";
while (my $line = <FH>) {
  if ($line =~ /Pattern/) {
    print $last;
    print $line;
    print scalar <FH>;  # next line
  }
  $last = $line;
}
4 голосов
/ 06 октября 2009
grep -A 1 -B 1 "search line"
3 голосов
/ 06 октября 2009

Я собираюсь игнорировать заголовок вашего вопроса и сосредоточиться на части кода, который вы разместили, потому что это положительно вредно, если оставить этот код без объяснения, что с ним не так. Вы говорите:

код, который может печатать совпадающие строки со строками непосредственно над ними. Код, который частично соответствует моей цели, выглядит примерно так:

Я собираюсь пройти через этот код. Во-первых, вы всегда должны включать

use strict;
use warnings;

в ваших сценариях, тем более что вы только изучаете Perl.

@array;

Это бессмысленное утверждение. С помощью strict вы можете объявить @array, используя:

my @array;

Предпочитайте форму с тремя аргументами open, если в конкретной ситуации нет особой выгоды от ее неиспользования. Используйте лексические файловые дескрипторы, потому что файловые дескрипторы голых слов являются глобальными пакетами и могут быть источником загадочных ошибок. Наконец, всегда проверяйте, удалось ли open, прежде чем продолжить. Итак, вместо:

open(FH, "FILE");

запись:

my $filename = 'something';
open my $fh, '<', $filename
    or die "Cannot open '$filename': $!";

Если вы используете autodie , вы можете сойти с:

open my $fh, '<', 'something';

Двигаемся дальше:

while ( <FH> ) {
  chomp;
  $my_line = "$_";

Сначала прочитайте FAQ (это нужно было сделать перед тем, как начать писать программы). См. Что плохого в том, чтобы всегда цитировать "$ vars"? . Во-вторых, если вы собираетесь присвоить строку, которую вы только что прочитали, $my_line, вы должны сделать это в операторе while, чтобы не нажимать на $_. Наконец, вы можете быть strict совместимым, не вводя больше символов:

while ( my $line =  <$fh> ) {
    chomp $line;

Снова обратитесь к предыдущему FAQ.

  if ("$my_line" =~ /Pattern/) {

Зачем интерполировать $my_line еще раз?

      foreach( @array ){
          print "$_\n";
      }

Либо используйте явную переменную цикла, либо превратите ее в:

print "$_\n" for @array;

Итак, вы интерполируете $my_line снова и добавляете новую строку, которая была удалена chomp ранее. Нет причин делать это:

      print "$my_line\n"

А теперь мы подошли к линии, которая побудила меня разбирать код, который вы в первую очередь разместили:

  if ( "$#array" > "0" ) {

$#array - это число . 0 - это число . > используется для проверки, больше ли число на LHS, чем число на RHS. Следовательно, нет необходимости преобразовывать оба операнда в строки.

Далее, $#array является последним индексом @array, и его значение зависит от значения $[. Я не могу понять, что это утверждение должно проверять.

Итак, ваша первоначальная постановка задачи была

печатать совпадающие строки с линиями непосредственно над ними

Естественный вопрос, конечно, состоит в том, сколько строк «непосредственно над» соответствием вы хотите напечатать.

#!/usr/bin/perl

use strict;
use warnings;

use Readonly;
Readonly::Scalar my $KEEP_BEFORE => 4;

my $filename = $ARGV[0];
my $pattern  = qr/$ARGV[1]/;

open my $input_fh, '<', $filename
    or die "Cannot open '$filename': $!";

my @before;

while ( my $line = <$input_fh> ) {
    $line = sprintf '%6d: %s', $., $line;
    print @before, $line, "\n" if $line =~ $pattern;
    push @before, $line;
    shift @before if @before > $KEEP_BEFORE;
}

close $input_fh;
2 голосов
/ 06 октября 2009

Командная строка grep - самый быстрый способ сделать это, но если ваша цель - изучить Perl, вам нужно будет создать некоторый код.

Вместо того, чтобы предоставлять код, как уже сделали другие, я немного расскажу о том, как написать свой собственный. Я надеюсь, что это помогает с блокировкой мозга.

  • Прочтите мой предыдущий ответ о том, как написать программу , он дает несколько советов о том, как начать работать над вашей проблемой.
  • Просмотрите все примеры программ, которые у вас есть, а также предложенные здесь, и прокомментируйте, что именно они делают. Обратитесь к perldoc для каждой функции и оператора, которые вы не понимаете. Ваш первый пример кода содержит ошибку, если две строки в строке соответствуют, строка после второго соответствия не будет напечатана. Под ошибкой я подразумеваю, что либо код, либо спецификация неверны, нужно определить желаемое поведение в этом случае.
  • Напишите, что вы хотите, чтобы ваша программа делала.
  • Начните заполнять поля с кодом.

Вот эскиз первого этапа написания:

# This program reads a file and looks for lines that match a pattern.

# Open the file

# Iterate over the file
# For each line
#    Check for a match
#    If match print line before, line and next line.

Но как получить следующую и предыдущую строки?

Вот тут и приходит творческое мышление, есть много способов, все, что вам нужно, это тот, который работает.

  • Вы можете читать по одной строке за раз, но читать вперед на одну строку.
  • Вы можете прочитать весь файл в память и выбрать предыдущие и последующие строки, проиндексировав массив.
  • Вы можете прочитать файл и сохранить смещение и длину каждой строки, отслеживая, какие из них совпадают по мере продвижения. Затем используйте данные смещения, чтобы извлечь необходимые строки.
  • Вы можете читать по одной строке за раз. Кэшируйте свою предыдущую строку, как вы идете. Используйте readline, чтобы прочитать следующую строку для печати, но используйте search и tell, чтобы перемотать ручку, чтобы можно было проверить «следующую» строку на соответствие.

Любой из этих методов и многие другие могут быть воплощены в работающую программу. В зависимости от ваших целей и ограничений любой из них может быть лучшим выбором для этой проблемной области. Знание, как выбрать, какой использовать, придет с опытом. Если у вас есть время, попробуйте два или три разных способа и посмотрите, как они работают.

Удачи.

1 голос
/ 06 октября 2009

Если вы не против потерять возможность перебора файлового дескриптора, вы можете просто скопировать файл и выполнить перебор массива:

#!/usr/bin/perl

use strict; # always do these
use warnings;

my $range = 1; # change this to print the first and last X lines

open my $fh, '<', 'FILE' or die "Error: $!";
my @file = <$fh>;
close $fh;

for (0 .. $#file) {
  if($file[$_] =~ /Pattern/) {
    my @lines = grep { $_ > 0 && $_ < $#file } $_ - $range .. $_ + $range;
    print @file[@lines];
  }
}

Это может быть ужасно медленно для больших файлов, но довольно легко понять (по моему мнению). Только когда вы знаете, как это работает, вы можете приступить к его оптимизации. Если у вас есть какие-либо вопросы по поводу любых функций или операций, которые я использовал, просто спросите.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...