З'єднайте два рядки в одному рядку з грепом


218

Я намагаюся використовувати grepдля узгодження рядків, які містять дві різні рядки. Я спробував наступні , але це відповідає лінії , які містять або строку1 або 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
перший варіант: трубопровід одного грефа в другий НЕ дає результату АБО, він дає результат І.
masukomi

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
це те, як схопитися за string1 АБО string2. у питанні чітко зазначено, що вони шукають string1 AND string2.
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\'

Перший греп починає рекурсивний пошук ( r), ігноруючи регістр ( i) та перелічуючи (роздруковуючи) назву файлів, які відповідають ( l) за один термін ( 'action'з єдиними лапками), що виникають у будь-якому місці файлу.

Подальші підказки шукають інші терміни, зберігаючи нечутливість регістру та перелічуючи файли відповідності.

Остаточний список файлів, які ви отримаєте, буде тими, що містять ці умови, в будь-якому порядку в будь-якому місці файлу.


2
Домовились! Я лише зазначу, що мені довелося дати xargs "-d '\ n" "для обробки імен файлів з пробілами. Це працювало для мене на Linux: grep -ril 'foo' | xargs -d '\n' grep -il 'bar'
Tommy Harris

16

Якщо у вас є grepз -Pможливістю для обмеженого perlрегулярного виразу, ви можете використовувати

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

що має перевагу в роботі з струнами, що перекриваються. Це дещо простіше використовувати perlяк grep, оскільки ви можете більш точно вказати та логіку:

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

1
Найкраща відповідь. Shell дуже легкий і швидкий, але як тільки візерунок стає складним, ви повинні використовувати 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 показує приклад, зіставляючи string1 або string2 і запитує, як відповідати рядки, що містять обидва рядки. Цей приклад все ще дає АБО.
gustafbstrom

7

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

grep 'string1' filename | grep 'string2'

яка передає результати першої команди у другу греп. Це повинно дати вам лише рядки, які відповідають обом.


1
Ваші твердження вірні, але не відповідайте на питання ОП
Бен Вілер

Це відповідає на питання, і саме так пише більшість людей.
Пітер К

7

Ви можете спробувати щось подібне:

(pattern1.*pattern2|pattern2.*pattern1)

4

І як люди пропонували Perl і Python, і складені сценарії оболонки, тут простий підхід awk :

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

Переглянувши коментарі до прийнятої відповіді: ні, це не робить багаторядкових; але тоді це теж не те, про що запитував автор запитання.


3

Не намагайтеся використовувати grep для цього, використовуйте awk. Для того, щоб відповідати 2 регулярними виразами R1 та R2 в греппі, ви вважаєте, що це буде:

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

тоді як у розпаді це було б:

awk '/R1/ && /R2/'

але що робити, якщо R2перекривається або є підмножиною R1? Ця команда grep просто не працює, тоді як команда awk буде. Скажімо, ви хочете знайти рядки, які містять theта heat:

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

Для цього вам доведеться використовувати 2 грейпи та трубу:

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

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

Відклавши це в бік, що робити, якщо ви хочете поширити своє рішення на 3 повторних вираза R1, R2 та R3. В грепі, це був би один із таких поганих варіантів:

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 грейпи та трубу:

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

що знову поганий вибір, тоді як з awk ви просто використовуєте струнний оператор замість оператора regexp:

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

Тепер, що робити, якщо ви хотіли відповідати 2 повторними виразами в абзаці, а не рядку? Не можна робити grep, trivial in awk:

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

Як щодо цілого файлу? Знову не можна робити греп і тривіально в awk (на цей раз я використовую GNU awk для багатоканальної RS для стисності, але це не набагато більше коду в будь-якому awk, або ви можете вибрати контрольну схему, яку ви не знаєте бути у вході для RS, щоб зробити те саме):

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

Отже - якщо ви хочете знайти кілька рядків або рядків у рядку чи абзаці чи файлі, тоді не використовуйте grep, використовуйте awk.


Чи awk '/R1/ && /R2/'нечутливі до регістру?
Прометей

@Hashim - ні. Щоб зробити це невідчутливим до випадку з GNU awk, який би ви робили, awk -v IGNORECASE=1 '/R1/ && /R2/'і з будь-яким awkawk '{x=toupper($0)} x~/R1/ && x~/R2/'
Ед Мортон,


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 

З егрепом вам не потрібно хвилюватися за вираз і просто розділити слова роздільником труби.


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 Кількість греп-робочих ниток для використання.

-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

Помістіть у файл рядки, які ви хочете виконати

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
Це виконує логічний І. ОП хоче логічного АБО.
Ben Wheeler

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

Це один з найшвидших інструментів прихватування, оскільки він побудований на основі регекс- двигуна Руста який використовує кінцеві автомати, SIMD та агресивні буквальні оптимізації, щоб зробити пошук дуже швидким.

Використовуйте його, особливо коли ви працюєте з великими даними.

Дивіться також відповідний запит на функції на GH-875 .


1
Ця відповідь не зовсім правильна. Названі групи захоплення непотрібні, і це не обробляє випадок, коли він string2з'являється раніше string1. Найпростіше рішення цієї проблеми rg string1 file.txt | rg string2.
BurntSushi5
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.