сравните файл B с A и извлеките данные из A с помощью awk, sed или grep

Я думаю, вам может потребоваться указать свое неквалифицированное доменное имя (имя хоста) в / etc / hostname и ваше полное доменное имя (hostname.com) в качестве первой записи после 127.0.0.1 в / etc / hosts . Я не уверен, что такое IP-адрес 1.1.1.1, но я думаю, что эту строку следует удалить. Этот вопрос askbuntu может помочь прояснить ситуацию, хотя я до сих пор не уверен, как вы получили эти результаты.

3
23.02.2017, 09:32
7 ответов

Я уверен, что получу удар по голове за использование петли, но все же ... вот один способ сделать это.

#!/bin/bash

while read -r line; do
        sed -n "/$line/,/^C/p" fileA | sed '$d'
        done < fileB

Пример:

./bacteria.sh 
C    02030 *Bacterial chemotaxis* [PATH:aap02030]
D      NT05HA_0919 maltose-binding periplasmic protein
D      NT05HA_0918 maltose-binding periplasmic protein 
C    03070 *Bacterial secretion system* [PATH:aap03070]
D      NT05HA_1309 protein-export membrane protein SecD 
D      NT05HA_1310 protein-export membrane protein SecF 
D      NT05HA_1819 preprotein translocase subunit SecE
D      NT05HA_1287 protein-export membrane protein  

Где fileA и fileB - ваши файлы примеров.

Структура регулярных выражений:

sed -n "/$line/,/^C/p" fileA | sed '$d'

Вывести строки между $ line и следующей строкой, начинающейся с буквы C , но исключить ( sed '$ d' ) последняя строка, поскольку она используется просто как «маркер остановки».


sed --version
sed (GNU sed) 4.2.2

bash --version
GNU bash, version 4.2.46(1)-release (x86_64-redhat-linux-gnu)
3
27.01.2020, 21:07

Общее решение, которое охватывает все размеры ваших файлов: https://www.gnu.org/software/parallel/man.html#EXAMPLE:-Grepping- n-строк-для-m-регулярных-выражений

ПРИМЕР: выделение n строк для m регулярных выражений.

Самым простым решением для большого количества регулярных выражений с помощью grep является:

grep -f regexps.txt bigfile

Или, если регулярные выражения представляют собой фиксированные строки:

grep -F -f regexps.txt bigfile

Есть 3 ограничивающих фактора: ЦП, ОЗУ и дисковый ввод-вывод.

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

CPU также легко измерить: если grep занимает> 90% CPU в верхней части, то CPU является ограничивающим фактором, и распараллеливание ускорит это.

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

Ограничивающий фактор: RAM

Обычный bigfile grep -f regexs.txt работает независимо от размера bigfile, но если regexps.txt настолько велик, что не помещается в памяти, то вам необходимо разделить его.

grep -F занимает около 100 байт ОЗУ, а grep занимает около 500 байт ОЗУ на 1 байт регулярного выражения. Поэтому, если regexps.txt занимает 1% вашей оперативной памяти, он может быть слишком большим.

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

ID1 foo bar baz Identifier1 quux
fubar ID2 foo bar baz Identifier2

, тогда ваш regexps.txt может быть преобразован из:

ID1.*Identifier1
ID2.*Identifier2

в:

ID1 foo bar baz Identifier1
ID2 foo bar baz Identifier2

Таким образом вы можете использовать grep -F, который занимает примерно на 80% меньше памяти и намного быстрее.

Если он по-прежнему не помещается в памяти, вы можете сделать следующее:

parallel --pipepart -a regexps.txt --block 1M grep -F -f - -n bigfile |
sort -un | perl -pe 's/^\d+://'

1M должен быть вашей свободной памятью, разделенной на количество ядер и разделенной на 200 для grep -F и на 1000 для обычного grep. В GNU / Linux вы можете:

free=$(awk '/^((Swap)?Cached|MemFree|Buffers):/ { sum += $2 }
          END { print sum }' /proc/meminfo)
percpu=$((free / 200 / $(parallel --number-of-cores)))k

parallel --pipepart -a regexps.txt --block $percpu --compress grep -F -f - -n bigfile |
sort -un | perl -pe 's/^\d+://'

Если вы можете жить с дублированными строками и неправильным порядком, это быстрее сделать:

parallel --pipepart -a regexps.txt --block $percpu --compress grep -F -f - bigfile

Ограничивающий фактор: CPU

Если CPU является ограничивающим фактором, распараллеливание должно выполняться на regexps:

cat regexp.txt | parallel --pipe -L1000 --round-robin --compress grep -f - -n bigfile |
sort -un | perl -pe 's/^\d+://'

Команда запускает одну команду grep для каждого процессора и читает большой файл один раз для каждого процессора, но поскольку это выполняется параллельно, все операции чтения, кроме первого, будут кэшироваться в ОЗУ. В зависимости от размера regexp.txt может быть быстрее использовать --block 10m вместо -L1000.

Некоторые системы хранения работают лучше при параллельном чтении нескольких фрагментов.Это верно для некоторых систем RAID и некоторых сетевых файловых систем. Чтобы распараллелить чтение большого файла:

parallel --pipepart --block 100M -a bigfile -k --compress grep -f regexp.txt

Это разделит большой файл на блоки по 100 МБ и запустит команду grep для каждого из этих блоков. Чтобы распараллелить чтение bigfile и regexp.txt, объедините их с помощью --fifo:

parallel --pipepart --block 100M -a bigfile --fifo cat regexp.txt \
\| parallel --pipe -L1000 --round-robin grep -f - {}

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

Большая проблема

Если проблема слишком велика, чтобы ее решить, вы, вероятно, готовы к Lucene.

2
27.01.2020, 21:07

Если вы хотите точно сопоставить часть, которая находится между C и [ПУТЬ :. ..] (и если предположить, что те * в вашем примере предназначены только для акцента, а не являются частью фактических данных), вы можете сделать:

awk '
  !start {all_strings[$0]; next}
  /^C/ {
    key = $0

    # strip the leading C <word>:
    sub(/^C[[:blank:]]+[^[:blank:]]+[[:blank:]]*/, "", key)

    # strip the trailing [...]:
    sub(/[[:blank:]]*\[[^]]*][[:blank:]]*$/, "", key)
    selected = key in all_strings
  }
  selected' fileB start=1 fileA

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

