bagaimana cara menggunakan sed, awk, atau gawk untuk mencetak hanya yang cocok?

100

Saya melihat banyak contoh dan halaman manual tentang bagaimana melakukan hal-hal seperti cari-dan-ganti menggunakan sed, awk, atau gawk.

Namun dalam kasus saya, saya memiliki ekspresi reguler yang ingin saya jalankan pada file teks untuk mengekstrak nilai tertentu. Saya tidak ingin melakukan pencarian-dan-ganti. Ini dipanggil dari bash. Mari gunakan contoh:

Contoh ekspresi reguler:

.*abc([0-9]+)xyz.*

Contoh file masukan:

a
b
c
abc12345xyz
a
b
c

Sesederhana kedengarannya, saya tidak tahu cara memanggil sed / awk / gawk dengan benar. Apa yang saya ingin lakukan, adalah dari dalam skrip bash saya:

myvalue=$( sed <...something...> input.txt )

Hal-hal yang saya coba termasuk:

sed -e 's/.*([0-9]).*/\\1/g' example.txt # extracts the entire input file
sed -n 's/.*([0-9]).*/\\1/g' example.txt # extracts nothing
Stéphane
sumber
10
Wow ... orang-orang memilih pertanyaan ini turun -1? Apakah itu benar-benar pertanyaan yang tidak pantas?
Stéphane
Tampaknya sangat tepat, menggunakan Regex dan utilitas baris perintah yang kuat seperti sed / awk atau editor apa pun seperti vi, emacs atau teco bisa lebih seperti pemrograman daripada hanya menggunakan beberapa aplikasi lama. IMO ini milik SO lebih dari SU.
dirilis
Mungkin itu ditolak karena dalam bentuk awalnya tidak secara jelas mendefinisikan beberapa persyaratannya. Masih tidak, kecuali Anda membaca komentar OP untuk jawaban (termasuk yang saya hapus ketika semuanya berbentuk buah pir).
pavium

Jawaban:

42

Saya sed(Mac OS X) tidak bekerja dengan +. Saya mencoba *sebagai gantinya dan saya menambahkan ptag untuk pertandingan pencetakan:

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

Untuk mencocokkan setidaknya satu karakter numerik tanpa +, saya akan menggunakan:

sed -n 's/^.*abc\([0-9][0-9]*\)xyz.*$/\1/p' example.txt
mouviciel
sumber
Terima kasih, ini juga berhasil untuk saya setelah saya menggunakan *, bukan +.
Stéphane
2
... dan opsi "p" untuk mencetak pertandingan, yang juga tidak saya ketahui. Terima kasih lagi.
Stéphane
2
Saya harus melarikan diri +dan kemudian berhasil untuk saya:sed -n 's/^.*abc\([0-9]\+\)xyz.*$/\1/p'
Dijeda sampai pemberitahuan lebih lanjut.
3
Itu karena Anda tidak menggunakan format RE modern oleh karena itu + adalah karakter standar dan Anda seharusnya mengekspresikannya dengan sintaks {,}. Anda dapat menambahkan opsi use -E sed untuk memicu format RE modern. Periksa re_format (7), khususnya paragraf terakhir DESCRIPTION developer.apple.com/library/mac/#documentation/Darwin/Reference/…
anddam
33

Anda dapat menggunakan sed untuk melakukan ini

 sed -rn 's/.*abc([0-9]+)xyz.*/\1/gp'
  • -n jangan mencetak baris yang dihasilkan
  • -rini membuatnya jadi Anda tidak memiliki jalan keluar dari kelompok tangkap parens ().
  • \1 pertandingan grup tangkap
  • /g pertandingan global
  • /p cetak hasilnya

Saya menulis alat untuk diri saya sendiri yang membuat ini lebih mudah

rip 'abc(\d+)xyz' '$1'
Ilia Choly
sumber
3
Sejauh ini, ini adalah jawaban terbaik dan paling banyak dijelaskan!
Nik Reiman
Dengan beberapa penjelasan, jauh lebih baik untuk memahami apa yang salah dengan masalah kita. Terima kasih !
r4phG
17

Saya biasa perlmembuat ini lebih mudah bagi diri saya sendiri. misalnya

perl -ne 'print $1 if /.*abc([0-9]+)xyz.*/'

Ini menjalankan Perl, -nopsi memerintahkan Perl untuk membaca dalam satu baris pada satu waktu dari STDIN dan mengeksekusi kode. The -epilihan menentukan instruksi untuk menjalankan.

