Tìm tất cả các lần xuất hiện trong một tập tin với sed


15

Sử dụng hệ điều hành MỞ BƯỚC 4.2 ... Tôi hiện đang sử dụng sedLệnh sau :

sed -n '1,/141.299.99.1/p' TESTFILE | tail -3

Lệnh này sẽ tìm thấy một thể hiện trong một tệp có ip 141.299.99.1 và cũng bao gồm 3 dòng trước đó là tốt, ngoại trừ tôi cũng muốn tìm tất cả các phiên bản của IP và 3 dòng trước nó và không chỉ đầu tiên.


1
Hãy luôn luôn bao gồm hệ điều hành của bạn. Các giải pháp rất thường phụ thuộc vào Hệ điều hành đang được sử dụng. Bạn đang sử dụng Unix, Linux, BSD, OSX, cái gì khác? Phiên bản nào?
terdon

ĐIỂM TUYỆT VỜI! Sử dụng Open Step phiên bản 4.2 khá cũ và các vỏ được bao gồm không bao gồm nhiều tính năng được đề cập trong các câu trả lời dưới đây.
Dale

Vì tò mò - hệ thống MỞ BƯỚC 4.2 là gì và nó được sử dụng cho ngày hôm nay là gì?
Thorbjørn Ravn Andersen

(và nếu Perl có sẵn, bạn thực sự có thể làm rất nhiều điều tốt đẹp chỉ với điều đó)
Thorbjørn Ravn Andersen

@ ThorbjørnRavnAndersen Có lẽ đây là: en.wikipedia.org/wiki/OpenStep
Barmar

Câu trả lời:


4

Đây là một nỗ lực mô phỏng grep -B3bằng cách sử dụng cửa sổ chuyển động sed, dựa trên ví dụ về sed sed GNU này (nhưng hy vọng tuân thủ POSIX - với sự thừa nhận với @ StéphaneChazelas):

sed -e '1h;2,4{;H;g;}' -e '1,3d' -e '/141\.299\.99\.1/P' -e '$!N;D' file

Hai biểu thức đầu tiên là một bộ đệm mẫu nhiều dòng và cho phép nó xử lý trường hợp cạnh trong đó có ít hơn 3 dòng bối cảnh trước trước khi khớp đầu tiên. Biểu thức giữa (khớp regex) in một dòng ra khỏi đầu cửa sổ cho đến khi văn bản khớp mong muốn được gợn lên thông qua bộ đệm mẫu. Cuối cùng $!N;Dcuộn cửa sổ theo một dòng trừ khi nó đến cuối đầu vào.


-ekhông phải là GNU cụ thể. Để trở thành POSIX / di động, bạn cần nó vì không thể có bất cứ thứ gì sau đó }(và bạn cần một cái ;trước nó).
Stéphane Chazelas

Cảm ơn @ StéphaneChazelas - vì vậy bạn có nói rằng để trở thành POSIX / di động, nhóm đầu tiên cần được phân tách / sửa đổi thành -e '1h;2,4{H;g;}' -e '1,3d'? Tôi không có hệ thống không phải GNU để kiểm tra (và công tắc GNU sed --posixdường như không quan tâm).
Steeldo

1
Có, trên Linux, bạn có thể kiểm tra một triển khai khác với sed từ công cụ gia truyền, là hậu duệ của dòng máy Unix truyền thống. Spec POSIX / Unix cho sedpubs.opengroup.org/onlinepubs/9699919799/utilities/sed.html
Stéphane Chazelas

Tôi đang nhận được sự kiện không được tìm thấy ở một trong hai điều sau: N; D ': Không tìm thấy sự kiện. Tôi có thiếu cú ​​pháp ở đâu đó không? Cảm ơn!!
Dale

Xin lỗi, tôi vừa nhận ra bản chỉnh sửa gần đây nhất của mình đã bỏ qua một trích dẫn kết thúc sau biểu thức -e đầu tiên. Tôi đã sửa nó ngay bây giờ - bạn có thể thử lại với biểu thức trên không?
Steeldo

10

grep sẽ làm tốt hơn việc này:

grep -B 3 141.299.99.1 TESTFILE

