как можно объединить серию операторов grep, собранных вместе, в один оператор grep?

Я хотел бы знать, есть ли способ объединить серию операторов grep, где эффект заключается в выражении «и», а не в «или» соответствующих выражениях.

Демонстрация ниже:

./script  
     From one grep statement, I want output like this
a b c

     not like this
a
c
a b
a b c
a b c d

Слушайте, посмотрите на сценарий.

 #!/bin/bash
 string="a
 b
 c
 d
 a b
 a b c
 a b c d"

 echo -e "\t From one grep statement I want output like this"
 echo "$string" |
 grep a |grep c |grep -v d #Correct output but pipes three grep statements

 echo -e "\n\tNot like this"
 echo "$string" |
 grep -e'a' -e'c' -e-v'd' #One grep statement but matching expressions are "or" versus "and"
6
задан 26 August 2015 в 20:49

3 ответа

Вы не можете преобразовать фильтр grep a | grep c | grep -v d к простому синглу grep. Там являются только сложными и неэффективные пути. Результат имеет медленную производительность, и значение выражения затенено.

Единственная комбинация команды трех властей

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

awk '/a/ && /c/ && $0 !~ /d/'

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

Подобные Unix системы разработаны, чтобы использовать каналы и соединить различные утилиты вместе. Хотя коммуникация канала не является самой эффективной, но в большинстве случаев это достаточно. Поскольку в наше время большинство новых компьютеров имеет несколько ядер процессора, можно "естественно" использовать распараллеливание ЦП только при помощи канала!

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

Сравнение производительности

Используя простую программу я генерировал случайный файл тестирования с 200 000 000 строк, каждого с 4 символами как случайная комбинация от символов a, b, c и d. Файл имеет 1 ГБ. Во время тестов это было полностью загружено в кэше, таким образом, никакие дисковые операции не влияли на измерение производительности. Тесты были запущены на двухъядерном Intel.

Единственный grep

$ time ( grep -E '^[^d]*a[^d]*c[^d]*$|^[^d]*c[^d]*a[^d]*$' testfile >/dev/null )
real    3m2.752s
user    3m2.411s
sys 0m0.252s

Единственный awk

$ time ( awk '/a/ && /c/ && $0 !~ /d/' testfile >/dev/null )
real    0m54.088s
user    0m53.755s
sys 0m0.304s

Исходные три власти передаются по каналу

$ time ( grep a testfile | grep c | grep -v d >/dev/null )
real    0m28.794s
user    0m52.715s
sys 0m1.072s

Гибрид - положительные власти, объединенные, отрицательные переданный по каналу

$ time ( grep -E 'a.*c|c.*a' testfile | grep -v d >/dev/null )
real    0m15.838s
user    0m24.998s
sys 0m0.676s

Здесь Вы видите что сингл grep является очень медленным из-за сложного выражения. Исходный канал трех властей довольно быстр из-за хорошего распараллеливания. Без распараллеливания - на одноядерном - исходный канал работает просто немного быстрее, чем awk который как единственный процесс не параллелизируется. Awk и grep, вероятно, используют тот же код регулярных выражений, и логика этих двух решений подобна.

Явный победитель является hybring объединение двух положительных властей и отъезд отрицательного в канале. Кажется что регулярное выражение с | не имеет никакой потери производительности.

6
ответ дан 26 August 2015 в 20:49

Вы можете использовать переключатель -x, который согласно справочной странице grep «выбирает только те совпадения, которые точно соответствуют всей строке».

В вашем примере, попробуйте: grep -x "a b c"

0
ответ дан 26 August 2015 в 20:49

Проблема в том, что -e работает как or, а не как and. Вы можете сделать это в одну строку, но это довольно запутанно. Не часть является самой сложной.

Чтобы упростить части a и c (при условии, что порядок неизвестен):

grep -E 'a.*c|c.*a'

или

grep -e 'a.*c' -e 'c.*a'

Следовательно, вы можете сделать

grep -E 'a.*c|c.*a' | grep -v 'd'

Для одного оператора grep вы должны убедиться, что нет d с до, после или между a и c:

grep -E '^[^d]*a[^d]*c[^d]*$|^[^d]*c[^d]*a[^d]*
0
ответ дан 26 August 2015 в 20:49

Другие вопросы по тегам:

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