Menangkap Grup Dari Grep RegEx

380

Saya punya skrip kecil ini di sh(Mac OSX 10.6) untuk melihat melalui array file. Google telah berhenti membantu pada saat ini:

files="*.jpg"
for f in $files
    do
        echo $f | grep -oEi '[0-9]+_([a-z]+)_[0-9a-z]*'
        name=$?
        echo $name
    done

Sejauh ini (jelas, bagi Anda shell guru) $namehanya memegang 0, 1 atau 2, tergantung pada apakah grepditemukan bahwa nama file cocok dengan masalah yang diberikan. Yang saya suka adalah untuk menangkap apa yang ada di dalam parens ([a-z]+)dan menyimpannya ke variabel .

Saya hanya ingin menggunakan grep, jika memungkinkan . Jika tidak, tolong jangan Python atau Perl, dll. sedAtau semacamnya - Saya baru mengenal shell dan ingin menyerang ini dari sudut * nix purist.

Juga, sebagai bonu super keren , saya ingin tahu bagaimana saya bisa menggabungkan string dalam shell? Apakah grup yang saya tangkap adalah string "somename" yang disimpan dalam $ name, dan saya ingin menambahkan string ".jpg" ke akhir, benarkan cat $name '.jpg'?

Tolong jelaskan apa yang terjadi, jika Anda punya waktu.

Ishak
sumber
30
Apakah grep benar-benar lebih murni dari pada sed?
martin clayton
3
Ah, tidak bermaksud menyarankan itu. Saya hanya berharap bahwa sebuah solusi dapat ditemukan menggunakan alat yang secara khusus saya coba pelajari di sini. Jika tidak mungkin diselesaikan dengan menggunakan grep, maka sedakan lebih bagus, jika mungkin untuk menyelesaikan menggunakan sed.
Isaac
2
Saya harus telah menempatkan :) pada yang btw ...
martin clayton
Psh, otakku terlalu goreng hari ini haha.
Isaac
2
@martinclayton Itu akan menjadi argumen yang menarik. Saya benar-benar berpikir sed, (atau ed lebih tepatnya) akan lebih tua (dan karena itu lebih murni? Mungkin?) Unix karena grep mendapatkan namanya dari ed ekspresi g (lobal) / re (ekspresi gular) / p (rint).
meraba

Jawaban:

500

Jika Anda menggunakan Bash, Anda bahkan tidak perlu menggunakan grep:

files="*.jpg"
regex="[0-9]+_([a-z]+)_[0-9a-z]*"
for f in $files    # unquoted in order to allow the glob to expand
do
    if [[ $f =~ $regex ]]
    then
        name="${BASH_REMATCH[1]}"
        echo "${name}.jpg"    # concatenate strings
        name="${name}.jpg"    # same thing stored in a variable
    else
        echo "$f doesn't match" >&2 # this could get noisy if there are a lot of non-matching files
    fi
done

Lebih baik menempatkan regex dalam sebuah variabel. Beberapa pola tidak akan berfungsi jika dimasukkan secara harfiah.

Ini menggunakan =~yang merupakan operator pertandingan regex Bash. Hasil pertandingan disimpan ke array yang disebut $BASH_REMATCH. Grup tangkapan pertama disimpan dalam indeks 1, yang kedua (jika ada) dalam indeks 2, dll. Indeks nol adalah kecocokan penuh.

Anda harus menyadari bahwa tanpa jangkar, regex ini (dan yang menggunakan grep) akan cocok dengan salah satu contoh berikut dan lebih banyak lagi, yang mungkin bukan yang Anda cari:

123_abc_d4e5
xyz123_abc_d4e5
123_abc_d4e5.xyz
xyz123_abc_d4e5.xyz

Untuk menghilangkan contoh kedua dan keempat, buat regex Anda seperti ini:

^[0-9]+_([a-z]+)_[0-9a-z]*