4
27.01.2020, 21:07

Данные в fileA разделены на записи, которые начинаются с C в новой строке. Каждая запись разделена на целые поля, которые начинаются с D в новой строке.

Нам нужно прочитать строки из fileB и использовать их для запроса первого поля каждой записи в fileA :

while read -r query; do
    awk -vq="$query" 'BEGIN { RS="^C|\nC"; FS=OFS="\nD" } $1 ~ q {print "C" $0}' fileA
done <fileB

Я устанавливаю разделитель записей ( RS ) для сопоставления либо C в начале строки , либо после новой строки, либо мы не сможем правильно сопоставить что-либо в первой записи. Я использую переменную awk , q , чтобы удерживать значение, прочитанное из файла, и сравниваю первое поле каждой записи с этим значением.

Результат:

C    02030 *Bacterial chemotaxis* [PATH:aap02030]
D      NT05HA_0919 maltose-binding periplasmic protein
D      NT05HA_0918 maltose-binding periplasmic protein
C    03070 *Bacterial secretion system* [PATH:aap03070]
D      NT05HA_1309 protein-export membrane protein SecD
D      NT05HA_1310 protein-export membrane protein SecF
D      NT05HA_1819 preprotein translocase subunit SecE
D      NT05HA_1287 protein-export membrane protein
3
27.01.2020, 21:07

Вы можете использовать awk :

awk 'NR==FNR{         # On the first file,
       a[$0];         # store the content in the array a
       next
     } 
     {                        # On the second file, 
         for(i in a)          # for all element in the array a,
            if(index($0,i)) { # check if there is match in the current record
               print "C" $0   # in that case print it with the record separator
               next
            }
     }' fileB RS='\nC' fileA
C    02030 *Bacterial chemotaxis* [PATH:aap02030]
D      NT05HA_0919 maltose-binding periplasmic protein
D      NT05HA_0918 maltose-binding periplasmic protein 
C    03070 *Bacterial secretion system* [PATH:aap03070]
D      NT05HA_1309 protein-export membrane protein SecD 
D      NT05HA_1310 protein-export membrane protein SecF 
D      NT05HA_1819 preprotein translocase subunit SecE
D      NT05HA_1287 protein-export membrane protein  
5
27.01.2020, 21:07

с grep и sed в одной строке:

for i in $(grep -f fileb filea | awk '{print $2}'); do sed -e 's/^C/\nC/g' filea | sed -n "/$i/,/^ *$/p" | grep -v "^$"; done

То есть:

Возьмите шаблоны для поиска с помощью:

grep -f fileb filea | awk '{print $2}'

Вставьте пустую строку перед каждой строкой, начинающейся с C:

sed -e 's/^C/\nC/g' filea

Взять из шаблона в пустая строка:

sed -n "/$i/,/^ *$/p"

Удалите пустые строки, чтобы получить желаемый результат:

grep -v "^$"

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

3
27.01.2020, 21:07

Bash использует только встроенные команды для генерации вывода. Логика кода довольно проста и не требует пояснений.

Bash

while IFS= read -r line; do
   IFS=$'\n'; for pat in $(< fileB); do
      case $line in
         [C]*"$pat"* )
            echo "$line"
            unset flag
            break
            ;;

         [D]* )
            ${flag+":"} echo "$line"
            ${flag+":"} break
            ;;

         * ) flag=;;
      esac
   done
done < fileA

С GNU sed мы загружаем шаблоны в область хранения, а затем в данных файла B, мы сравниваем их, чтобы определить, печатать ли блоки C-D + или нет, в зависимости от того, было ли найдено совпадение в строке C с использованием шаблоны, хранящиеся на удержании. Файл B не должен начинаться с C или D.

Sed

sed -e '
   /^D/bD

   /^C/{
      x;G
      /\n\(.*\)\n\(.*\n\)\{0,1\}[^\n]*\1[^\n]*$/{
         s/\(.*\)\n.*/\1/;x
         :loop
            n
         /^C/!bloop
         s/^/\n/;D
      }
      s/\(.*\)\n.*/\1/;x
      :D
      $!N;D
   }

   H;d
' fileB fileA

Perl

perl -l -0777ne '
   push(@A, split $\), next if @ARGV;
   for my $pat ( map { quotemeta } @A ) {
      while ( /^C [^\n]* $pat [^\n]*$/xmg ) {
         my $C = $&;
         print $C .= $& if /\G(\nD.*$)+/xm;
      }
   }
' fileB fileA
1
27.01.2020, 21:07

Теги

Похожие вопросы