Bagaimana cara menghasilkan hanya kelompok yang ditangkap dengan sed?


278

Apakah ada cara untuk mengatakan sedhanya menghasilkan kelompok yang ditangkap? Misalnya diberi input:

This is a sample 123 text and some 987 numbers

dan pola:

/([\d]+)/

Bisakah saya mendapatkan hanya output 123 dan 987 dengan cara diformat oleh referensi belakang?


Catatan, penangkapan grup sedharus mengaktifkan ekspresi reguler yang diperluas dengan -Ebendera.
peterh

Jawaban:


333

Kunci untuk membuat ini bekerja adalah untuk mengatakan seduntuk mengecualikan apa yang tidak Anda inginkan sebagai output serta menentukan apa yang Anda inginkan.

string='This is a sample 123 text and some 987 numbers'
echo "$string" | sed -rn 's/[^[:digit:]]*([[:digit:]]+)[^[:digit:]]+([[:digit:]]+)[^[:digit:]]*/\1 \2/p'

Ini mengatakan:

  • tidak default untuk mencetak setiap baris ( -n)
  • mengecualikan nol atau lebih non-digit
  • termasuk satu atau lebih digit
  • kecualikan satu atau lebih non-digit
  • termasuk satu atau lebih digit
  • mengecualikan nol atau lebih non-digit
  • cetak substitusi ( p)

Secara umum, dalam sedAnda menangkap grup menggunakan tanda kurung dan output apa yang Anda ambil menggunakan referensi kembali:

echo "foobarbaz" | sed 's/^foo\(.*\)baz$/\1/'

akan menampilkan "bar". Jika Anda menggunakan -r( -Euntuk OS X) untuk regex yang diperluas, Anda tidak perlu keluar dari tanda kurung:

echo "foobarbaz" | sed -r 's/^foo(.*)baz$/\1/'

Mungkin ada hingga 9 kelompok penangkapan dan referensi belakang mereka. Referensi belakang diberi nomor sesuai urutan munculnya kelompok, tetapi mereka dapat digunakan dalam urutan apa pun dan dapat diulang:

echo "foobarbaz" | sed -r 's/^foo(.*)b(.)z$/\2 \1 \2/'

menghasilkan "bilah a".

Jika Anda memiliki GNU grep(mungkin juga berfungsi di BSD, termasuk OS X):

echo "$string" | grep -Po '\d+'

atau variasi seperti:

echo "$string" | grep -Po '(?<=\D )(\d+)'

The -Ppilihan memungkinkan Perl Regular Expressions Kompatibel. Lihat man 3 pcrepatternatau man 3 pcresyntax.


24
Sebagai catatan, OSX Mountain Lion tidak lagi mendukung PCRE di grep.
yincrash

1
Sebagai catatan tambahan, opsi grep -o tidak didukung pada Solaris 9. Selain itu, Solaris 9 tidak mendukung opsi sed -r. :(
Daniel Kats

7
Minta sysadmin Anda untuk menginstal gsed. Anda akan kagum dengan apa yang akan diberikan beberapa donat kepada Anda ...
avgvstvs

3
Perhatikan bahwa Anda mungkin perlu awalan '(' dan ')' dengan '\', saya tidak tahu mengapa.
lumbric

7
@ lumbric: Jika Anda merujuk pada sedcontoh, jika Anda menggunakan -ropsi (atau -Euntuk OS X, IIRC) Anda tidak perlu keluar dari tanda kurung. Perbedaannya adalah antara ekspresi reguler dasar dan ekspresi reguler lanjutan ( -r).
Dijeda sampai pemberitahuan lebih lanjut.

55

Sed memiliki hingga sembilan pola yang diingat tetapi Anda harus menggunakan tanda kurung yang lolos untuk mengingat bagian dari ekspresi reguler.

Lihat di sini untuk contoh dan lebih detail


58
sed -e 's/version=\(.+\)/\1/' input.txtini masih akan menampilkan seluruh input.txt
Pablo

@Pablo, Dalam pola Anda, Anda harus menulis \+alih-alih +. Dan saya tidak mengerti mengapa orang -ehanya menggunakan satu perintah sed.
Fredrick Gauss

1
gunakan sed -e -n 's/version=\(.+\)/\1/p' input.txtlihat: mikeplate.com/2012/05/09/...
awattar

1
Saya sarankan menggunakan sed -Emenggunakan apa yang disebut ekspresi reguler "modern" atau "extended" yang terlihat jauh lebih dekat dengan Perl / Java / JavaScript / Go / apa pun rasanya. (Bandingkan dengan grep -Eatau egrep.) Sintaks default memiliki aturan pelarian yang aneh dan dianggap "usang". Untuk info lebih lanjut tentang perbedaan antara keduanya, jalankan man 7 re_format.
AndrewF

31

Anda bisa menggunakan grep

grep -Eow "[0-9]+" file

4
@ ghostdog74: Sepenuhnya setuju dengan Anda. Bagaimana saya bisa mendapatkan greo ke output hanya grup yang ditangkap?
Pablo

1
@Michael - itu sebabnya opilihan ada di sana - unixhelp.ed.ac.uk/CGI/man-cgi?grep : -o, --only-matching Tampilkan hanya bagian dari garis yang cocok yang cocok dengan POLA
Bert F

14
@Bert F: Saya mengerti bagian yang cocok, tetapi tidak menangkap grup. Yang saya inginkan adalah memiliki seperti ini ([0-9] +). + ([Abc] {2,3}) sehingga ada 2 kelompok yang menangkap. Saya ingin keluaran HANYA menangkap kelompok dengan referensi kembali atau entah bagaimana.
Pablo

Halo Michael. Apakah Anda berhasil mengekstrak grup yang ditangkap dengan grep?
doc_id

1
@Pablo: grep hanya mengeluarkan yang cocok. Untuk memberikannya beberapa grup, gunakan beberapa ekspresi: grep -Eow -e "[0-9]+" -e "[abc]{2,3}"Saya tidak tahu bagaimana Anda dapat mengharuskan kedua ekspresi itu berada di satu baris selain dari perpipaan dari grep sebelumnya (yang masih bisa tidak bekerja jika salah satu pola cocok lebih dari satu kali pada satu baris) ).
idbrii