yang mengatakan string harus dimulai dengan satu digit atau lebih. Karat mewakili awal dari string. Jika Anda menambahkan tanda dolar di akhir regex, seperti ini:

^[0-9]+_([a-z]+)_[0-9a-z]*$

maka contoh ketiga juga akan dihilangkan karena titik tidak ada di antara karakter di regex dan tanda dolar mewakili akhir dari string. Perhatikan bahwa contoh keempat juga gagal dalam pertandingan ini.

Jika Anda memiliki GNU grep(sekitar 2,5 atau lebih baru, saya pikir, ketika \Koperator ditambahkan):

name=$(echo "$f" | grep -Po '(?i)[0-9]+_\K[a-z]+(?=_[0-9a-z]*)').jpg

The \Koperator (variable-length lihat-balik) menyebabkan pola sebelumnya untuk pertandingan, tetapi tidak termasuk pertandingan dalam hasil. Persamaan panjang tetap adalah (?<=)- pola akan dimasukkan sebelum tanda kurung tutup. Anda harus menggunakan \Kjika bilangan dapat mencocokkan string panjang yang berbeda (misalnya +, *, {2,4}).

The (?=)pertandingan operator fixed atau pola variabel-panjang dan disebut "tampak-depan". Itu juga tidak termasuk string yang cocok dalam hasil.

Untuk membuat korek api tidak peka, (?i)operator digunakan. Ini mempengaruhi pola yang mengikutinya sehingga posisinya signifikan.

Regex mungkin perlu disesuaikan tergantung pada apakah ada karakter lain dalam nama file. Anda akan perhatikan bahwa dalam kasus ini, saya menunjukkan contoh menggabungkan string pada saat yang sama ketika substring ditangkap.

Dijeda sampai pemberitahuan lebih lanjut.
sumber
48
Dalam jawaban ini saya ingin mengungguli baris tertentu yang mengatakan, "Lebih baik menempatkan regex dalam variabel. Beberapa pola tidak akan berfungsi jika dimasukkan secara harfiah."
Brandin
5
@FrancescoFrassinelli: Contohnya adalah pola yang menyertakan ruang putih. Ini canggung untuk melarikan diri dan Anda tidak dapat menggunakan tanda kutip karena itu memaksa dari regex ke string biasa. Cara yang benar untuk melakukannya adalah dengan menggunakan variabel. Kutipan dapat digunakan selama penugasan membuat segalanya lebih sederhana.
Dijeda sampai pemberitahuan lebih lanjut.
5
/Kbatu operator.
razz
2
@ Brandon: Ini berhasil. Versi Bash apa yang Anda gunakan? Tunjukkan pada saya apa yang Anda lakukan itu tidak berhasil dan mungkin saya bisa memberi tahu Anda alasannya.
Dijeda sampai pemberitahuan lebih lanjut.
2
@mdelolmo: Jawaban saya mencakup informasi tentang grep. Itu juga diterima oleh OP dan cukup banyak dipilih. Terima kasih untuk downvote.
Dijeda sampai pemberitahuan lebih lanjut.
145

Ini tidak mungkin dengan murni grep, setidaknya tidak secara umum.

Tetapi jika pola Anda cocok, Anda mungkin dapat menggunakan grepbeberapa kali dalam pipa untuk mengurangi garis Anda menjadi format yang dikenal, dan kemudian mengekstrak hanya sedikit yang Anda inginkan. (Meskipun alat suka cutdan sedjauh lebih baik dalam hal ini).

Misalkan demi argumen bahwa pola Anda sedikit lebih sederhana: [0-9]+_([a-z]+)_Anda dapat mengekstraksi seperti ini:

echo $name | grep -Ei '[0-9]+_[a-z]+_' | grep -oEi '[a-z]+'

Yang pertama grepakan menghapus baris apa pun yang tidak cocok dengan pola keseluruhan Anda, yang kedua grep(yang telah --only-matchingditentukan) akan menampilkan bagian alfa nama. Ini hanya berfungsi karena polanya cocok: "porsi alfa" cukup spesifik untuk mengeluarkan apa yang Anda inginkan.

