Làm thế nào để đầu ra chỉ bắt các nhóm với sed?


277

Có cách nào để nói sedvới đầu ra chỉ các nhóm bị bắt không? Ví dụ cho đầu vào:

This is a sample 123 text and some 987 numbers

và mô hình:

/([\d]+)/

Tôi chỉ có thể nhận đầu ra 123 và 987 theo cách được định dạng bởi các tham chiếu trở lại?


Lưu ý, chụp nhóm yêu cầu sedbật biểu thức chính quy mở rộng bằng -Ecờ.
peterh - Phục hồi Monica

Câu trả lời:


333

Chìa khóa để làm cho điều này hoạt động là nói sedđể loại trừ những gì bạn không muốn là đầu ra cũng như chỉ định những gì bạn muốn.

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

Điều này nói rằng:

  • không mặc định để in từng dòng ( -n)
  • loại trừ không hoặc nhiều chữ số
  • bao gồm một hoặc nhiều chữ số
  • loại trừ một hoặc nhiều chữ số
  • bao gồm một hoặc nhiều chữ số
  • loại trừ không hoặc nhiều chữ số
  • in thay thế ( p)

Nói chung, trong sedbạn chụp các nhóm bằng dấu ngoặc đơn và xuất ra những gì bạn chụp bằng tham chiếu ngược:

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

sẽ xuất "thanh". Nếu bạn sử dụng -r( -Echo OS X) cho regex mở rộng, bạn không cần phải thoát dấu ngoặc đơn:

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

Có thể có tới 9 nhóm chụp và tài liệu tham khảo trở lại của họ. Các tham chiếu trở lại được đánh số theo thứ tự các nhóm xuất hiện, nhưng chúng có thể được sử dụng theo bất kỳ thứ tự nào và có thể được lặp lại:

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

xuất ra "một thanh a".

Nếu bạn có GNU grep(nó cũng có thể hoạt động trong BSD, bao gồm cả OS X):

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

hoặc các biến thể như:

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

Các -Ptùy chọn cho phép Perl Regular Expressions Tương thích. Xem man 3 pcrepatternhoặc man 3 pcresyntax.


24
Lưu ý, OSX Mountain Lion không còn hỗ trợ PCRE trong grep.
yincrash

