Bagaimana cara saya mengambil baris yang mengandung dua kata, tetapi tidak keduanya?


25

Saya mencoba untuk menggunakan grephanya menampilkan baris yang mengandung salah satu dari dua kata, jika hanya satu dari mereka muncul di baris, tetapi tidak jika mereka berada di baris yang sama.

Sejauh ini saya sudah mencoba grep pattern1 | grep pattern2 | ...tetapi tidak mendapatkan hasil yang saya harapkan.


(1) Anda berbicara tentang "kata-kata" dan "pola". Yang mana itu? Kata-kata biasa seperti "cepat", "coklat" dan "rubah", atau ekspresi reguler seperti [a-z][a-z0-9]\(,7\}\(\.[a-z0-9]\{,3\}\)+? (2) Bagaimana jika salah satu kata / pola muncul lebih dari satu kali dalam satu baris (dan yang lainnya tidak muncul)? Apakah itu setara dengan kata yang muncul sekali, atau apakah itu dihitung sebagai beberapa kejadian?
G-Man Mengatakan 'Reinstate Monica'

Jawaban:


59

Alat selain grepadalah cara untuk pergi.

Menggunakan perl, misalnya, perintahnya adalah:

perl -ne 'print if /pattern1/ xor /pattern2/'

perl -nemenjalankan perintah yang diberikan pada setiap baris stdin, yang dalam hal ini mencetak garis jika cocok /pattern1/ xor /pattern2/, atau dengan kata lain cocok dengan satu pola tetapi tidak yang lain (eksklusif atau).

Ini berfungsi untuk pola dalam urutan apa pun, dan harus memiliki kinerja yang lebih baik daripada banyak pemanggilan grep, dan kurang mengetik juga.

Atau, bahkan lebih pendek, dengan awk:

awk 'xor(/pattern1/,/pattern2/)'

atau untuk versi awk yang tidak memiliki xor:

awk '/pattern1/+/pattern2/==1`

4
Bagus - apakah Awk hanya xortersedia di GNU Awk?
steeldriver

9
@steeldriver Saya pikir itu hanya GNU, ya. Atau setidaknya itu hilang pada versi yang lebih lama. Anda dapat menggantinya dengan /pattern1/+/pattern2/==1ir xoryang hilang.
Chris

4
@ Jim. Anda bisa meletakkan batas kata ( \b) dalam pola itu sendiri, yaitu \bword\b.
wjandrea

4
@vikingsteve Jika Anda secara khusus ingin menggunakan grep, ada banyak jawaban lain di sini. Tetapi bagi orang-orang yang hanya ingin menyelesaikan pekerjaan, ada baiknya mengetahui ada alat lain yang dapat melakukan semua yang dilakukan grep, tetapi semakin mudah.
Chris

3
@vikingsteve Saya akan sangat mengira bahwa permintaan untuk solusi grep adalah semacam masalah XY
Hagen von Eitzen

30

Dengan GNU grep, Anda bisa meneruskan kedua kata grepdan kemudian menghapus baris yang mengandung kedua pola.

$ cat testfile.txt
abc
def
abc def
abc 123 def
1234
5678
1234 def abc
def abc

$ grep -w -e 'abc' -e 'def' testfile.txt | grep -v -e 'abc.*def' -e 'def.*abc'
abc
def

16

Coba dengan egrep

egrep  'pattern1|pattern2' file | grep -v -e 'pattern1.*pattern2' -e 'pattern2.*pattern1'

3
juga dapat ditulis sebagaigrep -e foo -e bar | grep -v -e 'foo.*bar' -e 'bar.*foo'
glenn jackman

8
Juga, perhatikan dari halaman manual grep: Direct invocation as either egrep or fgrep is deprecated- prefergrep -E
glenn jackman

Itu tidak ada di OS @glennjackman saya
Grump

1
@ Grump benarkah? OS apa itu? Bahkan POSIX menyebutkan bahwa grep harus memiliki -fdan -eopsi meskipun lebih tua egrepdan fgrepakan terus didukung untuk sementara waktu.
terdon

