làm thế nào để sử dụng sed, awk hoặc gawk để chỉ in những gì phù hợp?


100

Tôi thấy rất nhiều ví dụ và trang nam về cách thực hiện những việc như tìm kiếm và thay thế bằng sed, awk hoặc gawk.

Nhưng trong trường hợp của tôi, tôi có một biểu thức chính quy mà tôi muốn chạy với một tệp văn bản để trích xuất một giá trị cụ thể. Tôi không muốn thực hiện tìm kiếm và thay thế. Điều này được gọi từ bash. Hãy sử dụng một ví dụ:

Ví dụ về biểu thức chính quy:

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

Tệp đầu vào mẫu:

a
b
c
abc12345xyz
a
b
c

Nghe đơn giản như vậy, tôi không thể tìm ra cách gọi sed / awk / gawk một cách chính xác. Những gì tôi đã hy vọng làm, là từ trong tập lệnh bash của tôi có:

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

Những điều tôi đã thử bao gồm:

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

10
Wow ... mọi người đã bỏ phiếu cho câu hỏi này xuống -1? Nó thực sự không phù hợp với một câu hỏi?
Stéphane

Nó có vẻ hoàn toàn phù hợp, sử dụng Regex và các tiện ích dòng lệnh mạnh mẽ như sed / awk hoặc bất kỳ trình soạn thảo nào như vi, emacs hoặc teco có thể giống như lập trình hơn là chỉ sử dụng một số ứng dụng ol '. IMO cái này thuộc về SO nhiều hơn SU.
Bỏ qua

Có lẽ nó đã bị bỏ phiếu vì ở dạng ban đầu nó không xác định rõ ràng một số yêu cầu của nó. Nó vẫn không, trừ khi bạn đọc các bình luận của OP về câu trả lời (bao gồm cả câu trả lời mà tôi đã xóa khi mọi thứ diễn ra theo hình quả lê).
pavium

Câu trả lời:


42

My sed(Mac OS X) đã không làm việc với +. *Thay vào đó, tôi đã thử và tôi đã thêm pthẻ để in khớp:

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

Để khớp ít nhất một ký tự số mà không có +, tôi sẽ sử dụng:

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

Cảm ơn bạn, điều này cũng hiệu quả với tôi khi tôi sử dụng * thay vì +.
Stéphane

2
... và tùy chọn "p" để in trận đấu mà tôi cũng không biết. Cảm ơn một lần nữa.
Stéphane 14/11/09

2
Tôi đã phải thoát khỏi +và sau đó nó hoạt động cho tôi:sed -n 's/^.*abc\([0-9]\+\)xyz.*$/\1/p'
Tạm dừng cho đến khi có thông báo mới.

3
Đó là bởi vì bạn không sử dụng định dạng RE hiện đại, do đó + là một ký tự chuẩn và bạn phải thể hiện điều đó bằng cú pháp {,}. Bạn có thể thêm tùy chọn use -E sed để kích hoạt định dạng RE hiện đại. Kiểm tra re_format (7), cụ thể là đoạn cuối cùng của DESCRIPTION developer.apple.com/library/mac/#documentation/Darwin/Reference/…
anddam Ngày

33

Bạn có thể sử dụng sed để làm điều này

 sed -rn 's/.*abc([0-9]+)xyz.*/\1/gp'
  • -n không in dòng kết quả
  • -rđiều này làm cho nó để bạn không có thoát khỏi nhóm bắt giữ parens ().
  • \1 trận đấu nhóm bắt
  • /g trận đấu toàn cầu
  • /p in kết quả

Tôi đã viết một công cụ cho chính mình giúp việc này dễ dàng hơn

rip 'abc(\d+)xyz' '$1'

3
Đây là câu trả lời hay nhất và được giải thích rõ ràng nhất cho đến nay!
Nik Reiman

Với một số giải thích, tốt hơn là bạn nên hiểu vấn đề của chúng ta có gì sai. Cảm ơn bạn !
r4phG

17

