Unix объединяет два файла с регулярными выражениями с помощью awk

#regex #bash #unix #awk

#регулярное выражение #bash #unix #awk

Вопрос:

У меня есть один файл (lookup.txt ), который содержит таблицу поиска, состоящую из списка регулярных выражений с соответствующими данными (категориями и периодами). например

 INTERNODE|household/bills/broadband|monthly
ORIGIN ENERGY|household/bills/electricity|quarterly
TELSTRA.*BILL|household/bills/phone|quarterly
OPTUS|household/bills/mobile|quarterly
SKYPE|household/bills/skype|non-periodic
  

У меня есть другой файл (data.txt ), который содержит список расходов, например:

 2009-10-31,cc,-39.9,INTERNODE BROADBAND
2009-10-31,cc,-50,ORIGIN ENERGY 543546
2009-10-31,cc,-68,INTERNODE BROADBAND EXCESS CHARGES
2009-10-31,cc,-90,TELSTRA MOBILE BILL
2009-11-02,cc,-320,TELSTRA HOME BILL
2009-11-03,cc,-22.96,DICK SMITH
2009-11-03,cc,-251.24,BUNNINGS
2009-11-04,cc,-4.2,7-ELEVEN
  

Я хочу объединить эти два вместе, в результате чего 4-й столбец в data.txt файл соответствует регулярному выражению из первого столбца lookup.txt файл.

Таким образом, результат будет:

 2009-10-31,cc,-39.9,INTERNODE BROADBAND,household/bills/broadband,monthly
2009-10-31,cc,-50,ORIGIN ENERGY 543546,household/bills/electricity,quarterly
2009-10-31,cc,-68,INTERNODE BROADBAND EXCESS CHARGES,household/bills/broadband,monthly
2009-10-31,cc,-90,TELSTRA MOBILE BILL,household/bills/phone,quarterly
2009-11-02,cc,-320,TELSTRA HOME BILL,household/bills/phone,quarterly
2009-11-03,cc,-22.96,DICK SMITH
2009-11-03,cc,-251.24,BUNNINGS
2009-11-04,cc,-4.2,7-ELEVEN
  

Я достиг этого, используя цикл bash, зацикливаясь на поиске, выполняя greps и добавляя дополнительные столбцы при использовании sed, но это очень медленно. Поэтому было интересно, есть ли более быстрый способ сделать это, скажем, с использованием awk.

Буду признателен за любую помощь.

Ответ №1:

 $ awk -F'|' 'FNR==NR{a[$1]=$2","$3;next}{m=split($0,b,",");for(i in a){if(b[4]~i){print $0","a[i];next}}}1' lookup file
2009-10-31,cc,-39.9,INTERNODE BROADBAND,household/bills/broadband,monthly
2009-10-31,cc,-50,ORIGIN ENERGY 543546,household/bills/electricity,quarterly
2009-10-31,cc,-68,INTERNODE BROADBAND EXCESS CHARGES,household/bills/broadband,monthly
2009-10-31,cc,-90,TELSTRA MOBILE BILL,household/bills/phone,quarterly
2009-11-02,cc,-320,TELSTRA HOME BILL,household/bills/phone,quarterly
2009-11-03,cc,-22.96,DICK SMITH
2009-11-03,cc,-251.24,BUNNINGS
2009-11-04,cc,-4.2,7-ELEVEN
  

Комментарии:

1. Идеальный. Именно то, что я искал. Невероятно быстро по сравнению с прямым циклом bash.

2. Мне предстоит выполнить аналогичную задачу, поэтому я был бы признателен, если бы вы могли немного объяснить эту строку awk.

Ответ №2:

Вы можете сделать это на Python:

 #!/usr/bin/python
import csv, re
lookup = []
with open('lookup.txt') as f:
    for rec in csv.reader(f, delimiter='|'):
        lookup.append((re.compile(rec[0]), rec[1:]))
with open('data.txt') as f:
    for rec in csv.reader(f, delimiter=','):
        for rexp, fields in lookup:
            if rexp.match(rec[3]):
                rec.extend(fields)
                break
        print ','.join(rec)
  

Для ваших файлов lookup.txt и data.txt это возвращает следующее менее чем за 0.3с:

 2009-10-31,cc,-39.9,INTERNODE BROADBAND,household/bills/broadband,monthly
2009-10-31,cc,-50,ORIGIN ENERGY 543546,household/bills/electricity,quarterly
2009-10-31,cc,-68,INTERNODE BROADBAND EXCESS CHARGES,household/bills/broadband,monthly
2009-10-31,cc,-90,TELSTRA MOBILE BILL,household/bills/phone,quarterly
2009-11-02,cc,-320,TELSTRA HOME BILL,household/bills/phone,quarterly
2009-11-03,cc,-22.96,DICK SMITH
2009-11-03,cc,-251.24,BUNNINGS
2009-11-04,cc,-4.2,7-ELEVEN
  

Ответ №3:

Если у вас не было регулярных выражений, вы могли бы использовать join . Сколько регулярных выражений имеет lookup.txt ? Если это только один файл, просто разверните его и удалите эту функцию.

Комментарии:

1. Я согласен, мне действительно не нужны регулярные выражения, но я должен предусмотреть случаи, когда текст в lookup.txt является частью текста в data.txt . Например, если поисковый запрос содержит «TELSTRA», а данные содержат «TELSTRA MOBILE», они должны совпадать. Таким образом, не используя регулярные выражения, а частичное буквальное совпадение. Работает ли unix join с частичными совпадениями столбцов?

2. Я посмотрел на это. По-видимому, нет. Но я предлагаю поместить две записи в lookup.txt . Один для «TELSTRA MOBILE» и один для «TELSTRA HOME». Если у вас всего около дюжины записей, это может быть проще, чем в принципе изобретать заново join .

Ответ №4:

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

Если вы намерены делать все это в awk, напишите один скрипт для генерации второго awk-скрипта из вашего файла поиска, который обрабатывает данные, затем запустите второй скрипт.

Комментарии:

1. Спасибо за ваш ответ. Знаете ли вы какие-либо хорошие ссылки, где вы видели, как это делалось раньше? т. Е. использование одного awk-скрипта для написания второго awk-скрипта?

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

Ответ №5:

Вы можете сделать это на Perl. Преимущество Perl (или Python) в том, что у них есть библиотеки для работы с файлами CSV. Ваши примеры достаточно просты, но что произойдет, если у вас есть запятая внутри двойных кавычек? Или как насчет utf8? и т.д.

Стандартной библиотекой Perl для этого является Text: CSV_XS. Однако это немного многословно, и я предпочитаю Parse::CSV, который является оболочкой вокруг Text::CSV_XS .

 #!/usr/bin/perl

use strict;
use warnings;
use Parse::CSV;

my %lookup;
my $l = Parse::CSV->new(file => "lookup.txt", sep_char => '|');
while (my $row = $l->fetch) {
   my $key = qr/$row->[0]/;
   $lookup{$key} = [$row->[1,]];
}

my $d = Parse::CSV->new(file => "data.txt");
while (my $row = $d->fetch) {
   foreach my $regex (keys %lookup) {
      if ($row->[3] =~ $regex) {
         push @$row, @{$lookup{$regex}};
         last;
      }
   }
   print join(",", @$row), "n";
}