1
@terdon, POSIX tidak menentukan jalur utilitas POSIX. Sekali lagi, ada, standar grep(yang mendukung -F, -E, -e, -fsebagai POSIX membutuhkan) dalam /usr/xpg4/bin. Utilitas di /binadalah yang kuno.
Stéphane Chazelas

12

Dengan grepimplementasi yang mendukung ekspresi reguler perl-like (seperti pcregrepatau GNU atau ast-open grep -P), Anda dapat melakukannya dalam satu greppermintaan dengan:

grep -P '^(?=.*pat1)(?!.*pat2)|^(?=.*pat2)(?!.*pat1)'

Yaitu menemukan garis yang cocok pat1tetapi tidak pat2, atau pat2tidak pat1.

(?=...)dan (?!...)masing-masing melihat ke depan dan operator melihat ke depan negatif. Jadi secara teknis, pencarian di atas untuk awal subjek ( ^) asalkan diikuti .*pat1dan tidak diikuti .*pat2, atau sama dengan pat1dan pat2terbalik.

Itu suboptimal untuk garis yang mengandung kedua pola karena mereka kemudian akan dicari dua kali. Anda bisa menggunakan operator perl yang lebih canggih seperti:

grep -P '^(?=.*pat1|())(?(1)(?=.*pat2)|(?!.*pat2))'

(?(1)yespattern|nopattern)cocok dengan yespatternjika grup penangkap 1st (kosong di ()atas) cocok, dan nopatternsebaliknya. Jika itu ()cocok, itu berarti pat1tidak cocok, jadi kami mencari pat2(melihat ke depan positif), dan kami mencari tidak pat2 sebaliknya (melihat ke depan negatif).

Dengan sed, Anda bisa menulisnya:

sed -ne '/pat1/{/pat2/!p;d;}' -e '/pat2/p'

Solusi pertama Anda gagal dengan grep: the -P option only supports a single pattern, setidaknya pada setiap sistem saya memiliki akses. +1 untuk solusi kedua Anda.
Chris

1
@ Chris, kamu benar. Itu tampaknya menjadi batasan khusus untuk GNU grep. pcregrepdan ast terbuka grep tidak memiliki masalah itu. Saya telah mengganti banyak -edengan operator RE pergantian, jadi seharusnya berfungsi dengan GNU grepjuga sekarang.
Stéphane Chazelas

Ya, sekarang berfungsi dengan baik.
Chris

3

Dalam istilah Boolean, Anda mencari A xor B, yang dapat ditulis sebagai

(A dan bukan B)

atau

(B dan bukan A)

Mengingat bahwa pertanyaan Anda tidak menyebutkan bahwa Anda prihatin dengan urutan output selama garis yang cocok ditampilkan, ekspansi Boolean A xor B sangat sederhana dalam grep:

$ cat << EOF > foo
> a b
> a
> b
> c a
> c b
> b a
> b c
> EOF
$ grep -w 'a' foo | grep -vw 'b'; grep -w 'b' foo | grep -vw 'a';
a
c a
b
c b
b c

1
Ini berfungsi, tetapi itu akan mengacak urutan file.
Sparhawk

@Sparhawk Benar, meskipun "berebut" adalah kata yang kasar. ;) itu mencantumkan semua 'a' cocok terlebih dahulu, secara berurutan, lalu semua 'b' cocok berikutnya, secara berurutan. OP tidak menyatakan minat untuk mempertahankan pesanan, cukup tunjukkan garisnya. FAWK, langkah selanjutnya bisa jadi sort | uniq.
Jim L.

Panggilan yang adil; Saya setuju bahasa saya tidak akurat. Saya bermaksud menyiratkan bahwa orde asli akan diubah.
Sparhawk

1
@Sparhawk ... Dan saya mengedit dalam pengamatan Anda untuk pengungkapan penuh.
Jim L.

-2

Untuk contoh berikut:

# Patterns:
#    apple
#    pear

# Example line
line="a_apple_apple_pear_a"

Hal ini dapat dilakukan murni dengan grep -E, uniq, dan wc.

# Grep for regex pattern, sort as unique, and count the number of lines
result=$(grep -oE 'apple|pear' <<< $line | sort -u | wc -l)

