Сопоставьте две строки в одной строке с grep


218

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

grep 'string1\|string2' filename

Так как же сопоставить grepтолько строки, содержащие обе строки ?


Ответы:


189

Ты можешь использовать grep 'string1' filename | grep 'string2'

Или, grep 'string1.*string2\|string2.*string1' filename


5
@AlexanderN действительно, я не могу заставить его работать с мультилинией, это так странно, что это было принято ..
Водолей Сила

1
Это был не многострочный вопрос. Если бы он был многострочным, grep -P поддерживает регулярное выражение в стиле Perl ...
Скотт Прайв

20
Работает только тогда, когда оба 'string1' И 'string2' находятся на одной строке. Если вы хотите найти строки со строкой 'string1' или 'string2', см. Ответ пользователя user45949.
lifeson106

10
первый вариант: передача одного grep в секунду НЕ приводит к результату ИЛИ, а к результату И.
Масукоми

1
Я использовалgrep -e "string1" -e "string2"
Рави Дхория ツ

198

Я думаю, это то, что вы искали:

grep -E "string1|string2" filename

Я думаю, что ответы таковы:

grep 'string1.*string2\|string2.*string1' filename

соответствует только случаю, когда присутствуют оба, а не один или другой или оба.


14
не grep -e "string1" -e "string2" filenameсделал бы то же самое?
Janosdivenyi

25
это как grep для строки1 ИЛИ строки2. в вопросе четко говорится, что они ищут строку1 И строку2.
orion elenzil

9
Уверен, что вопрос довольно точный:How do I match lines that contains *both* strings?
r0estir0bbe

Может ли он печатать с той же строкой?
凡 凡

1
Почему этот ответ все еще здесь? Это НЕ ответ на вопрос.
Прометей

26

Для поиска файлов, содержащих все слова в любом порядке в любом месте:

grep -ril \'action\' | xargs grep -il \'model\' | xargs grep -il \'view_type\'

Первый grep запускает рекурсивный поиск ( r), игнорируя case ( i) и перечисляя (распечатывая) имена файлов, которые соответствуют ( l) для одного термина ( 'action'с одинарными кавычками), встречающегося в любом месте файла.

Последующие greps ищут другие термины, сохраняя нечувствительность к регистру и выводя соответствующие файлы.

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


2
Согласовано! Я только отмечу, что я должен был дать xargs «-d '\ n'» для обработки имен файлов с пробелами. Это сработало для меня в Linux: grep -ril 'foo' | xargs -d '\n' grep -il 'bar'
Томми Харрис

16

Если у вас есть grepс -Pвозможностью для ограниченного perlрегулярного выражения, вы можете использовать

grep -P '(?=.*string1)(?=.*string2)'

который имеет преимущество работы с перекрывающимися строками. Использование perlas несколько проще grep, потому что вы можете указать логику и непосредственно:

perl -ne 'print if /string1/ && /string2/'

1
Лучший ответ. Оболочка очень проста и быстра, но как только шаблон становится сложным, вы должны использовать Python или Perl (или Awk). Не бейте головой о стену, пытаясь доказать, что это можно сделать в чистом виде (что бы это ни значило в наши дни). Напомним, что эти инструменты могут быть использованы в синтаксисе "один лайнер", который встроен в существующий сценарий оболочки.
Скотт Прайв

12

Ваш метод был почти хорош, пропустил только -w

grep -w 'string1\|string2' filename

1
По крайней мере, на OS-X и FreeBSD это работает! Я предполагаю, что вы занимаетесь чем-то другим (что ОП не определило - надеюсь, вы не поняли правильный ответ для многих пользователей, кроме вас).
Лев

Я на OS-X. Возможно, я не делаю это правильно? Посмотрите, что я сделал: i.imgur.com/PFVlVAG.png
Ариэль

1
Странный. Я ожидал, что разница не в том, чтобы перейти к файлу, но, если я передам свой метод с вашим ls, я получу результат, которого вы не сделаете: imgur.com/8eTt3Ak.png - Оба в обеих ОС OS-X 10.9.5 ( «grep (BSD grep) 2.5.1-FreeBSD») и FreeBSD 10 («grep (GNU grep) 2.5.1-FreeBSD»). Мне любопытно, кто ты grep -V.
Лев

1
Ваши примеры работают для меня: i.imgur.com/K8LM69O.png Так что разница в том, что этот метод не подбирает подстроки, они должны быть полными строками сами по себе. Я думаю, вам нужно будет создать регулярные выражения в grep для поиска подстрок. Как то так:grep -w 'regexp1\|regexp2' filename
Ариэль

2
OP показывает пример путем сопоставления строки1 или строки2 и спрашивает, как сопоставить строки, содержащие обе строки. Этот пример все еще дает ИЛИ.
gustafbstrom

7

|Оператор в регулярном выражении означает или. То есть будут совпадать либо string1, либо string2. Вы могли бы сделать:

grep 'string1' filename | grep 'string2'

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


