Я хотел бы знать, есть ли способ объединить серию операторов 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"
Вы не можете преобразовать фильтр 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 объединение двух положительных властей и отъезд отрицательного в канале. Кажется что регулярное выражение с |
не имеет никакой потери производительности.
Вы можете использовать переключатель -x
, который согласно справочной странице grep
«выбирает только те совпадения, которые точно соответствуют всей строке».
В вашем примере, попробуйте: grep -x "a b c"
Проблема в том, что -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]*