Mengekstrak regex yang cocok dengan 'sed' tanpa mencetak karakter di sekitarnya

24

Untuk semua dokter 'sed' di luar sana:

Bagaimana Anda bisa mendapatkan 'sed' untuk mengekstraksi ekspresi reguler yang cocok dengan garis?

Dengan kata lain, saya ingin hanya string yang sesuai dengan ekspresi reguler dengan semua karakter yang tidak cocok dari garis yang mengandung dilucuti.

Saya mencoba menggunakan fitur referensi belakang seperti di bawah ini

regular expression to be isolated 
         gets `inserted` 
              here     
               |
               v  
 sed -n 's/.*\( \).*/\1/p 

ini berfungsi untuk beberapa ekspresi seperti

 sed -n 's/.*\(CONFIG_[a-zA-Z0-9_]*\).*/\1/p 

yang dengan rapi mengekstrak semua nama makro yang dimulai dengan 'CONFIG_ ....' (ditemukan dalam beberapa file '* .h') dan mencetak semuanya keluar baris demi baris

          CONFIG_AT91_GPIO
          CONFIG_DRIVER_AT91EMAC
                   .
                   .   
          CONFIG_USB_ATMEL
          CONFIG_USB_OHCI_NEW
                   .
                 e.t.c. 

TAPI di atas rusak untuk sesuatu seperti

  sed -n 's/.*\([0-9][0-9]*\).*/\1/p 

ini selalu mengembalikan satu digit seperti

                 7
                 9
                 .
                 .  
                 6

daripada mengekstraksi bidang angka yang berdekatan seperti.

              8908078
              89670890  
                 .
                 .  
                 .
               23019   
                 .
               e.t.c.  

PS: Saya akan berterima kasih atas umpan balik tentang bagaimana ini dicapai dalam 'sed'. Saya tahu bagaimana melakukan ini dengan 'grep' dan 'awk' saya ingin mengetahui apakah pemahaman saya - walaupun terbatas - tentang 'sed' memiliki lubang di dalamnya dan jika ada cara untuk melakukan ini dalam 'sed' yang saya
miliki hanya diabaikan.

darbehdar
sumber

Jawaban:

22

Ketika regexp berisi grup, mungkin ada lebih dari satu cara untuk mencocokkan string terhadapnya: regexps dengan grup bersifat mendua. Sebagai contoh, perhatikan regexp ^.*\([0-9][0-9]*\)$dan string a12. Ada dua kemungkinan:

  • Pertandingan amelawan .*dan 2melawan [0-9]*; 1cocok dengan [0-9].
  • Cocok a1melawan .*dan string kosong melawan [0-9]*; 2cocok dengan [0-9].

Sed, seperti semua alat regexp lainnya di luar sana, menerapkan aturan kecocokan terlama yang paling awal: pertama-tama mencoba untuk mencocokkan bagian panjang variabel pertama terhadap string yang selama mungkin. Jika menemukan cara untuk mencocokkan sisa string dengan sisa regexp, baik-baik saja. Jika tidak, sed mencoba pencocokan terpanjang berikutnya untuk bagian panjang variabel pertama dan mencoba lagi.

Di sini, pertandingan dengan string terlama pertama adalah a1melawan .*, jadi grup hanya cocok 2. Jika Anda ingin grup memulai lebih awal, beberapa mesin regexp memungkinkan Anda membuat yang .*kurang serakah, tetapi tidak memiliki fitur seperti itu. Jadi, Anda perlu menghapus ambiguitas dengan beberapa jangkar tambahan. Tentukan bahwa pemimpin .*tidak dapat diakhiri dengan digit, sehingga digit pertama grup adalah kecocokan pertama yang mungkin.

  • Jika kelompok angka tidak boleh berada di awal baris:

    sed -n 's/^.*[^0-9]\([0-9][0-9]*\).*/\1/p'
    
  • Jika kelompok angka bisa di awal garis, dan sed Anda mendukung \?operator untuk suku cadang opsional:

    sed -n 's/^\(.*[^0-9]\)\?\([0-9][0-9]*\).*/\1/p'
    
  • Jika kelompok angka dapat berada di awal garis, tetap pada konstruksi regexp standar:

    sed -n -e 's/^.*[^0-9]\([0-9][0-9]*\).*/\1/p' -e t -e 's/^\([0-9][0-9]*\).*/\1/p'
    

Ngomong-ngomong, aturan pertandingan terlama yang sama yang paling awal yang membuat [0-9]*cocok dengan digit setelah yang pertama, bukan yang berikutnya .*.

Perhatikan bahwa jika ada beberapa urutan angka pada satu baris, program Anda akan selalu mengekstrak urutan angka terakhir, sekali lagi karena aturan kecocokan terlama yang diterapkan pada inisial .*. Jika Anda ingin mengekstrak urutan angka pertama, Anda perlu menentukan bahwa apa yang datang sebelumnya adalah urutan angka non-digit.

sed -n 's/^[^0-9]*\([0-9][0-9]*\).*$/\1/p'