Các -B 3phương tiện để in ba dòng trước mỗi trận đấu. Điều này sẽ in --giữa mỗi nhóm dòng. Để vô hiệu hóa, sử dụng --no-group-separatorlà tốt.

Các -Btùy chọn được hỗ trợ bởi GNUgrep và hầu hết các phiên bản BSD cũng ( OSX , FreeBSD , OpenBSD , NetBSD ), nhưng nó là về mặt kỹ thuật không phải là một lựa chọn tiêu chuẩn.


1
Michael Homer - Cảm ơn bạn. Tôi không có tùy chọn - B. Còn ý tưởng nào khác không?
Dale

@Dale Bạn có thể cài đặt GNU grep không? Điều đó sẽ cung cấp cho bạn tùy chọn.
Barmar

9

Với sedbạn có thể làm một cửa sổ trượt.

sed '1N;$!N;/141.299.99.1/P;D'

Nó làm điều đó. Nhưng hãy cẩn thận - bashhành vi điên rồ của việc mở rộng ! ngay cả khi được trích dẫn !!! vào chuỗi lệnh từ lịch sử lệnh của bạn có thể làm cho nó trở nên hơi điên rồ. Tiền tố lệnh vớiset +H; nếu bạn thấy đây là trường hợp. Để sau đó kích hoạt lại nó (nhưng tại sao ???) làm set -Hsau đó.

Điều đó, tất nhiên, sẽ chỉ áp dụng nếu bạn đang sử dụng bash- mặc dù tôi không tin là bạn. Tôi khá chắc chắn rằng bạn đang làm việc với csh- (đó là cái vỏ có hành vi điên rồ bashmô phỏng với việc mở rộng lịch sử, nhưng có lẽ không phải là cực đoan mà vỏ c đã lấy nó) . Vì vậy, có lẽ là một \!nên làm việc. Tôi hi vọng.

Đó là tất cả mã di động: POSIX mô tả ba nhà khai thác của nó do đó: (mặc dù đáng lưu ý rằng tôi chỉ xác nhận mô tả này tồn tại vào đầu năm 2001)

[2addr]N Nối dòng đầu vào tiếp theo, trừ \newline kết thúc của nó vào không gian mẫu, sử dụng \newline nhúng để tách vật liệu được nối với vật liệu ban đầu. Lưu ý rằng số dòng hiện tại thay đổi.

[2addr]P Viết không gian mẫu, lên đến \newline đầu tiên , đến đầu ra tiêu chuẩn.

[2addr]D Xóa phân đoạn ban đầu của không gian mẫu thông qua \newline đầu tiên và bắt đầu chu trình tiếp theo.

Vì vậy, trên dòng đầu tiên bạn thêm một dòng bổ sung vào không gian mẫu, vì vậy nó trông như thế này:

^line 1s contents\nline 2s contents$

Sau đó, trên dòng đầu tiên và mỗi dòng sau đó - ngoại trừ dòng cuối cùng - bạn thêm dòng khác dòng vào không gian mẫu. Vì vậy, nó trông như thế này:

^line 1\nline 2\nline 3$

Nếu địa chỉ IP của bạn được tìm thấy trong bạn Pcho đến dòng mới đầu tiên, vì vậy chỉ cần dòng 1 ở đây. Vào cuối mỗi chu kỳ, bạn Dbắt đầu giống nhau và bắt đầu lại với những gì còn lại. Vì vậy, chu kỳ tiếp theo trông như sau:

^line 2\nline 3\nline 4$

...và như thế. Nếu ip của bạn được tìm thấy trên bất kỳ một trong ba cái cũ nhất sẽ in ra - mọi lúc. Vì vậy, bạn luôn luôn chỉ có ba dòng phía trước.

Đây là một ví dụ nhanh. Tôi sẽ nhận được bộ đệm ba dòng được in cho mỗi số kết thúc bằng 0:

seq 10 52 | sed '1N;$!N;/0\(\n\|$\)/P;D'

10
18
19
20
28
29
30
38
39
40
48
49
50

Đó là một chút phức tạp hơn trường hợp của bạn bởi vì tôi phải thay thế từ một trong hai 0\n dòng mới hoặc 0$cuối không gian mẫu để gần giống với vấn đề của bạn hơn - nhưng chúng khác biệt một cách tinh tế ở chỗ điều này đòi hỏi một cái neo - có thể hơi khó thực hiện vì không gian mô hình liên tục thay đổi.