Instruksi menjalankan regexp pada baris yang dibaca, dan jika cocok mencetak konten set bracks ( $1) pertama.

Anda juga dapat melakukan ini dengan beberapa nama file di akhir. misalnya

perl -ne 'print $1 if /.*abc([0-9]+)xyz.*/' example1.txt example2.txt

PP.
sumber
Terima kasih, tetapi kami tidak memiliki akses ke perl, itulah sebabnya saya bertanya tentang sed / awk / gawk.
Stéphane
5

Jika versi Anda grepmendukungnya, Anda dapat menggunakan -oopsi untuk mencetak hanya sebagian dari baris yang cocok dengan regexp Anda.

Jika tidak maka inilah yang terbaik yang sedbisa saya hasilkan:

sed -e '/[0-9]/!d' -e 's/^[^0-9]*//' -e 's/[^0-9]*$//'

... yang menghapus / melompati tanpa digit dan, untuk baris yang tersisa, menghapus semua karakter non-digit di depan dan di belakangnya. (Saya hanya menebak bahwa niat Anda adalah mengekstrak nomor dari setiap baris yang berisi satu).

Masalah dengan sesuatu seperti:

sed -e 's/.*\([0-9]*\).*/&/' 

.... atau

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

... apakah itu sedhanya mendukung pertandingan "serakah" ... jadi yang pertama. * akan cocok dengan sisa baris. Kecuali kita dapat menggunakan kelas karakter yang dinegasikan untuk mencapai kecocokan yang tidak serakah ... atau versi yang sedkompatibel dengan Perl atau ekstensi lain ke regex-nya, kita tidak dapat mengekstrak pola persis yang cocok dari dengan ruang pola (garis ).

Jim Dennis
sumber
Anda bisa menggabungkan dua sedperintah Anda dengan cara ini:sed -n 's/[^0-9]*\([0-9]\+\).*/\1/p'
Dijeda hingga pemberitahuan lebih lanjut.
Sebelumnya tidak tahu tentang opsi -o di grep. Bagus untuk mengetahui. Tapi mencetak seluruh pertandingan, bukan "(...)". Jadi jika Anda mencocokkan "abc ([[: digit:]] +) xyz" maka Anda mendapatkan "abc" dan "xyz" serta digitnya.
Stéphane
Terima kasih telah mengingatkan saya tentang grep -o! Saya mencoba melakukan ini dengan seddan berjuang dengan kebutuhan saya untuk menemukan banyak kecocokan di beberapa baris. Solusi saya adalah stackoverflow.com/a/58308239/117471
Bruno Bronosky
3

Anda dapat menggunakan awkdengan match()untuk mengakses grup yang diambil:

$ awk 'match($0, /abc([0-9]+)xyz/, matches) {print matches[1]}' file
12345

Ini mencoba mencocokkan polanya abc[0-9]+xyz. Jika melakukannya, ia menyimpan irisannya dalam larik matches, yang item pertamanya adalah blok [0-9]+. Karena match() mengembalikan posisi karakter, atau indeks, di mana substring tersebut dimulai (1, jika dimulai pada awal string) , printtindakan tersebut memicu .


Dengan grepAnda dapat menggunakan lihat ke belakang dan ke depan:

$ grep -oP '(?<=abc)[0-9]+(?=xyz)' file
12345

$ grep -oP 'abc\K[0-9]+(?=xyz)' file
12345

Ini memeriksa pola [0-9]+ketika terjadi di dalam abcdan xyzdan hanya mencetak angka.

fedorqui 'JADI berhenti merugikan'
sumber
2

perl adalah sintaks terbersih, tetapi jika Anda tidak memiliki perl (tidak selalu ada, saya mengerti), maka satu-satunya cara untuk menggunakan gawk dan komponen regex adalah dengan menggunakan fitur gensub.

gawk '/abc[0-9]+xyz/ { print gensub(/.*([0-9]+).*/,"\\1","g"); }' < file

keluaran dari file masukan sampel akan

12345

