Regex pergantian / atau operator (foo | bar) di GNU atau BSD Sed

28

Sepertinya saya tidak bisa membuatnya bekerja. Dokumentasi sed GNU mengatakan untuk melarikan diri dari pipa, tetapi itu tidak berhasil, juga tidak menggunakan pipa lurus tanpa melarikan diri. Menambahkan parens tidak ada bedanya.

$ echo 'cat
dog
pear
banana
cat
dog' | sed 's/cat|dog/Bear/g'
cat
dog
pear
banana
cat
dog

$ echo 'cat
dog
pear
banana
cat
dog' | sed 's/cat\|dog/Bear/g'
cat
dog
pear
banana
cat
dog
Gregg Leventhal
sumber

Jawaban:

33

Secara defaultsed menggunakan POSIX Basic Regular Expressions , yang tidak termasuk |operator alternatif. Banyak versi sed, termasuk GNU dan FreeBSD, mendukung peralihan ke Ekspresi Reguler Diperpanjang , yang menyertakan |pergantian. Bagaimana Anda melakukannya bervariasi: GNU menggunakan sed-r , sedangkan FreeBSD , NetBSD , OpenBSD , dan OS X menggunakan sed-E . Versi lain kebanyakan tidak mendukung sama sekali. Anda dapat gunakan:

echo 'cat dog pear banana cat dog' | sed -E -e 's/cat|dog/Bear/g'

dan itu akan bekerja pada sistem BSD itu, dan sed -rdengan GNU.


GNU sedtampaknya memiliki dukungan yang sepenuhnya tidak berdokumen tetapi berfungsi -E, jadi jika Anda memiliki skrip multi-platform yang terbatas pada di atas, itu adalah pilihan terbaik Anda. Karena tidak didokumentasikan, Anda mungkin tidak dapat benar-benar mengandalkannya.

Sebuah komentar mencatat bahwa versi BSD juga mendukung -rsebagai alias tanpa dokumen. OS X masih tidak hari ini dan mesin NetBSD dan OpenBSD yang lebih tua saya juga tidak memiliki akses, tetapi NetBSD 6.1 yang satu tidak. Unites komersial yang bisa saya jangkau secara universal tidak. Jadi dengan semua pertanyaan portabilitas menjadi cukup rumit pada saat ini, tetapi jawaban sederhana adalah beralih keawk jika Anda membutuhkannya, yang menggunakan ERE di mana-mana.

Michael Homer
sumber
Tiga BSD yang Anda sebutkan semua dukungan -ropsi sebagai sinonim dari -Euntuk kompatibilitas dengan GNU sed. OpenBSD dan OS X sed -Eakan menafsirkan pipa yang lolos sebagai pipa literal, bukan sebagai operator bergantian. Inilah tautan yang berfungsi ke halaman manual NetBSD dan ini satu untuk OpenBSD yang belum berusia sepuluh tahun.
damien
9

Ini terjadi karena (a|b)merupakan ekspresi reguler yang diperluas, bukan Ekspresi Reguler Dasar. Gunakan -Eopsi untuk menangani ini.

echo 'cat
dog
pear
banana
cat
dog'|sed -E 's/cat|dog/Bear/g'

Dari sedhalaman manual:

 -E      Interpret regular expressions as extended (modern) regular
         expressions rather than basic regular expressions (BRE's).

Perhatikan bahwa -rini adalah flag lain untuk hal yang sama, tetapi -Elebih portabel dan bahkan akan ada dalam versi berikutnya dari spesifikasi POSIX.

Networker
sumber
6

Cara portabel untuk melakukan ini - dan cara yang lebih efisien - adalah dengan alamat. Kamu bisa melakukan ini:

printf %s\\n cat dog pear banana cat dog |
sed -e '/cat/!{/dog/!b' -e '};cBear'

Dengan cara ini jika baris tidak mengandung cat string dan tidak berisi string dog sed b ranches keluar dari skrip, autoprint baris saat ini dan menarik yang berikutnya untuk memulai siklus berikutnya. Karena itu tidak melakukan instruksi selanjutnya - yang dalam contoh inic menggantung seluruh baris untuk membaca Bear tetapi bisa melakukan apa saja.

Ini mungkin perlu dicatat juga bahwa pernyataan apapun menyusul !bdalam sedperintah dapat hanya cocok pada baris yang berisi baik stringdog atau cat- sehingga Anda dapat melakukan tes lebih lanjut tanpa bahaya pencocokan garis yang tidak - yang berarti Anda sekarang dapat menerapkan aturan hanya satu atau yang lain juga.

Tapi selanjutnya. Ini output dari perintah di atas:

###OUTPUT###
Bear
Bear
pear
banana
Bear
Bear

Anda juga dapat mengimplementasikan tabel pencarian dengan referensi belakang dengan mudah.

printf %s\\n cat dog pear banana cat dog |
sed '1{x;s/^/ cat dog /;x
};G;s/^\(.*\)\n.* \1 .*/Bear/;P;d'