1
Ваши утверждения верны, но не отвечают на вопрос ОП
Бен Уилер,

Это действительно отвечает на вопрос, и это действительно, как большинство людей пишут это.
Питер К

7

Вы можете попробовать что-то вроде этого:

(pattern1.*pattern2|pattern2.*pattern1)

4

И как люди предложили Perl и Python, а также сложные сценарии оболочки, вот простой подход awk :

awk '/string1/ && /string2/' filename

Посмотрев на комментарии к принятому ответу: нет, это не делает многострочным; но это также не то, о чем просил автор вопроса.


3

Не пытайтесь использовать grep для этого, используйте вместо этого awk. Чтобы сопоставить 2 регулярных выражения R1 и R2 в grep, можно подумать, что это будет:

grep 'R1.*R2|R2.*R1'

в то время как в awk это будет:

awk '/R1/ && /R2/'

но что, если R2перекрывается или является подмножеством R1? Эта команда grep просто не будет работать, в то время как команда awk будет работать. Допустим, вы хотите найти строки, которые содержат theи heat:

$ echo 'theatre' | grep 'the.*heat|heat.*the'
$ echo 'theatre' | awk '/the/ && /heat/'
theatre

Для этого вам нужно использовать 2 greps и трубу:

$ echo 'theatre' | grep 'the' | grep 'heat'
theatre

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

Если оставить в стороне, что делать, если вы хотите расширить свое решение для соответствия 3 регулярным выражениям R1, R2 и R3. В grep это был бы один из этих неудачных вариантов:

grep 'R1.*R2.*R3|R1.*R3.*R2|R2.*R1.*R3|R2.*R3.*R1|R3.*R1.*R2|R3.*R2.*R1' file
grep R1 file | grep R2 | grep R3

в то время как в awk это будет кратким, очевидным, простым, эффективным:

awk '/R1/ && /R2/ && /R3/'

Теперь, что если вы действительно хотите сопоставить литеральные строки S1 и S2 вместо регулярных выражений R1 и R2? Вы просто не можете сделать это за один вызов grep, вы должны либо написать код, чтобы экранировать все метасхемы RE, прежде чем вызывать grep:

S1=$(sed 's/[^^]/[&]/g; s/\^/\\^/g' <<< 'R1')
S2=$(sed 's/[^^]/[&]/g; s/\^/\\^/g' <<< 'R2')
grep 'S1.*S2|S2.*S1'

или снова используйте 2 greps и трубу:

grep -F 'S1' file | grep -F 'S2'

что опять-таки плохой выбор, тогда как с помощью awk вы просто используете строковый оператор вместо оператора регулярного выражения:

awk 'index($0,S1) && index($0.S2)'

А что если вы хотите сопоставить 2 регулярных выражения в абзаце, а не в строке? Не может быть сделано в grep, тривиально в awk:

awk -v RS='' '/R1/ && /R2/'

Как насчет всего файла? Снова не может быть сделано в grep и тривиально в awk (на этот раз я использую GNU awk для multi-char RS для краткости, но это не намного больше кода в любом awk, или вы можете выбрать контрольный char, который вы не знаете быть на входе для RS, чтобы сделать то же самое):

awk -v RS='^$' '/R1/ && /R2/'

Итак, если вы хотите найти несколько регулярных выражений или строк в строке, абзаце или файле, не используйте grep, используйте awk.


Является ли awk '/R1/ && /R2/'регистронезависимым?
Прометей

@ Хашим - нет. Чтобы сделать его нечувствительным к регистру с GNU awk, вы должны делать это awk -v IGNORECASE=1 '/R1/ && /R2/'и с любым awkawk '{x=toupper($0)} x~/R1/ && x~/R2/'
Ed Morton


2

Найдены строки, которые начинаются только с 6 пробелов и заканчиваются:

 cat my_file.txt | grep
 -e '^      .*(\.c$|\.cpp$|\.h$|\.log$|\.out$)' # .c or .cpp or .h or .log or .out
 -e '^      .*[0-9]\{5,9\}$' # numers between 5 and 9 digist
 > nolog.txt

2

Допустим, нам нужно найти количество слов в тестовом файле. Есть два способа сделать это

1) Используйте команду grep с шаблоном сопоставления регулярных выражений

grep -c '\<\(DOG\|CAT\)\>' testfile

2) Используйте команду egrep

egrep -c 'DOG|CAT' testfile 

С egrep вам не нужно беспокоиться о выражении, а просто разделяйте слова разделителем каналов.


2

git grep

Вот синтаксис, использующий git grepнесколько шаблонов:

git grep --all-match --no-index -l -e string1 -e string2 -e string3 file

Вы также можете комбинировать шаблоны с логическими выражениями, такими как --and, --orи--not .

Проверьте man git-grepна помощь.


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

--no-index Поиск файлов в текущем каталоге, который не управляется Git.

-l/ --files-with-matches/--name-only Показывать только имена файлов.

-eСледующий параметр - это шаблон. По умолчанию используется базовое регулярное выражение.