Tôi sử dụng perlđể làm cho điều này dễ dàng hơn cho chính mình. ví dụ

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

Điều này chạy Perl, -ntùy chọn hướng dẫn Perl đọc từng dòng một từ STDIN và thực thi mã. Các -etùy chọn xác định hướng dẫn để chạy.

Lệnh chạy một regexp trên dòng được đọc, và nếu nó khớp sẽ in ra nội dung của bộ dấu ngoặc đầu tiên ( $1).

Bạn có thể làm điều này cũng sẽ có nhiều tên tệp ở cuối. ví dụ

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


Cảm ơn, nhưng chúng tôi không có quyền truy cập vào perl, đó là lý do tại sao tôi hỏi về sed / awk / gawk.
Stéphane

5

Nếu phiên bản của bạn grephỗ trợ nó, bạn có thể sử dụng -otùy chọn để chỉ in phần của bất kỳ dòng nào khớp với regexp của bạn.

Nếu không thì đây là điều tốt nhất sedtôi có thể nghĩ ra:

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

... xóa / bỏ qua không có chữ số và đối với các dòng còn lại, xóa tất cả các ký tự không phải chữ số ở đầu và cuối. (Tôi chỉ đoán rằng ý định của bạn là trích xuất số từ mỗi dòng chứa một).

Vấn đề với một cái gì đó như:

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

.... hoặc là

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

... là sedchỉ hỗ trợ khớp "tham lam" ... vì vậy đầu tiên. * sẽ khớp với phần còn lại của dòng. Trừ khi chúng tôi có thể sử dụng một lớp ký tự bị phủ định để đạt được kết quả phù hợp không tham lam ... hoặc một phiên bản sedtương thích với Perl hoặc các phần mở rộng khác cho regexes của nó, chúng tôi không thể trích xuất một kết hợp mẫu chính xác từ không gian mẫu (một dòng ).


Bạn chỉ có thể kết hợp hai sedlệnh của mình theo cách này:sed -n 's/[^0-9]*\([0-9]\+\).*/\1/p'
Tạm dừng cho đến khi có thông báo mới.

Trước đây không biết về tùy chọn -o trên grep. Rất vui được biết. Nhưng nó in toàn bộ trận đấu, không phải "(...)". Vì vậy, nếu bạn đang khớp trên "abc ([[: digit:]] +) xyz" thì bạn sẽ nhận được "abc" và "xyz" cũng như các chữ số.
Stéphane

Cảm ơn vì đã nhắc nhở tôi grep -o! Tôi đã cố gắng làm điều này với sedvà đấu tranh với nhu cầu của tôi để tìm nhiều kết quả phù hợp trên một số dòng. Giải pháp của tôi là stackoverflow.com/a/58308239/117471
Bruno Bronosky

3

Bạn có thể sử dụng awkvới match()để truy cập nhóm đã chụp:

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

Điều này cố gắng phù hợp với mô hình abc[0-9]+xyz. Nếu nó làm như vậy, nó lưu trữ các lát cắt của nó trong mảng matches, có mục đầu tiên là khối [0-9]+. Vì match() trả về vị trí ký tự hoặc chỉ mục, nơi chuỗi con đó bắt đầu (1, nếu nó bắt đầu ở đầu chuỗi) , nó sẽ kích hoạt printhành động.


Với grepbạn có thể sử dụng một cái nhìn đằng sau và nhìn về phía trước:

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

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

Kiểm tra này mô hình [0-9]+khi nó xảy ra trong phạm vi abcxyzvà chỉ in các chữ số.


2

perl là cú pháp rõ ràng nhất, nhưng nếu bạn không có perl (không phải lúc nào cũng có, tôi hiểu), thì cách duy nhất để sử dụng gawk và các thành phần của regex là sử dụng tính năng gensub.

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

đầu ra của tệp đầu vào mẫu sẽ là

12345

