Найти все вхождения строки в файл и вывести номер строки в Perl

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

А также у меня есть файл, содержащий список ключевых слов для сопоставления. Скажем, этот файл действует как поиск.

Поэтому для каждого ключевого слова в таблице поиска мне нужно найти все его вхождения в данном файле. И должен напечатать номер строки вхождения.

я пробовал это

#!usr/bin/perl
use strict;
use warnings;

my $linenum = 0;

print "Enter the file path of lookup table:";
my $filepath1 = <>;

print "Enter the file path that contains keywords :";
my $filepath2 = <>;

open( FILE1, "< $filepath1" );
open FILE2, "< $filepath2" ;

open OUT, ">", "SampleLineNum.txt";

while( $line = <FILE1> )
{
    while( <FILE2> ) 
    {
        $linenum = $., last if(/$line/);
    }
    print OUT "$linenum ";
}

close FILE1;

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

Проблема, с которой я сталкиваюсь при точном совпадении, например, у меня есть ключевые слова «привет» и «привет, мир».

если мне нужно сопоставить «привет», он возвращает номер строки, который содержит «привет, мир», и мой скрипт должен соответствовать только «привет» и указывать номер строки.


person Sishanth    schedule 19.12.2012    source источник
comment
Просто: если эффективность не имеет большого значения, вы можете просто загрузить все ключевые слова из файла2 в массив. Затем переберите файл1 и в каждой строке вы ищете все ключевые слова из массива.   -  person Frank    schedule 19.12.2012
comment
не могли бы вы уточнить последний абзац? Вы хотите, чтобы поиск по приветствию возвращал привет, мир или нет?   -  person Karthik T    schedule 19.12.2012
comment
@KarthikT Если я ищу привет, мир, он должен соответствовать только этому слову, а не его подстроке, например привет.   -  person Sishanth    schedule 20.12.2012


Ответы (6)


Вот решение, которое соответствует каждому вхождению всех ключевых слов:

#!usr/bin/perl
use strict;
use warnings;

#Lexical variable for filehandle is preferred, and always error check opens.
open my $keywords,    '<', 'keywords.txt' or die "Can't open keywords: $!";
open my $search_file, '<', 'search.txt'   or die "Can't open search file: $!";

my $keyword_or = join '|', map {chomp;qr/\Q$_\E/} <$keywords>;
my $regex = qr|\b($keyword_or)\b|;

while (<$search_file>)
{
    while (/$regex/g)
    {
        print "$.: $1\n";
    }
}

ключевые слова.txt:

hello
foo
bar

поиск.txt:

plonk
food is good
this line doesn't match anything
bar bar bar
hello world
lalalala
hello everyone

Вывод:

4: bar
4: bar
4: bar
5: hello
7: hello

Пояснение:

Это создает одно регулярное выражение, которое соответствует всем ключевым словам в файле ключевых слов.

<$keywords> — когда это используется в контексте списка, он возвращает список всех строк файла.

map {chomp;qr/\Q$_\E/} — это удаляет новую строку из каждой строки и применяет оператор регулярного выражения \Q...\E кавычек к каждой строке (это гарантирует, что если у вас есть ключевое слово, такое как «foo.bar», оно будет рассматривать точку как буквальный символ, а не метасимвол регулярного выражения ).

join '|', - объединить полученный список в одну строку, разделенную вертикальной чертой.

my $regex = qr|\b($keyword_or)\b|; — создайте регулярное выражение, которое выглядит так:

/\b(\Qhello\E|\Qfoo\E|\Qbar\E)\b/

Это регулярное выражение будет соответствовать любому из ваших ключевых слов. \b — это маркер границы слова, обеспечивающий соответствие только целым словам: food больше не соответствует foo. Круглые скобки фиксируют конкретное ключевое слово, совпавшее в $1. Вот как на выходе выводится совпадающее ключевое слово.

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

person dan1111    schedule 19.12.2012

Является ли это частью чего-то большего? Потому что это один лайнер с grep

grep -n hello filewithlotsalines.txt

grep -n "hello world" filewithlotsalines.txt

-n получает grep для отображения номеров строк перед соответствующими строками. Вы можете сделать man grep для получения дополнительных опций.

Я предполагаю, что вы работаете в системе Linux или * nix.

person Karthik T    schedule 19.12.2012
comment
Не могли бы вы дать мне больше объяснений? - person Sishanth; 19.12.2012
comment
@Sishanth Вы можете увидеть пример с grep - person Karthik T; 19.12.2012
comment
Это нормально для одного ключевого слова, но OP хотел сопоставить весь список ключевых слов из файла. - person dan1111; 19.12.2012
comment
@KarthikT, достаточно честно. Но как только вы добавите цикл и логику для получения ключевых слов из файла, решение grep не будет короче, чем решение Perl. - person dan1111; 19.12.2012
comment
@dan1111: Неправильно. grep -n -f keywords.txt filewithlotsalines.txt берет ключевые слова из файла для поиска в большом файле. - person mpe; 19.12.2012

