Nối hai chuỗi trong một dòng với grep


218

Tôi đang cố gắng sử dụng grepđể khớp các dòng có chứa hai chuỗi khác nhau. Tôi đã thử các cách sau nhưng điều này khớp với các dòng có chứa chuỗi1 hoặc chuỗi2 mà không phải là những gì tôi muốn.

grep 'string1\|string2' filename

Vậy làm thế nào để tôi chỉ khớp với grepcác dòng có chứa cả hai chuỗi ?


Câu trả lời:


189

Bạn có thể dùng grep 'string1' filename | grep 'string2'

Hoặc là, grep 'string1.*string2\|string2.*string1' filename


5
@AlexanderN thực sự tôi không thể làm cho nó hoạt động với multiline, điều đó thật kỳ lạ khi nó được chấp nhận ..
Sức mạnh của Bảo Bình vào

1
Đó không phải là một câu hỏi đa dòng. Nếu là multiline, grep -P hỗ trợ regex theo phong cách Perl ...
Scott Prive

20
Chỉ hoạt động khi cả 'chuỗi1' VÀ 'chuỗi2' nằm trên cùng một dòng. Nếu bạn muốn tìm các dòng có 'chuỗi1' hoặc 'chuỗi2', hãy xem câu trả lời của người dùng.
lifeson106

10
tùy chọn đầu tiên: đường ống một grep vào giây không tạo ra kết quả HOẶC nó tạo ra kết quả AND.
masukomi

1
Tôi đã sử dụnggrep -e "string1" -e "string2"
Ravi Dhoriya

198

Tôi nghĩ rằng đây là những gì bạn đang tìm kiếm:

grep -E "string1|string2" filename

Tôi nghĩ rằng câu trả lời như thế này:

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

chỉ phù hợp với trường hợp cả hai có mặt, không phải cái này hay cái kia hoặc cả hai.


14
sẽ không grep -e "string1" -e "string2" filenamelàm như vậy?
janosdivenyi

25
đây là cách grep cho chuỗi1 HOẶC chuỗi2. câu hỏi nêu rõ họ đang tìm kiếm chuỗi1 VÀ chuỗi2.
orion elenzil

9
Khá chắc chắn rằng câu hỏi khá chính xác:How do I match lines that contains *both* strings?
r0estir0bbe

Nó có thể in với cùng một dòng không?
吴毅

1
Tại sao câu trả lời này vẫn còn ở đây? Nó KHÔNG phải là một câu trả lời cho câu hỏi.
Prometheus

26

Để tìm kiếm các tệp chứa tất cả các từ theo bất kỳ thứ tự nào ở bất cứ đâu:

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

Grep đầu tiên khởi động một tìm kiếm đệ quy ( r), bỏ qua trường hợp ( i) và liệt kê (in ra) tên của các tệp phù hợp ( l) cho một thuật ngữ ( 'action'với dấu ngoặc đơn) xuất hiện ở bất kỳ đâu trong tệp.

Các greps tiếp theo tìm kiếm các thuật ngữ khác, giữ lại trường hợp không nhạy cảm và liệt kê ra các tệp phù hợp.

Danh sách cuối cùng của các tệp mà bạn sẽ nhận sẽ là các tệp có chứa các điều khoản này, theo bất kỳ thứ tự nào ở bất kỳ đâu trong tệp.


2
Đã đồng ý! Tôi sẽ chỉ lưu ý rằng tôi phải cung cấp cho xargs một "-d '\ n'" để xử lý tên tệp có khoảng trắng. Điều này làm việc cho tôi trên Linux: grep -ril 'foo' | xargs -d '\n' grep -il 'bar'
Tommy Harris

16

Nếu bạn có một grepvới một -Plựa chọn cho một hạn chế perlregex, bạn có thể sử dụng

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

có lợi thế làm việc với các chuỗi chồng chéo. Nó hơi đơn giản hơn perlkhi sử dụng as grep, bởi vì bạn có thể chỉ định trực tiếp và logic hơn:

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

1
Câu trả lời tốt nhất. Shell rất dễ dàng và nhanh chóng, nhưng một khi mô hình trở nên phức tạp, bạn nên sử dụng Python hoặc Perl (hoặc Awk). Đừng đập đầu vào tường cố gắng chứng minh nó có thể được thực hiện trong vỏ nguyên chất (bất cứ điều gì có nghĩa là những ngày này). Xin nhắc lại, các công cụ này có thể được sử dụng theo cú pháp "một lớp lót" được nhúng vào tập lệnh shell hiện có.
Scott Prive

12

Phương pháp của bạn gần như tốt, chỉ thiếu -w

grep -w 'string1\|string2' filename

1
Ít nhất là trên OS-X và FreeBSD, nó hoạt động! Tôi đoán là bạn đang ở một cái gì đó khác (mà OP không xác định - hy vọng bạn không hạ thấp câu trả lời đúng cho nhiều người dùng ngoại trừ bạn).
Leo

Tôi đang dùng OS-X. Có lẽ tôi không làm điều này một cách chính xác? Hãy xem những gì tôi đã làm: i.imgur.com/PFVlVAG.png
Ariel

