2009-10-06 4 views
4

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

#!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 и два приложения, агент Ransack и Devas, оказались полезными, но они отображают только строки, содержащие согласованный запрос, и я хочу также заглянуть в соседние строки. Тогда идея импровизации программы появилась у меня в голове. Несколько лет назад я был впечатлен сценарием Perl, который мог бы создать формат Википедии Tomeraider, чтобы я мог легко искать Wiki на моем Lifedrive, и я также читал где-то в сети, что Perl легко учиться специально для такого парня, как я, который не имеет опыта программирования на любом языке программирования. Тогда я как-то начал преподавать Perl пару дней назад. Мой первый шаг состоял в том, чтобы узнать, как выполнять ту же работу, что и «Агент Ransack», и это оказалось не так сложно с помощью Perl. Сначала я узнал, как искать содержимое одного файла и отображать соответствующие строки с помощью модификации примера, используемого в книге под названием «Perl by Example», но я застрял там. Я стал совершенно невежественным, как иметь дело с несколькими файлами. Подобных примеров не было найдено в книге или, вероятно, потому, что я был слишком нетерпелив. И затем я снова попробовал googling и был приведен сюда, и я спросил свой первый вопрос: «Как я могу найти несколько файлов для шаблона строк в 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"; 

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

Затем я сбросил тело.

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

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

Je sais que tu as un micro.

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

Alors je vais te dire la vérité.

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

+1

Возможно, вы захотите рассмотреть вопрос о создании блога. * «Я думаю, что я как бы тронута». * Ну, да? –

+0

Вы британцы? вы пишете в несколько узнаваемом классическом лирическом стиле. :) – Ether

+2

Изучение на примере a - замечательная вещь. Этот сайт и Perlmonks (http://perlmonks.org) - отличные ресурсы для Perl. Преимущество SO состоит в том, что он охватывает широкий круг тем. Преимущество Perlmonks заключается в том, что Perl сосредоточен. Я бы не хотел обойтись без;) – daotoad

ответ

5

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

(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).

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

+0

+1 даже несмотря на то, что я не люблю выходной формат (я не думаю, что у вас должны быть повторы, даже если мой ответ). –

+0

Да, небольшая ошибка, исправлено сейчас :-) – paxdiablo

+4

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

5

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

my $last = ""; 
while (my $line = <FH>) { 
    if ($line =~ /Pattern/) { 
    print $last; 
    print $line; 
    print scalar <FH>; # next line 
    } 
    $last = $line; 
} 
+1

Если шаблон может появляться в последовательных строках, то вы можете сделать это несколько иначе. – mob

+0

Удивительный! Код работает как магия! Благодарю! – Mike

+0

Я согласен с @mobrule, но это можно устранить, просто изменив последние два отпечатка на 'print $ last = $ line; напечатать $ line = ; 'и затем положить' $ last = $ line; 'в блок' else'. –

10

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

+5

Я тоже так думал, но тогда OP ничего не узнает о Perl, кроме, может быть, ** не **, чтобы использовать его для всего. – pavium

+4

+1 для правильного инструмента для работы. В этом случае Perl не является решением _best_, если 'grep (1)' (disambiguate из функции 'grep()') Perl является доступным. Кроме того, подобный (и более мощный (и написанный на Perl)) инструмент будет 'ack (1)', который является удивительной маленькой программой. –

+1

Вопрос, который я опубликовал, является лишь частью нескольких функций, которые я хотел добавить в мое приложение. Я изучаю Perl без опыта работы на других языках. Но я вижу, что grep выглядит великолепно! Я добавил в закладки URL. – Mike

1

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

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

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

+0

Ужасно неэффективно, но довольно легко понять. Для удобства чтения я бы заменил grep чем-то вроде 'my $ start = $ _ - $ range; $ start = 0, если $ start> = 0; 'и' my $ end = $ _ + range; $ end = $ # lines, если не $ end <= $ # lines; ', а затем' print @file [$ start .. $ end]; ' – daotoad

+0

@daotoad - Слишком много функциональных шумих заставило меня думать, что' grep() 'как-то проще/читаем. Я согласен с тем, что вам определенно легче разбираться. –

+0

это все еще немного за меня: (ну, во всяком случае, я в процессе ознакомления себя с самой базой, и поэтому я думал, что мои вопросы лучше зарезервировать для более позднего этапа :) Я очень ценю ваш ответ , – Mike

2

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

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

  • Прочитайте мой previous answer on how to write a program, в нем приведены некоторые советы о том, как начать работу над вашей проблемой.
  • Пройдите через все предлагаемые вами образцы программ, а также предлагаемые здесь и прокомментируйте, что именно они делают. Обратитесь к 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 для чтения следующей строки для печати, но используйте поиск и скажите, чтобы перемотать дескриптор так, чтобы строка «next» была проверена для соответствия.

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

Удачи.

+0

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

7

Вот модернизированный вариант отличного ответа Пакс в:

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

См Why is three-argument open calls with lexical filehandles a Perl best practice? для обсуждения причин наиболее важных изменений.

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

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

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

  • Убраны из круглые скобки после исправить if
  • преобразовал если-не contstruct в unless.

Если вы считаете этот ответ полезным, убедитесь, что вы подтвердили оригинал Pax.

+1

Технически, это два аргумента :-) Но основная причина для 3-arg one действительно не существует здесь, поскольку у вас есть полный контроль над именем файла. Я буду принимать все эти предложения на борту в будущем, строгие и предупреждения, которые я обычно добавляю только тогда, когда мои первоначальные версии не ведут себя :-) Но глобальное предотвращение обращения к файлам является хорошим. Извините, но если они были, они были изначально «if() {}», и я вспомнил постфиксную версию позже при сжатии кода. +1. – paxdiablo

+1

@Pax, я не могу поверить, что пропустил это редактирование! Сейчас действительно 3. Я согласен с тем, что причины этого не применяются. Несмотря на этот факт, я бы все же написал этот код с 3-х символьной формой для согласованности с моим другим кодом и для укрепления хорошей практики. Если бы была хорошая причина использовать две формы arg (не то, что я знаю об одном), я бы использовал ее и оставил комментарий о том, почему. – daotoad

2

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

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

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

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 (вы должны были сделать s o перед началом написания программ). См. What's wrong with always quoting "$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 больше числа на РИТ. Поэтому нет необходимости конвертировать оба операнда в строки.

Далее $#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; 
+0

Большое спасибо за ваш совет и подробное объяснение. Спасибо! – Mike

+1

Я записал ключевые моменты ваших комментариев в своем ноутбуке. Еще раз спасибо! – Mike

+0

@Mike: Добро пожаловать. –

Смежные вопросы