Jika grepdikompilasi dengan ekspresi reguler Perl maka Anda dapat mencocokkan pada kejadian terakhir alih-alih perlu mengirim ke uniq:

# Grep for regex pattern and count the number of lines
result=$(grep -oP '(apple(?!.*apple)|pear(?!.*pear))' <<< $line | wc -l)

Keluarkan hasilnya:

# Only one of the words exists if the result is < 2
((result > 0)) &&
   if (($result < 2)); then
      echo Only one word matched
   else
      echo Both words matched
   fi

Satu kalimat:

(($(grep -oP '(apple(?!.*apple)|pear(?!.*pear))' <<< $line | wc -l) == 1)) && echo Only one word matched

Jika Anda tidak ingin hard-code polanya, menggabungkannya dengan set elemen variabel dapat diotomatisasi dengan suatu fungsi.

Ini juga dapat dilakukan secara asli di Bash sebagai fungsi tanpa pipa atau proses tambahan tetapi akan lebih terlibat dan mungkin di luar ruang lingkup pertanyaan Anda.


(1) Saya bertanya-tanya kapan seseorang akan memberikan jawaban menggunakan ekspresi reguler Perl. Jika Anda fokus pada bagian posting itu, dan menjelaskan cara kerjanya, ini bisa menjadi jawaban yang bagus. (2) Tapi saya khawatir sisanya tidak begitu baik. Pertanyaannya mengatakan "hanya tampilkan baris yang mengandung salah satu dari dua kata" (penekanan ditambahkan). Jika output seharusnya berupa baris , maka masuk akal bahwa input tersebut juga harus beberapa baris.   Tetapi pendekatan Anda hanya bekerja ketika hanya melihat satu baris. … (Lanjutan)
G-Man Mengatakan 'Reinstate Monica'

(Lanjutkan) ... Misalnya, jika input berisi baris Big apple\ndan pear-shaped\n, maka output harus berisi kedua baris tersebut. Solusi Anda akan mendapatkan hitungan 2; versi panjang akan melaporkan "Kedua kata cocok" (yang merupakan jawaban untuk pertanyaan yang salah) dan versi pendek tidak akan mengatakan apa-apa. (3) Saran: menggunakan di -osini adalah ide yang sangat buruk, karena menyembunyikan garis yang berisi kecocokan, sehingga Anda tidak dapat melihat saat kedua kata muncul di baris yang sama. … (Lanjutan)
G-Man Mengatakan 'Reinstate Monica'

(Lanjutkan) ... (4) Intinya: Anda menggunakan uniq/ sort -udan ekspresi biasa Perl mewah untuk mencocokkan hanya kejadian terakhir pada setiap baris tidak benar-benar menambahkan hingga jawaban yang berguna untuk pertanyaan ini. Tetapi, bahkan jika mereka melakukannya, itu akan tetap menjadi jawaban yang buruk karena Anda tidak menjelaskan bagaimana mereka berkontribusi dalam menjawab pertanyaan. (Lihat jawaban Stéphane Chazelas untuk contoh penjelasan yang baik.)
G-Man Mengatakan 'Reinstate Monica'

OP mengatakan bahwa mereka ingin "hanya menunjukkan baris yang mengandung salah satu dari dua kata" yang berarti bahwa setiap baris harus dievaluasi sendiri. Saya tidak mengerti mengapa Anda merasa ini tidak menjawab pertanyaan. Harap berikan contoh input yang Anda rasa akan gagal.
Zhro

Oh, itukah yang kamu maksud? “Baca input satu baris pada satu waktu dan laksanakan dua atau tiga perintah ini untuk setiap baris . " (1) Sangat tidak jelas bahwa itulah yang Anda maksudkan. (2) Sangat tidak efisien. Empat jawaban sebelum Anda menunjukkan bagaimana menangani seluruh file dalam beberapa perintah (satu, dua atau empat), dan Anda ingin menjalankan 3 ×  n perintah untuk n baris input? Bahkan jika itu berhasil, itu menghasilkan suara turun untuk eksekusi mahal yang tidak perlu. (3) Dengan risiko rambut rontok, masih tidak berfungsi menunjukkan garis yang tepat.
G-Man Mengatakan 'Reinstate Monica'
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.