Tôi đã sử dụng các trường hợp lẻ 10 và 52 để chỉ ra rằng miễn là neo linh hoạt thì đầu ra cũng vậy. Hoàn toàn có thể, tôi có thể đạt được kết quả tương tự bằng cách thay vào đó dựa vào thuật toán và thực hiện:

seq 10 52 | sed '1N;$!N;/[90]\n/P;D'

Và mở rộng tìm kiếm trong khi giới hạn cửa sổ của tôi - từ 0 đến 9 và 0 và từ 3 dòng thành hai.

Dù sao, bạn có được ý tưởng.


Cảm ơn tất cả công việc khó khăn của bạn. Xin lỗi, tôi sẽ đặt tên tệp tôi muốn tìm ở đâu?
Dale

@Dale - xấu của tôi. sed '...' $filename. Nhân tiện - tôi đã để lại các khoảng thời gian từ chuỗi tìm kiếm của riêng bạn, nhưng những khoảng đó không thực sự là các giai đoạn trong một mẫu - chúng đại diện cho bất kỳ ký tự đơn lẻ nào. Bạn có lẽ nên làm gì oct\.oct\.oct\.octđể thoát khỏi chúng để chúng chỉ khớp với thời gian.
mikeerv

Tôi đã cố gắng xử lý nó và các biểu tượng <> khác nhau và tôi không tìm thấy sự kiện nào với các giải pháp khác ở đây vì vậy tôi tự hỏi liệu hệ điều hành của tôi không tương thích với các giải pháp này.
Dale

bây giờ kết quả với -> N; /141.299.99.1/P; D ': Không tìm thấy sự kiện.
Dale

@Dale - vui lòng xem cập nhật. Nó sẽ giúp bạn.
mikeerv

4

bạn đề cập rằng bạn không có -Btùy chọn grep, bạn có thể sử dụng Perl (ví dụ) để tạo một cửa sổ trượt gồm 4 dòng:

perl -ne '
    push @window,$_;
    shift @window if @window > 4;
    print @window if /141\.299\.99\.1/
' your_file

Câu trả lời của Ramesh làm một điều tương tự với awk.


Tôi không chắc chắn nếu phiên bản Perl của tôi hỗ trợ điều này nhưng tôi sẽ thử. Cảm ơn bạn rất nhiều vì đã dành thời gian để trả lời câu hỏi của tôi - rất biết ơn!
Dale

@Dale Bạn rất hoan nghênh. Tôi nghi ngờ rằng mã này sử dụng bất kỳ tính năng tiên tiến nào của Perl.
Joseph R.

4

Khi có sẵn, bạn có thể sử dụng pcregrep :

pcregrep -M '.*\n.*\n.*\n141.299.99.1' file

Kiểm tra xem tôi có PCREGREP không. Tôi thích sự gọn nhẹ của lệnh. Rất biết ơn thời gian và nỗ lực của bạn. Cảm ơn bạn!!!
Dale

4

Bạn có thể thực hiện cùng một cách tiếp cận cơ bản như các câu trả lời không phải là grep khác trong chính trình bao (điều này giả định một trình bao tương đối gần đây hỗ trợ =~):

while IFS= read -r line; do 
    [[ $line =~ 141.299.99.1 ]] && printf "%s\n%s\n%s\n%s\n" $a $b $c $line;
    a=$b; b=$c; c=$line; 
done < file 

Ngoài ra, bạn có thể nhét toàn bộ tệp vào một mảng:

perl -e '@F=<>; 
        for($i=0;$i<=$#F;$i++){
          print $F[$i-3],$F[$i-2],$F[$i-1],$F[$i] if $F[$i]=~/141.299.99.1/
        }' file 

Vỏ của tôi rất cũ - Steve Jobs Open Step. Ý tưởng tuyệt vời mặc dù và cảm ơn bạn đã dành thời gian của bạn !!! Dale
Dale

@Dale phương pháp perl sẽ hoạt động ở bất cứ đâu. Vui lòng cho chúng tôi biết hệ điều hành của bạn (thêm nó vào câu hỏi của bạn) theo cách đó chúng tôi có thể đề xuất những thứ sẽ phù hợp với bạn.
terdon