Catatan: gensub menggantikan seluruh regex (di antara //), jadi Anda perlu meletakkan. * Sebelum dan sesudah ([0-9] +) untuk menghilangkan teks sebelum dan sesudah angka dalam substitusi.

Mark Lakata
sumber
2
Solusi yang cerdas dan bisa diterapkan jika Anda perlu (atau ingin) menggunakan gawk. Anda mencatat ini, tetapi untuk memperjelas: awk non-GNU tidak memiliki gensub (), dan karenanya tidak mendukung ini.
cincodenada
Bagus! Namun, mungkin yang terbaik adalah menggunakan match()untuk mengakses grup yang diambil. Lihat jawaban saya untuk ini.
fedorqui 'SO berhenti melukai'
1

Jika Anda ingin memilih garis, hapus bit yang tidak Anda inginkan:

egrep 'abc[0-9]+xyz' inputFile | sed -e 's/^.*abc//' -e 's/xyz.*$//'

Ini pada dasarnya memilih garis yang Anda inginkan egrepdan kemudian digunakan seduntuk menghapus bit sebelum dan sesudah nomor.

Anda dapat melihat ini beraksi di sini:

pax> echo 'a
b
c
abc12345xyz
a
b
c' | egrep 'abc[0-9]+xyz' | sed -e 's/^.*abc//' -e 's/xyz.*$//'
12345
pax> 

Pembaruan: jelas jika situasi Anda sebenarnya lebih kompleks, RE perlu saya modifikasi. Misalnya jika Anda selalu memiliki satu nomor yang terkubur dalam nol atau lebih non-numerik di awal dan akhir:

egrep '[^0-9]*[0-9]+[^0-9]*$' inputFile | sed -e 's/^[^0-9]*//' -e 's/[^0-9]*$//'
paxdiablo
sumber
Menarik ... Jadi tidak ada cara sederhana untuk menerapkan ekspresi reguler yang kompleks dan mendapatkan kembali apa yang ada di bagian (...)? Karena sementara saya melihat apa yang Anda lakukan di sini pertama kali dengan grep kemudian dengan sed, situasi nyata kita jauh lebih kompleks daripada membuang "abc" dan "xyz". Ekspresi reguler digunakan karena banyak teks berbeda dapat muncul di kedua sisi teks yang ingin saya ekstrak.
Stéphane
Aku yakin ada adalah cara yang lebih baik jika Res benar-benar kompleks. Mungkin jika Anda memberikan beberapa contoh lagi atau penjelasan yang lebih rinci, kami dapat menyesuaikan jawaban kami agar sesuai.
paxdiablo
0

Kasus OP tidak menentukan bahwa mungkin ada beberapa kecocokan dalam satu baris, tetapi untuk lalu lintas Google, saya akan menambahkan contoh untuk itu juga.

Karena kebutuhan OP adalah mengekstrak grup dari pola, penggunaan grep -oakan membutuhkan 2 lintasan. Tapi, saya masih menganggap ini cara paling intuitif untuk menyelesaikan pekerjaan.

$ cat > example.txt <<TXT
a
b
c
abc12345xyz
a
abc23451xyz asdf abc34512xyz
c
TXT

$ cat example.txt | grep -oE 'abc([0-9]+)xyz'
abc12345xyz
abc23451xyz
abc34512xyz

$ cat example.txt | grep -oE 'abc([0-9]+)xyz' | grep -oE '[0-9]+'
12345
23451
34512

Karena waktu prosesor pada dasarnya gratis tetapi keterbacaan manusia sangat berharga, saya cenderung memfaktorkan ulang kode saya berdasarkan pertanyaan, "setahun dari sekarang, menurut pendapat saya apa manfaatnya?" Faktanya, untuk kode yang ingin saya bagikan secara publik atau dengan tim saya, saya bahkan akan terbuka man grepuntuk mencari tahu apa saja opsi panjang dan menggantinya. Seperti:grep --only-matching --extended-regexp

Bruno Bronosky
sumber
-1

Anda bisa melakukannya dengan cangkang

while read -r line
do
    case "$line" in
        *abc*[0-9]*xyz* ) 
            t="${line##abc}"
            echo "num is ${t%%xyz}";;
    esac
done <"file"
anjing hantu74
sumber
-3

Untuk awk. Saya akan menggunakan skrip berikut:

/.*abc([0-9]+)xyz.*/ {
            print $0;
            next;
            }
            {
            /* default, do nothing */
            }
Pierre
sumber
Ini tidak mengeluarkan nilai numerik ([0-9+]), ini mengeluarkan seluruh baris.
Mark Lakata
-3
gawk '/.*abc([0-9]+)xyz.*/' file
anjing hantu74
sumber
2
Ini sepertinya tidak berhasil. Ini mencetak seluruh baris, bukan pertandingan.
Stéphane
dalam file masukan sampel Anda, pola tersebut adalah keseluruhan baris. Baik??? jika Anda tahu polanya akan berada di bidang tertentu: gunakan $ 1, $ 2 dll .. misalnya gawk '$ 1 ~ /.*abc([0-9]+)xyz.*/' file
ghostdog74