2010-04-12 42 views
10

Tôi muốn trích xuất một chuỗi con phù hợp với một mẫu và lưu nó vào một tệp. Một chuỗi ví dụ:Lưu một phần của mẫu phù hợp thành biến

Apr 12 19:24:17 PC_NMG kernel: sd 11:0:0:0: [sdf] Attached SCSI removable disk 

Tôi muốn trích phần giữa các dấu ngoặc, trong trường hợp này là [sdf].

Tôi đã cố gắng thực hiện một cái gì đó như grep -e '[$subtext]' để lưu văn bản trong dấu ngoặc vào một biến. Tất nhiên nó không hoạt động, nhưng tôi đang tìm kiếm một cách tương tự như thế này. Nó sẽ rất thanh lịch để bao gồm một biến trong một regex như thế này. Tôi có thể làm gì tốt nhất?

Cảm ơn!

+0

Tôi giả sử rằng ảnh chụp phải là ** giữa ** các dấu ngoặc vuông, ví dụ: không bao gồm? –

Trả lời

7

Có lẽ một cách tốt hơn chỉ sử dụng bash, nhưng:

echo 'Apr 12 19:24:17 PC_NMG kernel: sd 11:0:0:0: [sdf] Attached SCSI removable disk' \ 
| sed -s 's/.*\[\(.*\)\].*/\1/' 

Như Jurgen chỉ ra, điều này phù hợp với dòng không phù hợp. Nếu bạn không muốn xuất các dòng nonmatching, sử dụng '-n' để nó không xuất ra mẫu và '/ p' để xuất ra mẫu khi nó khớp.

| sed -n 's/.*\[\(.*\)\].*/\1/p' 
+1

này cũng in không phù hợp với dòng –

+0

@Jurgen Hotzel: Cảm ơn, thay đổi nội dung một sửa chữa. – Stephen

+0

Người dùng OS X sẽ cần phải loại bỏ tùy chọn -s vì nó không được hỗ trợ. –

4

trận đấu chống lại regex, thay thế bằng nhóm và chỉ in nếu regex lần xuất hiện:

sed -n "s/.*\[\(.*\)\].*/\1/p" 
0

sed là tham lam, vì vậy câu trả lời sed sẽ bỏ lỡ một số dữ liệu nếu có nhiều [] cặp trong dữ liệu của bạn. Sử dụng grep + tr giải pháp hoặc bạn có thể sử dụng awk

$ cat file 
[sss]Apr 12 19:24:17 PC_NMG kernel: sd 11:0:0:0: [sdf] Attached SCSI removable disk [tag] blah blah 

$ awk -F"[" '{for(i=2;i<=NF;i++){if($i~/\]/){sub("].*","",$i)};print $i}}' file 
sss 
sdf 
tag 
10

BASH_REMATCH là một mảng chứa các nhóm phù hợp của vỏ.

$ line='Apr 12 19:24:17 PC_NMG kernel: sd 11:0:0:0: [sdf] Attached SCSI removable disk' 
$ [[ $line =~ \[([^]]+)\] ]]; echo "${BASH_REMATCH[1]}" 
sdf 

Nếu bạn muốn đặt điều này trong vòng lặp, bạn có thể làm điều đó; đây là một ví dụ:

while read -r line; do 
    if [[ $line =~ \[([^]]+)\] ]] ; then 
    drive="${BASH_REMATCH[1]}" 
    do_something_with "$drive" 
    fi 
done < <(dmesg | egrep '\[([hsv]d[^]]+)\]') 

Cách tiếp cận này đặt không có cuộc gọi bên ngoài vào các vòng lặp - vì vậy vỏ không cần phải forkexec để bắt đầu chương trình bên ngoài như sed hoặc grep. Như vậy, nó được cho là sạch hơn đáng kể so với các cách tiếp cận khác được cung cấp ở đây.

BTW, cách tiếp cận ban đầu của bạn (sử dụng grep) không xa lắm; sử dụng grep -o sẽ chỉ xuất ra chuỗi con phù hợp:

$ subtext=$(egrep -o "\[[^]]*\]" <<<"$line") 

... mặc dù điều này bao gồm dấu ngoặc trong ảnh chụp và do đó không chính xác 100%.

+0

nhưng sau đó bash trong khi đọc loop là chậm hơn đáng kể cho iterating một file lớn so với awk (vv). Nhân tiện, tôi không nhận được đầu ra cho phiên bản đầu tiên của bạn mà không có vòng lặp while. Không thể thoát khỏi ']' trong phạm vi ký tự của bạn. – ghostdog74

+0

@ghostdog - được cập nhật, cảm ơn. Tôi * làm * nhận được kết quả ngay cả khi có, nhưng đó là bash 4. Tôi đồng ý rằng vòng lặp đọc chậm - lọc một lần ở phía đầu vào nhiều, tốt hơn nhiều so với lọc bên trong vòng lặp của bạn, và bạn * có * để có một vòng lặp nếu bạn sắp khớp nhiều hơn một dòng. –

+0

@CharlesDuffy Tôi gues bạn có thể trả lời https://unix.stackexchange.com/a/413680/176227 giải pháp tương tự cần thiết. Chỉ cần sửa regex. – alhelal

Các vấn đề liên quan