(Selain: Secara pribadi saya akan menggunakan grep+ cutuntuk mencapai apa yang Anda inginkan:. echo $name | grep {pattern} | cut -d _ -f 2Ini akan cutmenguraikan baris ke dalam bidang dengan memisahkan pada pembatas _, dan mengembalikan hanya bidang 2 (angka bidang mulai dari 1)).

Filosofi Unix adalah memiliki alat yang melakukan satu hal, dan melakukannya dengan baik, dan menggabungkannya untuk mencapai tugas-tugas non-sepele, jadi saya berpendapat bahwa grep+ sedetc adalah cara yang lebih Unixy dalam melakukan sesuatu :-)

RobM
sumber
3
for f in $files; do name=echo $ f | grep -oEi '[0-9] + _ ([az] +) _ [0-9a-z] *' | cut -d _ -f 2 ;Aha!
Isaac
2
Saya tidak setuju dengan "filosofi" itu. jika Anda dapat menggunakan kemampuan bawaan shell tanpa memanggil perintah eksternal, maka skrip Anda akan jauh lebih cepat dalam kinerjanya. ada beberapa alat yang tumpang tindih fungsinya. misalnya grep dan sed dan awk. mereka semua melakukan manipulasi string, tetapi awk menonjol di atas mereka semua karena dapat melakukan lebih banyak lagi. Secara praktis, semua chaining perintah, seperti grep ganda di atas atau grep + sed dapat dipersingkat dengan melakukannya dengan satu proses awk.
ghostdog74
7
@ ghostdog74: Tidak ada argumen di sini bahwa rantai banyak operasi kecil bersama-sama umumnya kurang efisien daripada melakukan semuanya di satu tempat, tapi saya berdiri di atas pernyataan saya bahwa filosofi Unix adalah banyak alat yang bekerja bersama. Sebagai contoh, tar hanya mengarsipkan file, itu tidak mengkompresnya, dan karena itu output ke STDOUT secara default Anda dapat pipa itu di jaringan dengan netcat, atau kompres dengan bzip2, dll. Yang menurut saya memperkuat konvensi dan umum etos bahwa alat Unix harus dapat bekerja bersama dalam pipa.
RobM
cut is awesome - terima kasih atas tipnya! Adapun argumen alat vs efisiensi, saya suka kesederhanaan alat chaining.
ether_joe
alat peraga untuk opsi o grep, itu sangat membantu
chiliNUT
96

Saya menyadari bahwa jawaban sudah diterima untuk ini, tetapi dari "sudut pandang murni * nix" sepertinya alat yang tepat untuk pekerjaan itu pcregrep, yang sepertinya belum disebutkan. Coba ubah garis:

    echo $f | grep -oEi '[0-9]+_([a-z]+)_[0-9a-z]*'
    name=$?

sebagai berikut:

    name=$(echo $f | pcregrep -o1 -Ei '[0-9]+_([a-z]+)_[0-9a-z]*')

untuk mendapatkan hanya konten dari grup penangkap 1.

The pcregrepAlat memanfaatkan semua sintaks yang sama yang telah digunakan dengan grep, tapi alat fungsi yang Anda butuhkan.

Parameter -oberfungsi seperti grepversi jika telanjang, tetapi juga menerima parameter numerik pcregrep, yang menunjukkan grup tangkapan mana yang ingin Anda tampilkan.

Dengan solusi ini ada minimal perubahan yang diperlukan dalam skrip. Anda cukup mengganti satu utilitas modular dengan yang lain dan mengubah parameternya.

Catatan Menarik: Anda dapat menggunakan argumen -o multipel untuk mengembalikan beberapa grup tangkapan sesuai urutan yang ditampilkan di telepon.