Lebih banyak pekerjaan yang harus disiapkan untuk contoh kasus sederhana ini, tetapi dapat membuatnya lebih fleksibel sed skrip yang dalam jangka panjang.

Pada baris pertama saya xmengubah ruang pegang dan pola ruang kemudian masukkan string <space>cat <space>dog<space> ke ruang pegang sebelum kita xmengubahnya kembali.

Sejak saat itu dan pada setiap baris berikut saya Gtahan ruang ditambahkan ke ruang pola, lalu periksa untuk melihat apakah semua karakter dari awal baris sampai baris baru yang saya tambahkan pada akhirnya cocok dengan string yang dikelilingi oleh spasi setelahnya. Jika demikian, saya mengganti seluruh lot dengan Bear dan jika tidak, tidak ada salahnya dilakukan karena saya Phanya akan memotong hingga baris pertama yang terjadi pertama kali di ruang pola kemudian dhapus semuanya.

###OUTPUT###
Bear
Bear
pear
banana
Bear
Bear

Dan ketika saya mengatakan fleksibel, saya sungguh-sungguh. Ini dia ganti kucing dengan BrownBear dan anjing dengan BlackBear :

printf %s\\n cat dog pear banana cat dog |
sed '1{x;s/^/ 1cat Brown 2dog Black /;x
};G;s/^\(.*\)\n.* [0-9]\1 \([^ ]*\) .*/\2Bear/;P;d'

###OUTPUT###
BrownBear
BlackBear
pear
banana
BrownBear
BlackBear

Anda tentu saja dapat memperluas banyak hal pada isi tabel pencarian - saya mengambil ide dari email usenet Greg Ubben tentang masalah tersebut ketika, pada tahun 90-an, dia menggambarkan bagaimana dia membuat kalkulator kasar dari satu sed s///pernyataan.

mikeserv
sumber
1
Fiuh, +1. Anda memiliki kegemaran untuk berpikir di luar kotak yang harus saya katakan
iruvar
@ 1_CR - Lihat hasil edit terakhir saya - bukan ide saya - yang bukan untuk mengatakan bahwa saya tidak menghargai itu dan menganggapnya sebagai pujian. Tapi saya suka memberi kredit di tempat yang seharusnya.
mikeserv
1

ini adalah pertanyaan yang cukup lama, tetapi jika seseorang ingin mencoba, ada upaya yang cukup rendah untuk melakukan ini sed dengan file sed. Setiap opsi dapat didaftar pada baris terpisah, dan sed akan mengevaluasi masing-masing. Ini setara dengan logis atau. Misalnya, untuk menghapus baris yang berisi kode tertentu:

Anda bisa mengatakan: sed -E '/^\/\*!(40103|40101|40111).*\/;$/d'

atau letakkan ini di file sed Anda:

/^\/\*!40103.*\/;$/d
/^\/\*!40101.*\/;$/d
/^\/\*!40111.*\/;$/d
Mordechai
sumber
0

Berikut adalah teknik yang tidak menggunakan opsi khusus implementasi apa pun untuk sed(misalnya -E, -r). Alih-alih menggambarkan pola sebagai satu regex cat|dog, kita cukup menjalankan seddua kali:

echo 'cat
dog
pear
banana
cat
dog' | sed 's/cat/Bear/g' | sed 's/dog/Bear/g'

Benar-benar solusi yang tepat, tetapi layak untuk dibagikan. Ini secara umum digeneralisasikan ke lebih dari dua string pola, meskipun rantai yang sangat panjang sedtidak terlalu bagus.

Saya sering menggunakan sed -i(yang berfungsi sama di semua implementasi) untuk membuat perubahan pada file. Di sini, daftar panjang string pola dapat dimasukkan dengan baik, karena setiap hasil sementara disimpan ke file:

for pattern in cat dog owl; do
    sed -i "s/${pattern}/Bear/g" myfile
done
jmd_dk
sumber