Nếu tôi sao chép Perl của bạn và đặt nó vào NotePad và đặt nó trên một dòng thì nó hoạt động! Câu hỏi - nếu tôi muốn cho phép nói 10 dòng trước mẫu khớp, tôi sẽ thay đổi 3 thành 10 ở đâu? Cảm ơn!
Dale

Tôi thấy rằng tôi có thể thêm nhiều dòng trở lại bằng cách thêm nhiều câu lệnh $ F [$ iX]. Cảm ơn!
Dale

4

Nếu hệ thống của bạn không hỗ trợ grepbối cảnh, bạn có thể thử ack-grep thay thế:

ack -B 3 141.299.99.1 file

ack là một công cụ như grep, được tối ưu hóa cho các lập trình viên.


Tôi thích sự gọn nhẹ của lệnh nhưng hệ thống của tôi không hỗ trợ ack trong việc tìm kiếm trong các trang hướng dẫn. Ý tưởng tuyệt vời và cảm ơn bạn rất nhiều vì thời gian của bạn !!! Dale
Dale

@Dale: Đáng ngạc nhiên! HĐH của bạn là gì? Nếu bạn có perl, bạn có thể sử dụng ack.
cuonglm

2
awk '/141.299.99.1/{for(i=1;i<=x;)print a[i++];print} {for(i=1;i<x;i++)
     a[i]=a[i+1];a[x]=$0;}'  x=3 filename

Trong awkgiải pháp này , một mảng được sử dụng sẽ luôn chứa 3 dòng trước mẫu hiện tại. Do đó, khi mẫu được khớp, nội dung mảng cùng với mẫu hiện tại được in.

Kiểm tra

-bash-3.2$ cat filename
10.0.0.1
10.0.0.2
10.0.0.3
10.0.0.4
141.299.99.1
10.0.0.5
10.0.0.6
10.0.0.7
10.0.0.8
10.0.0.9
10.0.0.10
141.299.99.1
10.0.0.11
10.0.0.12
10.0.0.13
10.0.0.14
10.0.0.15
10.0.0.16
141.299.99.1
10.0.0.17
10.0.0.18
10.0.0.19

Sau khi tôi thực hiện lệnh, đầu ra là,

10.0.0.2
10.0.0.3
10.0.0.4
141.299.99.1
10.0.0.8
10.0.0.9
10.0.0.10
141.299.99.1
10.0.0.14
10.0.0.15
10.0.0.16
141.299.99.1

rất chi tiết - cảm ơn bạn rất nhiều Tôi sẽ thử. Rất biết ơn thời gian của bạn !! Dale
Dale

Tôi có một tập tin thử nghiệm và giải pháp của bạn hoạt động! Vấn đề là khi tôi chạy nó trên tệp sản xuất lớn của mình, nó trở lại với Số bản ghi quá dài để đầu ra không thể hoạt động với lệnh. Lệnh ban đầu của tôi ở đầu trang này hoạt động nhưng chỉ tìm thấy một trường hợp. Tôi đánh giá cao sự giúp đỡ của bạn. Có bất cứ điều gì tôi có thể làm với lệnh ban đầu của mình để làm cho nó tìm thấy nhiều hơn một instatnce không?
Dale

1

Trong hầu hết những điều này, /141.299.99.1/cũng sẽ khớp (ví dụ) 141a299q99+1hoặc 141029969951bởi vì .trong một biểu thức chính quy có thể đại diện cho bất kỳ ký tự nào.

Sử dụng /141[.]299[.]99[.]1/an toàn hơn, và bạn có thể thêm bối cảnh bổ sung ngay từ đầu và kết thúc của toàn regexp để chắc chắn rằng nó không phù hợp 3141., .12, .104vv


1
Đây là một điểm tốt - và tôi cũng đã xem xét. Tuy nhiên, tôi đã sử dụng chuỗi được cung cấp bởi người hỏi như một trận đấu làm việc đã biết - và thông báo cho cá nhân anh ta về điều tương tự khi được cung cấp cơ hội. Dù sao - không phải tất cả trong số này - câu trả lời của Steeldo đã trích dẫn trận đấu char ngay từ đầu.
mikeerv
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.