Другие параметры для рассмотрения:

--threads Количество рабочих потоков grep для использования.

-q/ --quiet/ --silentНе выводить соответствует линии; выйти со статусом 0, когда есть совпадение.

Чтобы изменить тип шаблона, вы также можете использовать -G/ --basic-regexp(по умолчанию), -F/ --fixed-strings, -E/ --extended-regexp, -P/ --perl-regexp,-f file и другие.

Связанный:

Для операции ИЛИ см .:


2
Всегда думал, что «git grep» можно запускать только внутри репозитория git. Я не знал о параметре --no-index. Спасибо за указание на это!
Камараю Кусуманчи

1

Поместите строки, которые вы хотите grep для в файл

echo who    > find.txt
echo Roger >> find.txt
echo [44][0-9]{9,} >> find.txt

Затем поиск с использованием -f

grep -f find.txt BIG_FILE_TO_SEARCH.txt 

1
grep '(string1.*string2 | string2.*string1)' filename

получит строку с string1 и string2 в любом порядке


Чем это отличается от двух лучших ответов?
luk2302

1
grep -i -w 'string1\|string2' filename

Это работает для точного совпадения слов и совпадения слов без учета регистра, для этого используется -i


0

для многострочного соответствия:

echo -e "test1\ntest2\ntest3" |tr -d '\n' |grep "test1.*test3"

или

echo -e "test1\ntest5\ntest3" >tst.txt
cat tst.txt |tr -d '\n' |grep "test1.*test3\|test3.*test1"

нам просто нужно удалить символ новой строки, и это работает!


0

Вы должны иметь grepвот так:

$ grep 'string1' file | grep 'string2'

1
Это выполняет логическое И. ОП хочет логическое ИЛИ.
Бен Уилер

1
@BenWheeler: Из вопроса: «Так как мне сопоставить с grep только те строки, которые содержат обе строки?»
Эрик I

0

Я часто сталкиваюсь с той же проблемой, что и ваша, и я просто написал фрагмент сценария:

function m() { # m means 'multi pattern grep'

    function _usage() {
    echo "usage: COMMAND [-inH] -p<pattern1> -p<pattern2> <filename>"
    echo "-i : ignore case"
    echo "-n : show line number"
    echo "-H : show filename"
    echo "-h : show header"
    echo "-p : specify pattern"
    }

    declare -a patterns
    # it is important to declare OPTIND as local
    local ignorecase_flag  filename linum header_flag colon result OPTIND

    while getopts "iHhnp:" opt; do
    case $opt in
        i)
        ignorecase_flag=true ;;
        H)
        filename="FILENAME," ;;
        n)
        linum="NR," ;;
        p)
        patterns+=( "$OPTARG" ) ;;
        h)
        header_flag=true ;;
        \?)
        _usage
        return ;;
    esac
    done

    if [[ -n $filename || -n $linum ]]; then
    colon="\":\","
    fi

    shift $(( $OPTIND - 1 ))

    if [[ $ignorecase_flag == true ]]; then
    for s in "${patterns[@]}"; do
            result+=" && s~/${s,,}/"
    done
    result=${result# && }
    result="{s=tolower(\$0)} $result"
    else
    for s in "${patterns[@]}"; do
            result="$result && /$s/"
    done
    result=${result# && }
    fi

    result+=" { print "$filename$linum$colon"\$0 }"

    if [[ ! -t 0 ]]; then       # pipe case
    cat - | awk "${result}"
    else
    for f in "$@"; do
        [[ $header_flag == true ]] && echo "########## $f ##########"
        awk "${result}" $f
    done
    fi
}

Использование:

echo "a b c" | m -p A 
echo "a b c" | m -i -p A # a b c

Вы можете положить его в .bashrc, если хотите.


0

Когда обе строки в последовательности, поместите шаблон между ними в grepкоманде:

$ grep -E "string1(?.*)string2" file

Пример, если следующие строки содержатся в файле с именем Dockerfile:

FROM python:3.8 as build-python
FROM python:3.8-slim

Чтобы получить строку, содержащую строки: FROM pythonи as build-pythonзатем используйте:

$ grep -E "FROM python:(?.*) as build-python" Dockerfile

Тогда в выводе будет показана только строка, содержащая обе строки :

FROM python:3.8 as build-python

-2

ripgrep

Вот пример использования rg:

rg -N '(?P<p1>.*string1.*)(?P<p2>.*string2.*)' file.txt

Это один из самых быстрых инструментов поиска, так как он построен на основе движка регулярных выражений Rust который использует конечные автоматы, SIMD и агрессивные буквальные оптимизации, чтобы сделать поиск очень быстрым.

Используйте его, особенно когда вы работаете с большими данными.

См. Также запрос связанных функций на GH-875 .


1
Этот ответ не совсем правильный. Названные группы захвата не нужны, и это не обрабатывает случай, когда string2появляется раньше string1. Самое простое решение этой проблемы rg string1 file.txt | rg string2.
BurntSushi5
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.