Secara umum, untuk mengekstrak kecocokan pertama dari regexp, Anda perlu menghitung negasi dari regexp tersebut. Walaupun ini selalu memungkinkan secara teoritis, ukuran negasi tumbuh secara eksponensial dengan ukuran regexp yang Anda negasikan, jadi ini seringkali tidak praktis.

Pertimbangkan contoh Anda yang lain:

sed -n 's/.*\(CONFIG_[a-zA-Z0-9_]*\).*/\1/p'

Contoh ini sebenarnya menunjukkan masalah yang sama, tetapi Anda tidak melihatnya pada input biasa. Jika Anda memberinya makan hello CONFIG_FOO_CONFIG_BAR, maka perintah di atas dicetak CONFIG_BAR, tidak CONFIG_FOO_CONFIG_BAR.

Ada cara untuk mencetak pertandingan pertama dengan sed, tetapi sedikit rumit:

sed -n -e 's/\(CONFIG_[a-zA-Z0-9_]*\).*/\n\1/' -e T -e 's/^.*\n//' -e p

(Dengan asumsi sed Anda mendukung \nberarti baris baru di steks pengganti.) Ini berfungsi karena sed mencari pertandingan regexp yang paling awal, dan kami tidak mencoba untuk mencocokkan apa yang mendahului CONFIG_…bit. Karena tidak ada baris baru di dalam baris, kita dapat menggunakannya sebagai penanda sementara. The Tperintah mengatakan menyerah jika sebelumnya sperintah tidak cocok.

Ketika Anda tidak tahu bagaimana melakukan sesuatu dalam sed, beralihlah ke awk. Perintah berikut mencetak pencocokan terlama dari regexp:

awk 'match($0, /[0-9]+/) {print substr($0, RSTART, RLENGTH)}'

Dan jika Anda ingin menjaganya tetap sederhana, gunakan Perl.

perl -l -ne '/[0-9]+/ && print $&'       # first match
perl -l -ne '/^.*([0-9]+)/ && print $1'  # last match
Gilles 'SANGAT berhenti menjadi jahat'
sumber
22

Meskipun tidak sed, salah satu hal yang sering diabaikan untuk ini adalah grep -o, yang menurut saya adalah alat yang lebih baik untuk tugas ini.

Misalnya, jika Anda ingin mendapatkan semua CONFIG_parameter dari konfigurasi kernel, Anda akan menggunakan:

# grep -Eo 'CONFIG_[A-Z0-9_]+' config
CONFIG_64BIT
CONFIG_X86_64
CONFIG_X86
CONFIG_INSTRUCTION_DECODER
CONFIG_OUTPUT_FORMAT

Jika Anda ingin mendapatkan urutan angka yang berdekatan:

$ grep -Eo '[0-9]+' foo
Patrick
sumber
7
sed '/\n/P;//!s/[0-9]\{1,\}/\n&\n/;D'

... akan melakukan ini tanpa ribut-ribut, meskipun Anda mungkin memerlukan baris baru literal sebagai ganti nhuruf s di bidang substitusi kanan. Dan, omong-omong, .*CONFIGmasalahnya hanya akan bekerja jika hanya ada satu pertandingan di telepon - itu akan selalu hanya mendapatkan yang terakhir.

Anda dapat melihat ini untuk deskripsi cara kerjanya, tetapi ini akan mencetak pada baris yang terpisah hanya kecocokan sebanyak yang terjadi pada suatu baris.

Anda dapat menggunakan strategi yang sama untuk mendapatkan [num]kemunculannya pada satu baris. Misalnya, jika Anda ingin mencetak kecocokan CONFIG hanya jika itu yang ketiga seperti pada baris:

sed '/\n/P;//d;s/CONFIG[[:alnum:]]*/\n&\n/3;D'

... meskipun itu mengasumsikan CONFIGstring dipisahkan oleh setidaknya satu karakter non-alfanumerik untuk setiap kejadian.

Saya kira - untuk hal nomor - ini juga akan berfungsi:

sed -n 's/[^0-9]\{1,\}/\n/g;s/\n*\(.*[0-9]\).*/\1/p

... dengan peringatan yang sama seperti sebelumnya tentang tangan kanan \n. Yang ini bahkan akan lebih cepat dari yang pertama, tetapi jelas tidak bisa berlaku seperti umumnya.

Untuk hal CONFIG Anda bisa menggunakan P;...;Dloop di atas dengan pola Anda, atau Anda bisa melakukan

sed -n 's/[^C]*\(CONFIG[[:alnum:]]*\)\{0,1\}C\{0,1\}/\1\n/g;s/\(\n\)*/\1/g;/C/s/.$//p'

... yang hanya sedikit lebih terlibat dan berfungsi dengan benar memesan sedprioritas referensi. Itu juga mengisolasi semua pertandingan CONFIG dalam satu baris sekaligus - walaupun itu membuat asumsi yang sama seperti sebelumnya - bahwa setiap pertandingan CONFIG akan dipisahkan oleh setidaknya satu karakter non-alfanumerik. Dengan GNU sedAnda bisa menulisnya:

sed -En 's/[^C]*(CONFIG\w*)?C?/\1\n/g;s/(\n)*/\1/g;/C/s/.$//p'
mikeserv
sumber