1
Lạ Tôi mong đợi sự khác biệt là không tham gia vào tệp, nhưng, nếu tôi thực hiện phương pháp của mình với ls của bạn, tôi sẽ nhận được kết quả là bạn không: imgur.com/8eTt3Ak.png - Cả hai trên cả OS-X 10.9.5 ( "grep (BSD grep) 2.5.1-FreeBSD") và FreeBSD 10 ("grep (GNU grep) 2.5.1-FreeBSD"). Tôi tò mò không biết bạn grep -Vlà ai.
Leo

1
Các ví dụ của bạn đang làm việc cho tôi: i.imgur.com/K8LM69O.png Vì vậy, sự khác biệt là phương pháp này không nhận các chuỗi con, chúng phải tự hoàn thành các chuỗi. Tôi đoán bạn sẽ cần phải xây dựng regexps trong grep để tìm kiếm chuỗi con. Một cái gì đó như thế này:grep -w 'regexp1\|regexp2' filename
Ariel

2
OP hiển thị một ví dụ bằng cách khớp chuỗi1 hoặc chuỗi2 và hỏi cách khớp các dòng có chứa cả hai chuỗi. Ví dụ này vẫn mang lại HOẶC.
gustafbstrom

7

Các |nhà điều hành trong một biểu thức chính quy có nghĩa là hoặc. Điều đó có nghĩa là chuỗi1 hoặc chuỗi2 sẽ khớp. Bạn có thể làm:

grep 'string1' filename | grep 'string2'

Nó sẽ dẫn các kết quả từ lệnh đầu tiên vào grep thứ hai. Điều đó sẽ cung cấp cho bạn chỉ dòng phù hợp với cả hai.


1
Câu nói của bạn là đúng, nhưng đừng trả lời câu hỏi của OP
Ben Wheeler

Điều này không trả lời câu hỏi và đây thực sự là cách mà hầu hết mọi người viết nó.
Peter K

7

Bạn có thể thử một cái gì đó như thế này:

(pattern1.*pattern2|pattern2.*pattern1)

4

Và như mọi người đề xuất perl và python, và các kịch bản shell phức tạp, đây là một cách tiếp cận awk đơn giản :

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

Đã xem xét các ý kiến ​​cho câu trả lời được chấp nhận: không, điều này không làm nhiều dòng; nhưng sau đó cũng không phải là những gì tác giả của câu hỏi yêu cầu.


3

Đừng cố sử dụng grep cho việc này, thay vào đó hãy sử dụng awk. Để khớp 2 regexps R1 và R2 trong grep bạn nghĩ nó sẽ là:

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

trong khi ở awk nó sẽ là:

awk '/R1/ && /R2/'

nhưng điều gì xảy ra nếu R2trùng lặp với hoặc là một tập hợp con R1? Lệnh grep đó đơn giản là không hoạt động trong khi lệnh awk sẽ. Hãy nói rằng bạn muốn tìm các dòng có chứa theheat:

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

Bạn sẽ phải sử dụng 2 greps và một đường ống cho điều đó:

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

và tất nhiên nếu bạn thực sự yêu cầu chúng tách biệt, bạn luôn có thể viết trong awk cùng một biểu thức chính quy như bạn đã sử dụng trong grep và có các giải pháp awk thay thế không liên quan đến việc lặp lại các biểu thức chính trong mọi trình tự có thể.

Đặt điều đó sang một bên, điều gì sẽ xảy ra nếu bạn muốn mở rộng giải pháp của mình để phù hợp với 3 regexps R1, R2 và R3. Trong grep đó sẽ là một trong những lựa chọn tồi:

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

trong khi trong awk nó sẽ là ngắn gọn, rõ ràng, đơn giản, hiệu quả:

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

Bây giờ, điều gì sẽ xảy ra nếu bạn thực sự muốn khớp các chuỗi ký tự S1 và S2 thay vì regexps R1 và R2? Bạn chỉ đơn giản là không thể làm điều đó trong một cuộc gọi đến grep, bạn phải viết mã để thoát tất cả các siêu dữ liệu RE trước khi gọi grep:

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

hoặc một lần nữa sử dụng 2 greps và một đường ống:

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

một lần nữa là những lựa chọn tồi trong khi với awk, bạn chỉ cần sử dụng toán tử chuỗi thay vì toán tử regrec:

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

Bây giờ, nếu bạn muốn khớp 2 regexps trong một đoạn chứ không phải là một dòng thì sao? Không thể được thực hiện trong grep, tầm thường trong awk:

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

Làm thế nào về toàn bộ tập tin? Một lần nữa không thể được thực hiện bằng grep và tầm thường trong awk (lần này tôi đang sử dụng GNU awk cho multi-char RS vì sự đơn giản nhưng nó không có nhiều mã hơn trong bất kỳ awk nào hoặc bạn có thể chọn một điều khiển mà bạn biết sẽ không ở trong đầu vào để RS thực hiện tương tự):

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