John Sherwood
sumber
3
pcregreptidak tersedia secara default Mac OS Xyang digunakan OP
grebneke
4
pcregrepTampaknya saya tidak mengerti digit setelah -o: "Huruf opsi tidak dikenal '1' dalam" -o1 ". Juga tidak disebutkan fungsi itu ketika melihatpcregrep --help
Peter Herdenborg
1
@ WAF maaf, saya kira saya harus memasukkan info itu dalam komentar saya. Aku di Centos 6.5 dan versi pcregrep tampaknya sangat tua: 7.8 2008-09-05.
Peter Herdenborg
2
ya, sangat membantu, misalnyaecho 'r123456 foo 2016-03-17' | pcregrep -o1 'r([0-9]+)' 123456
zhuguowei
5
pcregrep8.41 (diinstal dengan apt-get install pcregrepaktif Ubuntu 16.03) tidak mengenali -Eisakelar. Ini bekerja sempurna tanpanya. Pada macOS, dengan pcregrepdiinstal via homebrew(juga 8,41) seperti @anishpatel menyebutkan di atas, setidaknya pada High Sierra -Esaklar juga tidak dikenali.
Ville
27

Tidak mungkin hanya dalam grep saya percaya

untuk sed:

name=`echo $f | sed -E 's/([0-9]+_([a-z]+)_[0-9a-z]*)|.*/\2/'`

Saya akan coba bonus ini:

echo "$name.jpg"
cobbal
sumber
2
Sayangnya, sedsolusi itu tidak berhasil. Itu hanya mencetak semua yang ada di direktori saya.
Isaac
diperbarui, akan menampilkan baris kosong jika tidak ada kecocokan, jadi pastikan untuk memeriksanya
cobbal
Sekarang hanya menghasilkan garis kosong!
Isaac
sed ini punya masalah. Kelompok pertama yang menangkap kurung mencakup segalanya. Tentu saja \ 2 tidak akan memiliki apa-apa.
ghostdog74
itu bekerja untuk beberapa kasus uji sederhana ... \ 2 mendapat kelompok batin
cobbal
16

Ini adalah solusi yang menggunakan gawk. Ini adalah sesuatu yang saya pikir perlu saya gunakan berulang kali jadi saya membuat fungsi untuk itu

function regex1 { gawk 'match($0,/'$1'/, ary) {print ary['${2:-'1'}']}'; }

untuk menggunakan lakukan

$ echo 'hello world' | regex1 'hello\s(.*)'
world
opsb
sumber
Ide bagus, tetapi tampaknya tidak bekerja dengan spasi di regexp - perlu diganti \s. Apakah Anda tahu cara memperbaikinya?
Adam Ryczkowski
4

Saran untuk Anda - Anda dapat menggunakan ekspansi parameter untuk menghapus bagian nama dari garis bawah terakhir dan seterusnya, dan demikian pula di awal:

f=001_abc_0za.jpg
work=${f%_*}
name=${work#*_}

Maka nameakan memiliki nilai abc.

Lihat dokumen pengembang Apple , cari 'Ekspansi Parameter'.

martin clayton
sumber
ini tidak akan memeriksa ([az] +).
ghostdog74
@levislevis - itu benar, tetapi, seperti dikomentari oleh OP, ia melakukan apa yang diperlukan.
martin clayton
2

jika Anda memiliki bash, Anda dapat menggunakan globbing yang diperluas

shopt -s extglob
shopt -s nullglob
shopt -s nocaseglob
for file in +([0-9])_+([a-z])_+([a-z0-9]).jpg
do
   IFS="_"
   set -- $file
   echo "This is your captured output : $2"
done

atau

ls +([0-9])_+([a-z])_+([a-z0-9]).jpg | while read file
do
   IFS="_"
   set -- $file
   echo "This is your captured output : $2"
done
ghostdog74
sumber
Itu terlihat menarik. Bisakah Anda menambahkan sedikit penjelasan untuk itu? Atau, jika Anda cenderung, tautkan ke sumber daya khusus yang menjelaskannya? Terima kasih!
Isaac