13

jalankan digit

Jawaban ini berfungsi dengan jumlah kelompok digit apa pun. Contoh:

$ echo 'Num123that456are7899900contained0018166intext' |
> sed -En 's/[^0-9]*([0-9]{1,})[^0-9]*/\1 /gp'
123 456 7899900 0018166

Jawaban diperluas.

Apakah ada cara untuk mengatakan sed kepada output hanya kelompok yang ditangkap?

Iya. ganti semua teks dengan grup tangkap:

$ echo 'Number 123 inside text' | sed 's/[^0-9]*\([0-9]\{1,\}\)[^0-9]*/\1/'
123

s/[^0-9]*                           # several non-digits
         \([0-9]\{1,\}\)            # followed by one or more digits
                        [^0-9]*     # and followed by more non-digits.
                               /\1/ # gets replaced only by the digits.

Atau dengan sintaks yang diperluas (kurangi backquotes dan izinkan penggunaan +):

$ echo 'Number 123 in text' | sed -E 's/[^0-9]*([0-9]+)[^0-9]*/\1/'
123

Untuk menghindari pencetakan teks asli ketika tidak ada nomor, gunakan:

$ echo 'Number xxx in text' | sed -En 's/[^0-9]*([0-9]+)[^0-9]*/\1/p'
  • (-n) Jangan cetak input secara default.
  • (/ p) cetak hanya jika penggantian dilakukan.

Dan untuk mencocokkan beberapa angka (dan juga mencetaknya):

$ echo 'N 123 in 456 text' | sed -En 's/[^0-9]*([0-9]+)[^0-9]*/\1 /gp'
123 456

Itu bekerja untuk setiap hitungan digit berjalan:

$ str='Test Num(s) 123 456 7899900 contained as0018166df in text'
$ echo "$str" | sed -En 's/[^0-9]*([0-9]{1,})[^0-9]*/\1 /gp'
123 456 7899900 0018166

Yang sangat mirip dengan perintah grep:

$ str='Test Num(s) 123 456 7899900 contained as0018166df in text'
$ echo "$str" | grep -Po '\d+'
123
456
7899900
0018166

Tentang \ d

dan pola: /([\d]+)/

Sed tidak mengenali sintaks '\ d' (pintasan). Setara ascii yang digunakan di atas [0-9]tidak persis sama. Satu-satunya solusi alternatif adalah dengan menggunakan kelas karakter: '[[: digit:]] `.

Jawaban yang dipilih menggunakan "kelas karakter" untuk membangun solusi:

$ str='This is a sample 123 text and some 987 numbers'
$ echo "$str" | sed -rn 's/[^[:digit:]]*([[:digit:]]+)[^[:digit:]]+([[:digit:]]+)[^[:digit:]]*/\1 \2/p'

Solusi itu hanya bekerja untuk dua digit digit.

Tentu saja, ketika jawaban dieksekusi di dalam shell, kita dapat mendefinisikan beberapa variabel untuk mempersingkat jawaban tersebut:

$ str='This is a sample 123 text and some 987 numbers'
$ d=[[:digit:]]     D=[^[:digit:]]
$ echo "$str" | sed -rn "s/$D*($d+)$D+($d+)$D*/\1 \2/p"

Tapi, seperti yang sudah dijelaskan, menggunakan s/…/…/gpperintah lebih baik:

$ str='This is 75577 a sam33ple 123 text and some 987 numbers'
$ d=[[:digit:]]     D=[^[:digit:]]
$ echo "$str" | sed -rn "s/$D*($d+)$D*/\1 /gp"
75577 33 123 987

Itu akan mencakup kedua digit yang berulang dan menulis perintah (er) pendek.