1
Là một lưu ý phụ, tùy chọn grep -o không được hỗ trợ trên Solaris 9. Ngoài ra, Solaris 9 không hỗ trợ tùy chọn sed -r. :(
Daniel Kats

7
Yêu cầu sysadmin của bạn để cài đặt gsed. Bạn sẽ ngạc nhiên về những gì một vài chiếc bánh rán sẽ mang lại cho bạn ...
avgvstvs

3
Lưu ý rằng bạn có thể cần phải thêm tiền tố '(' và ')' với '\', tôi không biết tại sao.
thắt lưng

7
@lumbric: Nếu bạn đang tham khảo sedví dụ, nếu bạn sử dụng -rtùy chọn (hoặc -Echo OS X, IIRC), bạn không cần phải thoát dấu ngoặc đơn. Sự khác biệt là giữa biểu thức chính quy cơ bản và biểu thức chính quy mở rộng ( -r).
Tạm dừng cho đến khi có thông báo mới.

55

Sed có tới chín mẫu đã nhớ nhưng bạn cần sử dụng dấu ngoặc đơn thoát để ghi nhớ các phần của biểu thức chính quy.

Xem ở đây để biết ví dụ và chi tiết hơn


58
sed -e 's/version=\(.+\)/\1/' input.txtđiều này vẫn sẽ xuất ra toàn bộ input.txt
Pablo

@Pablo, Trong mẫu của bạn, bạn phải viết \+thay vì +. Và tôi không hiểu tại sao mọi người chỉ sử dụng -emột lệnh sed.
Fredrick Gauss

1
sử dụng sed -e -n 's/version=\(.+\)/\1/p' input.txtxem: mikeplate.com/2012/05/09/ trên
awattar

1
Tôi khuyên bạn nên sử dụng sed -Eđể sử dụng các biểu thức thông thường được gọi là "hiện đại" hoặc "mở rộng" trông gần gũi hơn với Perl / Java / JavaScript / Go / bất kỳ hương vị nào. (So ​​sánh với grep -Ehoặc egrep.) Cú pháp mặc định có các quy tắc thoát kỳ lạ đó và được coi là "lỗi thời". Để biết thêm thông tin về sự khác biệt giữa hai, chạy man 7 re_format.
AndrewF

31

bạn có thể sử dụng grep

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

4
@ ghostdog74: Hoàn toàn đồng ý với bạn. Làm cách nào tôi có thể nhận được greo để chỉ xuất các nhóm bị bắt?
Pablo

1
@ Michael - đó là lý do tại sao olựa chọn là có - unixhelp.ed.ac.uk/CGI/man-cgi?grep : -o, thảo mà chỉ-phù hợp với Chỉ một phần của một dòng tương rằng trận đấu MẪU
Bert F

14
@Bert F: Tôi hiểu phần phù hợp, nhưng nó không bắt nhóm. Điều tôi muốn là có như thế này ([0-9] +). + ([Abc] {2,3}) để có 2 nhóm bắt giữ. Tôi muốn đầu ra CHỈ bắt các nhóm bằng cách phản hồi hoặc bằng cách nào đó.
Pablo

Xin chào Michael. Bạn đã quản lý để trích xuất nhóm thứ n bị bắt bởi grep?
doc_id

1
@Pablo: grep chỉ xuất ra những gì phù hợp. Để cung cấp cho nhiều nhóm, hãy sử dụng nhiều biểu thức: grep -Eow -e "[0-9]+" -e "[abc]{2,3}"Tôi không biết làm thế nào bạn có thể yêu cầu hai biểu thức đó nằm trên một dòng ngoài đường ống từ một grep trước đó (vẫn không thể hoạt động nếu một trong hai mẫu khớp với nhiều hơn một dòng trên một dòng ).
idbrii

13

chạy (các) chữ số

Câu trả lời này hoạt động với bất kỳ số lượng các nhóm chữ số. Thí dụ:

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

Câu trả lời mở rộng.

Có cách nào để bảo sed chỉ xuất ra các nhóm bị bắt không?

Đúng. thay thế tất cả văn bản bởi nhóm chụp:

$ 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.

Hoặc với cú pháp mở rộng (ít backquote và cho phép sử dụng +):

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

Để tránh in văn bản gốc khi không có số, hãy sử dụng:

$ echo 'Number xxx in text' | sed -En 's/[^0-9]*([0-9]+)[^0-9]*/\1/p'
  • (-n) Không in đầu vào theo mặc định.
  • (/ p) chỉ in nếu việc thay thế đã được thực hiện.

Và để khớp với một số số (và cũng in chúng):

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

Điều đó làm việc cho bất kỳ số lượng chữ số chạy:

$ 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

Điều này rất giống với lệnh grep:

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

Giới thiệu về

và mô hình: /([\d]+)/

Sed không nhận ra cú pháp '\ d' (phím tắt). Tương đương ascii được sử dụng ở trên[0-9] là không chính xác tương đương. Giải pháp thay thế duy nhất là sử dụng một lớp ký tự: '[[: chữ số:]] `.

Câu trả lời được chọn sử dụng các "lớp ký tự" như vậy để xây dựng giải pháp:

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

Giải pháp đó chỉ hoạt động cho (chính xác) hai lần chạy chữ số.

Tất nhiên, vì câu trả lời đang được thực thi bên trong shell, chúng ta có thể định nghĩa một vài biến để rút ngắn câu trả lời như vậy:

$ 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"

Nhưng, như đã được giải thích, sử dụng s/…/…/gplệnh sẽ tốt hơn:

$ 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

Điều đó sẽ bao gồm cả các chữ số lặp đi lặp lại và viết một lệnh ngắn (er).


Ngạc nhiên sau khi đọc câu trả lời được chấp nhận cao, tôi cuộn xuống để viết về phạm vi hẹp của nó và thực sự giải quyết tinh thần của câu hỏi. Tôi nên đoán rằng ai đó sẽ làm điều đó từ nhiều năm trước. Điều này được giải thích rất tốt và là câu trả lời đúng chính xác.
Amit N Nikol

9

Tôi tin rằng mô hình được đưa ra trong câu hỏi chỉ bằng ví dụ và mục tiêu là phù hợp với bất kỳ mẫu .

Nếu bạn có một sed với phần mở rộng GNU cho phép chèn một dòng mới vào không gian mẫu, một gợi ý là:

> 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

Những ví dụ này là với tcsh (vâng, tôi biết nó là vỏ sai) với CYGWIN. (Chỉnh sửa: Đối với bash, xóa bộ và khoảng trắng xung quanh =.)


@Joseph: cảm ơn, tuy nhiên, dựa trên nhiệm vụ của tôi, tôi cảm thấy grep tự nhiên hơn, giống như ghostdog74 đề xuất. Chỉ cần tìm ra cách làm cho grep xuất ra các nhóm chụp mà không phải toàn bộ khớp.
Pablo

2
Chỉ là một ghi chú, nhưng dấu cộng '+' có nghĩa là 'một hoặc nhiều' sẽ loại bỏ nhu cầu lặp lại chính bạn trong các mẫu. Vì vậy, "[0-9] [0-9] *" sẽ trở thành "[0-9] +"
RandomInsano

4
@RandomInsano: Để sử dụng +, bạn cần thoát nó hoặc sử dụng -rtùy chọn ( -Echo OS X). Bạn cũng có thể sử dụng \{1,\}(hoặc -rhoặc-E không có lối thoát).
Tạm dừng cho đến khi có thông báo mới.

9

Từ bỏ và sử dụng Perl

sedkhông cắt nó, chúng ta hãy ném khăn và sử dụng Perl, ít nhất đó là LSB trong khi các grepphần mở rộng GNU thì không :-)

  • In toàn bộ phần phù hợp, không cần nhóm phù hợp hoặc giao diện cần thiết:

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

    Đầu ra:

    12
    3456
  • Khớp đơn trên mỗi dòng, các trường dữ liệu thường có cấu trúc:

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

    Đầu ra:

    1
    34

    Với cái nhìn:

    cat <<EOS | perl -lane 'print m/(?<=a)(\d+)/'
    a1 b2
    a34 b56
    EOS
  • Nhiều lĩnh vực:

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

    Đầu ra:

    1 2
    34 56
  • Nhiều kết quả trùng khớp trên mỗi dòng, dữ liệu thường không có cấu trúc:

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

    Đầu ra:

    1 
    34 78

    Với cái nhìn:

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

    Đầu ra:

    1
    3478

1
Bạn đã không nhận được gì khi kết thúc câu hỏi: "với sed"?
Moonchild

@Moonchild Nhân viên Google không quan tâm.
Ciro Santilli 郝海东 冠状 病 事件

1
tôi thấy điều này hữu ích không phải tất cả các vấn đề regex dòng lệnh cần được giải quyết với sed.
PPPaul

5

Thử

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"

Tôi đã nhận được điều này dưới 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

Đó không phải là những gì OP yêu cầu (chụp các nhóm) nhưng bạn có thể trích xuất các số bằng cách sử dụng:

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

Cung cấp như sau:

123
987
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.