Vì vậy - nếu bạn muốn tìm nhiều regexps hoặc chuỗi trong một dòng hoặc đoạn hoặc tệp thì đừng sử dụng grep, hãy sử dụng awk.


awk '/R1/ && /R2/'trường hợp không nhạy cảm?
Prometheus

@Hashim - không. Để làm cho nó không phân biệt chữ hoa chữ thường với GNU awk bạn sẽ làm awk -v IGNORECASE=1 '/R1/ && /R2/'và với bất kỳ awk nàoawk '{x=toupper($0)} x~/R1/ && x~/R2/'
Ed Morton


2

Các dòng được tìm thấy chỉ bắt đầu với 6 khoảng trắng và kết thúc bằng:

 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

Giả sử chúng ta cần tìm số lượng nhiều từ trong tệp kiểm tra tệp. Có hai cách để đi về nó

1) Sử dụng lệnh grep với mẫu khớp regex

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

2) Sử dụng lệnh egrep

egrep -c 'DOG|CAT' testfile 

Với egrep bạn không cần phải lo lắng về biểu thức và chỉ cần tách các từ bằng dấu tách ống.


2

git grep

Đây là cú pháp sử dụng git grepvới nhiều mẫu:

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

Bạn cũng có thể kết hợp các mẫu với các biểu thức Boolean như --and, --or--not.

Kiểm tra man git-grepgiúp đỡ.


--all-matchKhi đưa ra nhiều biểu thức mẫu, cờ này được chỉ định để giới hạn đối sánh với các tệp có các dòng khớp với tất cả chúng .

--no-index Tìm kiếm tệp trong thư mục hiện tại không được quản lý bởi Git.

-l/ --files-with-matches/ --name-onlyChỉ hiển thị tên của các tập tin.

-eTham số tiếp theo là mẫu. Mặc định là sử dụng regrec cơ bản.

Các thông số khác cần xem xét:

--threads Số lượng chủ đề grep worker để sử dụng.

-q/ --quiet/ --silentKhông xuất các dòng khớp; thoát với trạng thái 0 khi có một trận đấu.

Để thay đổi kiểu mẫu, bạn cũng có thể sử dụng -G/ --basic-regexp(mặc định), -F/ --fixed-strings, -E/ --extended-regexp, -P/ --perl-regexp, -f filevà khác.

Liên quan:

Đối với hoạt động OR , xem:


2
Luôn nghĩ rằng "git grep" chỉ có thể được chạy trong kho git. Tôi không biết tùy chọn --no-index. Cảm ơn đã chỉ ra điều đó!
Kamaraju Kusumanchi

1

Đặt các chuỗi bạn muốn grep cho vào một tệp

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

Sau đó tìm kiếm bằng cách sử dụng -f

grep -f find.txt BIG_FILE_TO_SEARCH.txt 

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

sẽ nhận được dòng với string1 và string2 theo bất kỳ thứ tự nào


Cách nào khác với ít nhất hai câu trả lời hàng đầu?
luk2302

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

Điều này hoạt động cho từ khớp chính xác và từ phù hợp với trường hợp từ không nhạy cảm, cho rằng -i được sử dụng


0

cho trận đấu nhiều dòng:

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

hoặc là

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

chúng ta chỉ cần loại bỏ ký tự dòng mới và nó hoạt động!


0

Bạn nên có grepnhư thế này:

$ grep 'string1' file | grep 'string2'

1
Điều này thực hiện một logic VÀ. OP muốn logic HOẶC.
Ben Wheeler

1
@BenWheeler: Từ câu hỏi: "Vậy làm thế nào để tôi khớp với grep chỉ các dòng có chứa cả hai chuỗi?"
Erik I

0

Tôi thường gặp vấn đề tương tự như của bạn và tôi chỉ viết một đoạn kịch bản:

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
}

Sử dụng:

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

Bạn có thể đặt nó trong .bashrc nếu bạn thích.


0

Khi cả hai chuỗi theo thứ tự thì đặt một mẫu ở giữa greplệnh:

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

Ví dụ nếu các dòng sau được chứa trong một tệp có tên Dockerfile:

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

Để lấy dòng chứa các chuỗi: FROM pythonas build-pythonsau đó sử dụng:

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

Sau đó, đầu ra sẽ chỉ hiển thị dòng chứa cả hai chuỗi :

FROM python:3.8 as build-python

-2

ripgrep

Dưới đây là ví dụ sử dụng rg:

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

Đây là một trong những công cụ grepping nhanh nhất, vì nó được xây dựng trên công cụ regex của Rust , sử dụng automata hữu hạn, SIMD và tối ưu hóa theo nghĩa đen tích cực để giúp tìm kiếm rất nhanh.

Sử dụng nó, đặc biệt là khi bạn đang làm việc với một dữ liệu lớn.

Xem thêm yêu cầu tính năng liên quan tại GH-875 .


1
Câu trả lời này không hoàn toàn đúng. Các nhóm bắt giữ được đặt tên là không cần thiết và điều này không xử lý trường hợp khi string2xuất hiện trước đó string1. Giải pháp đơn giản nhất cho vấn đề này là rg string1 file.txt | rg string2.
BurntSushi5
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.