Terkejut setelah membaca jawaban yang diterima dengan suara tinggi, saya gulir ke bawah untuk menulis tentang ruang lingkup yang sempit dan untuk benar-benar mengatasi semangat pertanyaan. Aku seharusnya sudah menebak bahwa seseorang sudah melakukannya bertahun-tahun yang lalu. Ini dijelaskan dengan sangat baik dan merupakan jawaban yang benar dan benar.
Amit Naidu

9

Saya percaya pola yang diberikan dalam pertanyaan itu hanya dengan contoh saja, dan tujuannya adalah untuk mencocokkan pola apa pun .

Jika Anda memiliki sed dengan ekstensi GNU yang memungkinkan penyisipan baris baru dalam ruang pola, satu saran adalah:

> set string = "This is a sample 123 text and some 987 numbers"
>
> set pattern = "[0-9][0-9]*"
> echo $string | sed "s/$pattern/\n&\n/g" | sed -n "/$pattern/p"
123
987
> set pattern = "[a-z][a-z]*"
> echo $string | sed "s/$pattern/\n&\n/g" | sed -n "/$pattern/p"
his
is
a
sample
text
and
some
numbers

Contoh-contoh ini dengan tcsh (ya, saya tahu itu shell yang salah) dengan CYGWIN. (Edit: Untuk bash, hapus set, dan spasi di sekitar =.)


@ Joseph: terima kasih, namun, berdasarkan tugas saya, saya merasa grep lebih alami, seperti yang disarankan ghostdog74. Hanya perlu mencari tahu bagaimana membuat grep output hanya grup tangkap, bukan seluruh pertandingan.
Pablo

2
Hanya sebuah catatan, tetapi tanda tambah '+' berarti 'satu atau lebih' yang akan menghilangkan kebutuhan untuk mengulangi diri Anda sendiri dalam pola. Jadi, "[0-9] [0-9] *" akan menjadi "[0-9] +"
RandomInsano

4
@RandomInsano: Untuk menggunakan +, Anda harus menghindarinya atau menggunakan -ropsi ( -Euntuk OS X). Anda juga dapat menggunakan \{1,\}( -ratau -Etanpa melarikan diri).
Dijeda sampai pemberitahuan lebih lanjut.

9

Menyerah dan menggunakan Perl

Karena sedtidak memotongnya, mari kita membuang handuk dan menggunakan Perl, setidaknya itu adalah LSB sedangkan grepekstensi GNU tidak :-)

  • Cetak seluruh bagian yang cocok, tidak perlu grup yang cocok atau yang terlihat di belakang:

    cat <<EOS | perl -lane 'print m/\d+/g'
    a1 b2
    a34 b56
    EOS

    Keluaran:

    12
    3456
  • Kecocokan tunggal per baris, bidang data yang sering terstruktur:

    cat <<EOS | perl -lape 's/.*?a(\d+).*/$1/g'
    a1 b2
    a34 b56
    EOS

    Keluaran:

    1
    34

    Dengan melihat di belakang:

    cat <<EOS | perl -lane 'print m/(?<=a)(\d+)/'
    a1 b2
    a34 b56
    EOS
  • Banyak bidang:

    cat <<EOS | perl -lape 's/.*?a(\d+).*?b(\d+).*/$1 $2/g'
    a1 c0 b2 c0
    a34 c0 b56 c0
    EOS

    Keluaran:

    1 2
    34 56
  • Beberapa kecocokan per baris, seringkali data tidak terstruktur:

    cat <<EOS | perl -lape 's/.*?a(\d+)|.*/$1 /g'
    a1 b2
    a34 b56 a78 b90
    EOS

    Keluaran:

    1 
    34 78

    Dengan melihat di belakang:

    cat EOS<< | perl -lane 'print m/(?<=a)(\d+)/g'
    a1 b2
    a34 b56 a78 b90
    EOS

    Keluaran:

    1
    3478

1
Apa yang tidak Anda dapatkan pada akhir pertanyaan: "dengan sed"?
Moonchild


1
Saya menemukan ini berguna. tidak semua masalah regex baris perintah harus diselesaikan dengan sed.
PPPaul

5

Mencoba

sed -n -e "/[0-9]/s/^[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\).*$/\1 \2 \3 \4 \5 \6 \7 \8 \9/p"

Saya mendapatkan ini di bawah cygwin:

$ (echo "asdf"; \
   echo "1234"; \
   echo "asdf1234adsf1234asdf"; \
   echo "1m2m3m4m5m6m7m8m9m0m1m2m3m4m5m6m7m8m9") | \
  sed -n -e "/[0-9]/s/^[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\)[^0-9]*\([0-9]*\).*$/\1 \2 \3 \4 \5 \6 \7 \8 \9/p"

1234
1234 1234
1 2 3 4 5 6 7 8 9
$

2

Bukan itu yang diminta OP (menangkap grup) tetapi Anda dapat mengekstraksi angka menggunakan:

S='This is a sample 123 text and some 987 numbers'
echo "$S" | sed 's/ /\n/g' | sed -r '/([0-9]+)/ !d'

Memberikan yang berikut:

123
987
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.