Lưu ý: gensub thay thế toàn bộ regex (giữa //), vì vậy bạn cần đặt. * Trước và sau ([0-9] +) để loại bỏ văn bản trước và sau số trong thay thế.


2
Một giải pháp thông minh, khả thi nếu bạn cần (hoặc muốn) sử dụng gawk. Bạn đã lưu ý điều này, nhưng phải rõ ràng: awk không phải GNU không có gensub (), và do đó không hỗ trợ điều này.
cincodenada

Đẹp! Tuy nhiên, có thể tốt nhất là sử dụng match()để truy cập các nhóm đã được chụp. Xem câu trả lời của tôi cho điều này.
fedorqui 'SO dừng hại'

1

Nếu bạn muốn chọn các dòng thì hãy loại bỏ các bit bạn không muốn:

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

Về cơ bản, nó chọn các dòng bạn muốn egrepvà sau đó sử dụngsed để loại bỏ các bit trước và sau số.

Bạn có thể thấy điều này hoạt động ở đây:

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

Cập nhật: rõ ràng nếu tình hình thực tế của bạn phức tạp hơn, REs sẽ cần tôi sửa đổi. Ví dụ: nếu bạn luôn có một số duy nhất bị chôn vùi trong 0 hoặc nhiều hơn không phải số ở đầu và cuối:

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

Thật thú vị ... Vì vậy, không có cách nào đơn giản để áp dụng một biểu thức chính quy phức tạp và lấy lại những gì có trong phần (...)? Bởi vì trong khi tôi thấy những gì bạn đã làm ở đây đầu tiên với grep sau đó với sed, tình huống thực tế của chúng tôi phức tạp hơn nhiều so với việc thả "abc" và "xyz". Biểu thức chính quy được sử dụng vì rất nhiều văn bản khác nhau có thể xuất hiện ở hai bên của văn bản mà tôi muốn trích xuất.
Stéphane

Tôi chắc rằng có một cách tốt hơn nếu RES là thực sự phức tạp. Có lẽ nếu bạn cung cấp thêm một vài ví dụ hoặc mô tả chi tiết hơn, chúng tôi có thể điều chỉnh câu trả lời của mình cho phù hợp.
paxdiablo

0

Trường hợp của OP không chỉ rõ rằng có thể có nhiều kết quả phù hợp trên một dòng, nhưng đối với lưu lượng truy cập của Google, tôi cũng sẽ thêm một ví dụ cho điều đó.

Vì nhu cầu của OP là trích xuất một nhóm từ một mẫu, việc sử dụng grep -osẽ yêu cầu 2 lần chuyển. Nhưng, tôi vẫn thấy đây là cách trực quan nhất để hoàn thành công việc.

$ 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

Vì thời gian của bộ xử lý về cơ bản là miễn phí nhưng khả năng đọc của con người là vô giá, tôi có xu hướng cấu trúc lại mã của mình dựa trên câu hỏi, "một năm nữa, tôi sẽ nghĩ điều này có tác dụng gì?" Trên thực tế, đối với mã mà tôi định chia sẻ công khai hoặc với nhóm của mình, tôi thậm chí sẽ mở man grepđể tìm ra các tùy chọn dài là gì và thay thế chúng. Như vậy:grep --only-matching --extended-regexp


-1

bạn có thể làm điều đó với vỏ

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

-3

Đối với awk. Tôi sẽ sử dụng tập lệnh sau:

/.*abc([0-9]+)xyz.*/ {
            print $0;
            next;
            }
            {
            /* default, do nothing */
            }

Điều này không xuất ra giá trị số ([0-9+]), điều này sẽ xuất ra toàn bộ dòng.
Mark Lakata

-3
gawk '/.*abc([0-9]+)xyz.*/' file

2
Điều này dường như không hoạt động. Nó in toàn bộ dòng thay vì khớp.
Stéphane

trong tệp đầu vào mẫu của bạn, mẫu đó là toàn bộ dòng. đúng??? nếu bạn biết mẫu sẽ nằm trong một trường cụ thể: hãy sử dụng $ 1, $ 2, v.v. ví dụ: gawk '$ 1 ~ /.*abc([0-9]+)xyz.*/' file
ghostdog74
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.