У меня другая интерпретация вашего запроса. Кажется, вы можете захотеть сохранить список номеров строк, в которых определенные записи из таблицы поиска находятся в строках файла «ключевых слов». Вот пример таблицы поиска:

hello world
hello
perl
hash
Test
script

И файл «ключевого слова» с разделителями табуляции, где несколько ключевых слов могут быть найдены в одной строке:

programming tests
hello   everyone
hello   hello world perl
scripting   scalar
test    perl    script
hello world perl    script  hash

Учитывая вышеизложенное, рассмотрим следующее решение:

use strict;
use warnings;

my %lookupTable;

print "Enter the file path of lookup table: \n";
chomp( my $lookupTableFile = <> );

print "Enter the file path that contains keywords: \n";
chomp( my $keywordsFile = <> );

open my $ltFH, '<', $lookupTableFile or die $!;

while (<$ltFH>) {
    chomp;
    undef @{ $lookupTable{$_} };
}

close $ltFH;

open my $kfFH, '<', $keywordsFile or die $!;

while (<$kfFH>) {
    chomp;
    for my $keyword ( split /\t+/ ) {
        push @{ $lookupTable{$keyword} }, $. if defined $lookupTable{$keyword};
    }
}

close $kfFH;

open my $slFH, '>', 'SampleLineNum.txt' or die $!;

print $slFH "$_: @{ $lookupTable{$_} }\n"
  for sort { lc $a cmp lc $b } keys %lookupTable;

close $slFH;

print "Done!\n";

Вывод в SampleLineNum.txt:

hash: 6
hello: 2 3
hello world: 3 6
perl: 3 5 6
script: 5 6
Test: 

Сценарий использует хэш массивов (HoA), где ключ — это запись из таблицы поиска, а связанное значение — это ссылка на список номеров строк, в которых эта запись была найдена в строках файла «ключевых слов». Хэш %lookupTable инициализируется ссылкой на пустой список.

Каждая строка файла «ключевых слов» обозначается split на вкладке-разделителе, и если соответствующая запись определена в %lookupTable, номер строки добавляется push в соответствующий список. После этого ключи %lookupTable сортируются без учета регистра и записываются в SampleLineNum.txt вместе с соответствующим им списком номеров строк, в которых была найдена запись, если таковая имеется.

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

Надеюсь это поможет!

person Kenosis    schedule 19.12.2012

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

#!usr/bin/perl
use strict;
use warnings;

my $linenum = 0;

print "Enter the file path of lookup table:";
my $filepath1 = <>;

print "Enter the file path that contains keywords :";
my $filepath2 = <>;

open( FILE1, "< $filepath1" );
open FILE2, "< $filepath2" ;

# Read in all of the keywords
my @keywords = <FILE2>; 

# Close the file2
close(FILE2);

# Remove the line returns from the keywords
chomp @keywords;

# Sort and reverse the items to compare the maximum length items
# first (hello there before hello)
@keywords = reverse sort @keywords;

foreach my $k ( @keywords)
{
  print "$k\n";
}
open OUT, ">", "SampleLineNum.txt";
my $line;
# Counter for the lines in the file
my $count = 0;
while( $line = <FILE1> )
{
    # Increment the counter for the number of lines
    $count++;
    # loop through the keywords to find matches
    foreach my $k ( @keywords ) 
    {
        # If there is a match, print out the line number 
        # and use last to exit the loop and go to the 
        # next line
        if ( $line =~ m/$k/ ) 
        {
            print "$count\n";
            last;
        }
    }
}

close FILE1;
person Glenn    schedule 19.12.2012

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

Интересен модуль File::Grep.

person Carlisle18    schedule 19.12.2012

поскольку другие уже дали какое-то решение на Perl, я предлагаю вам, возможно, вы могли бы использовать здесь awk.

> cat temp
abc
bac
xyz

> cat temp2
abc     jbfwerf kfnm
jfjkwebfkjwe    bac     xyz
ndwjkfn abc kenmfkwe    bac     xyz

> awk 'FNR==NR{a[$1];next}{for(i=1;i<=NF;i++)if($i in a)print $i,FNR}' temp temp2
abc 1
bac 2
xyz 2
abc 3
bac 3
xyz 3
>
person Vijay    schedule 19.12.2012