Grep Match và giải nén


10

Tôi có một tập tin chứa các dòng như

proto=tcp/http  sent=144        rcvd=52 spkt=3 
proto=tcp/https  sent=145        rcvd=52 spkt=3
proto=udp/dns  sent=144        rcvd=52 spkt=3

Tôi cần phải trích xuất các giá trị của proto đó là tcp/http, tcp/https, udp/dns.

Cho đến nay tôi đã thử điều này grep -o 'proto=[^/]*/'nhưng chỉ có thể trích xuất giá trị như proto=tcp/.


3
Bản sao có thể
Julien Lopez

Đây là một công việc cho sed, awkhoặc perl, không grep.
OrangeDog

Câu trả lời:


1

Giả sử điều này có liên quan đến câu hỏi trước đó của bạn , bạn đang đi sai đường. Thay vì cố gắng ghép các đoạn script sẽ loại / sắp xếp làm những gì bạn muốn hầu hết thời gian và cần có một tập lệnh hoàn toàn khác nhau mỗi khi bạn cần làm bất cứ điều gì khác biệt một chút, chỉ cần tạo 1 tập lệnh có thể phân tích cú pháp của bạn nhập tệp vào một mảng ( f[]bên dưới) ánh xạ tên trường (thẻ) của bạn tới các giá trị của chúng và sau đó bạn có thể làm bất cứ điều gì bạn muốn với kết quả, ví dụ: đưa ra tệp đầu vào này từ câu hỏi trước của bạn:

$ cat file
Feb             3       0:18:51 17.1.1.1                      id=firewall     sn=qasasdasd "time=""2018-02-03"     22:47:55        "UTC""" fw=111.111.111.111       pri=6    c=2644        m=88    "msg=""Connection"      "Opened"""      app=2   n=2437       src=12.1.1.11:49894:X0       dst=4.2.2.2:53:X1       dstMac=42:16:1b:af:8e:e1        proto=udp/dns   sent=83 "rule=""5"      "(LAN->WAN)"""

chúng ta có thể viết một tập lệnh awk tạo ra một mảng các giá trị được lập chỉ mục theo tên / thẻ của chúng:

$ cat tst.awk
{
    f["hdDate"] = $1 " " $2
    f["hdTime"] = $3
    f["hdIp"]   = $4
    sub(/^([^[:space:]]+[[:space:]]+){4}/,"")

    while ( match($0,/[^[:space:]]+="?/) ) {
        if ( tag != "" ) {
            val = substr($0,1,RSTART-1)
            gsub(/^[[:space:]]+|("")?[[:space:]]*$/,"",val)
            f[tag] = val
        }

        tag = substr($0,RSTART,RLENGTH-1)
        gsub(/^"|="?$/,"",tag)

        $0 = substr($0,RSTART+RLENGTH)
    }

    val = $0
    gsub(/^[[:space:]]+|("")?[[:space:]]*$/,"",val)
    f[tag] = val
}

và được cho rằng bạn có thể làm bất cứ điều gì bạn muốn với dữ liệu của mình, chỉ cần tham chiếu nó bằng tên trường, ví dụ: sử dụng GNU awk -eđể dễ dàng trộn tập lệnh trong tệp với tập lệnh dòng lệnh:

$ awk -f tst.awk -e '{for (tag in f) printf "f[%s]=%s\n", tag, f[tag]}' file
f[fw]=111.111.111.111
f[dst]=4.2.2.2:53:X1
f[sn]=qasasdasd
f[hdTime]=0:18:51
f[sent]=83
f[m]=88
f[hdDate]=Feb 3
f[n]=2437
f[app]=2
f[hdIp]=17.1.1.1
f[src]=12.1.1.11:49894:X0
f[c]=2644
f[dstMac]=42:16:1b:af:8e:e1
f[msg]="Connection"      "Opened"
f[rule]="5"      "(LAN->WAN)"
f[proto]=udp/dns
f[id]=firewall
f[time]="2018-02-03"     22:47:55        "UTC"
f[pri]=6

$ awk -f tst.awk -e '{print f["proto"]}' file
udp/dns

$ awk -f tst.awk -e 'f["proto"] ~ /udp/ {print f["sent"], f["src"]}' file
83 12.1.1.11:49894:X0

2
Điều này thật tuyệt vời, Cảm ơn bạn rất nhiều :)
user356831

Đối với loại công việc này, perlcó thể dễ sử dụng hơn.
OrangeDog

1
@OrangeDog tại sao bạn nghĩ vậy? Tôi thực sự muốn thấy tương đương trong perl nếu bạn không muốn đăng câu trả lời như vậy. Perl chắc chắn sẽ không dễ sử dụng hơn nếu tôi không có nó trong hộp của mình và không thể cài đặt nó, tuy nhiên, đó là điều mà tôi thường xuyên phải đối phó trong nhiều năm qua. Awk, mặt khác là một tiện ích bắt buộc và vì vậy luôn có mặt trên các bản cài đặt UNIX, giống như sed, grep, sort, v.v.
Ed Morton

@EdMorton đúng, mặc dù cá nhân tôi chưa bao giờ gặp phải một bản phân phối mà perl không được bao gồm theo mặc định. Phức tạp awksedtập lệnh thường đơn giản hơn perlvì về cơ bản nó là siêu bộ của chúng, với các tính năng bổ sung cho các tác vụ thông thường.
OrangeDog

@OrangeDog không ai nên viết một tập lệnh sed phức tạp hơn s/old/new/gvà sed không phải là awk vì vậy hãy đặt nó sang một bên. Tôi hoàn toàn không đồng ý rằng các kịch bản awk phức tạp đơn giản hơn trong perl. Tất nhiên chúng có thể dễ dàng hơn nhưng sự ngắn gọn không phải là một thuộc tính mong muốn của phần mềm, sự đồng nhất là và chúng cực kỳ hiếm khi có bất kỳ lợi ích thực sự nào cộng với chúng thường khó đọc hơn, đó là lý do tại sao mọi người đăng những thứ như zoitz.com / archives / 13 about perl và gọi nó là ngôn ngữ chỉ viết, không giống như awk. Tôi vẫn muốn thấy một perl tương đương với điều này mặc dù
Ed Morton

13

Với grep -o, bạn sẽ phải khớp chính xác những gì bạn muốn giải nén. Vì bạn không muốn trích xuất proto=chuỗi, bạn không nên khớp chuỗi đó.

Một biểu thức chính quy mở rộng sẽ khớp với tcphoặc udptheo dấu gạch chéo và một chuỗi ký tự chữ và số không trống là

(tcp|udp)/[[:alnum:]]+

Áp dụng điều này trên dữ liệu của bạn:

$ grep -E -o '(tcp|udp)/[[:alnum:]]+' file
tcp/http
tcp/https
udp/dns

Để đảm bảo rằng chúng tôi chỉ làm điều này trên các dòng bắt đầu bằng chuỗi proto=:

grep '^proto=' file | grep -E -o '(tcp|udp)/[[:alnum:]]+'

Với sed, xóa mọi thứ trước ký tự đầu tiên =và sau ký tự trống đầu tiên:

$ sed 's/^[^=]*=//; s/[[:blank:]].*//' file
tcp/http
tcp/https
udp/dns

Để đảm bảo rằng chúng tôi chỉ thực hiện điều này trên các dòng bắt đầu bằng chuỗi proto=, bạn có thể chèn cùng một bước xử lý trước grepnhư trên hoặc bạn có thể sử dụng

sed -n '/^proto=/{ s/^[^=]*=//; s/[[:blank:]].*//; p; }' file

Ở đây, chúng tôi triệt tiêu đầu ra mặc định bằng -ntùy chọn, và sau đó chúng tôi kích hoạt các thay thế và một bản in rõ ràng của dòng chỉ khi dòng khớp ^proto=.


Với awk, bằng cách sử dụng dấu tách trường mặc định, sau đó tách trường đầu tiên trên =và in bit thứ hai của nó:

$ awk '{ split($1, a, "="); print a[2] }' file
tcp/http
tcp/https
udp/dns

Để đảm bảo rằng chúng tôi chỉ thực hiện điều này trên các dòng bắt đầu bằng chuỗi proto=, bạn có thể chèn cùng một bước xử lý trước grepnhư trên hoặc bạn có thể sử dụng

awk '/^proto=/ { split($1, a, "="); print a[2] }' file

10

Nếu bạn đang sử dụng GNU grep (cho -Ptùy chọn), bạn có thể sử dụng:

$ grep -oP 'proto=\K[^ ]*' file
tcp/http
tcp/https
udp/dns

Ở đây chúng tôi khớp proto=chuỗi, để đảm bảo rằng chúng tôi đang trích xuất cột chính xác, nhưng sau đó chúng tôi loại bỏ nó khỏi đầu ra với \Kcờ.

Các giả định ở trên cho rằng các cột được phân tách bằng dấu cách. Nếu các tab cũng là một dấu tách hợp lệ, bạn sẽ sử dụng \Sđể khớp với các ký tự không phải khoảng trắng, vì vậy lệnh sẽ là:

grep -oP 'proto=\K\S*' file

Nếu bạn cũng muốn bảo vệ chống lại các trường khớp trong đó proto=là một chuỗi con, chẳng hạn như a thisisnotaproto=tcp/https, bạn có thể thêm ranh giới từ với \bnhư vậy:

grep -oP '\bproto=\K\S*' file

1
Bạn có thể cải thiện điều đó bằng cách viết grep -oP 'proto=\K\S+'. Có proto=tcp/httpthể được theo sau bởi một tab thay vì khoảng trắng và \Skhông giống như [^ ]bất kỳ ký tự không phải không gian nào.
mosvy

@mosvy: Đó là một gợi ý tốt, cảm ơn.
user000001

1
Dù sao, -olà một GNUism là tốt. -Pchỉ được GNU hỗ trợ grepnếu được xây dựng với hỗ trợ PCRE (tùy chọn tại thời điểm xây dựng).
Stéphane Chazelas

6

Sử dụng awk:

awk '$1 ~ "proto" { sub(/proto=/, ""); print $1 }' input

$1 ~ "proto"sẽ đảm bảo chúng tôi chỉ thực hiện hành động trên các dòng prototrong cột đầu tiên

sub(/proto=/, "")sẽ loại bỏ proto=khỏi đầu vào

print $1 in cột còn lại


$ awk '$1 ~ "proto" { sub(/proto=/, ""); print $1 }' input
tcp/http
tcp/https
udp/dns

3

Code golfing trên các grepgiải pháp

grep -Po "..p/[^ ]+" file

hoặc thậm chí

grep -Po "..p/\S+" file


2

Chỉ là một grepgiải pháp khác :

grep -o '[^=/]\+/[^ ]\+' file

Và một cái tương tự với sedchỉ in nhóm bắt được:

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

1

Một awkcách tiếp cận khác :

$ awk -F'[= ]' '/=(tc|ud)p/{print $2}' file
tcp/http
tcp/https
udp/dns

Điều đó sẽ đặt dấu tách trường của awk thành một =hoặc một khoảng trắng. Sau đó, nếu dòng khớp với a =, thì udhoặc tctheo sau là a p, in trường thứ 2.

Một sedcách tiếp cận khác (không khả dụng cho tất cả các phiên bản của sed, nhưng hoạt động với GNU sed):

$ sed -En 's/^proto=(\S+).*/\1/p' file 
tcp/http
tcp/https
udp/dns

-nnghĩa là "không in" và -Echo phép các biểu thức chính quy mở rộng cung cấp cho chúng tôi \S"không phải khoảng trắng", +cho "một hoặc nhiều" và dấu ngoặc đơn để chụp. Cuối cùng, /pở cuối sẽ làm cho sed in một dòng chỉ khi thao tác thành công vì vậy nếu có một kết quả khớp cho toán tử thay thế.

Và, một perl một:

$ perl -nle '/^proto=(\S+)/ && print $1' file 
tcp/http
tcp/https
udp/dns

-nnghĩa là "đọc dòng tệp đầu vào theo dòng và áp dụng tập lệnh được cung cấp -echo từng dòng". Việc -lthêm một dòng mới vào mỗi printcuộc gọi (và loại bỏ các dòng mới ra khỏi đầu vào). Bản thân đoạn script sẽ in đoạn ký tự không phải khoảng trắng dài nhất được tìm thấy sau a proto=.


1
-Engày càng di động, nhưng \Skhông. [^[:space:]]là một tương đương di động hơn.
Stéphane Chazelas

1

Đây là một giải pháp khá dễ dàng:

grep -o "[tc,ud]*p\\/.*  "   INPUTFile.txt  |   awk '{print $1}'

Bạn grepkhông phù hợp với bất cứ điều gì. [tc,ud]\*\\/.*tìm kiếm một lần xuất hiện của một trong hai t, hoặc c, hoặc ,, uhoặc dtheo sau là một ký *tự theo nghĩa đen , sau đó plà dấu gạch chéo ngược. Bạn có thể có nghĩa grep -Eo '(tc|ud)p/.* ' file | awk '{print $1}'. Nhưng sau đó, nếu bạn đang sử dụng awk, bạn cũng có thể làm toàn bộ trong awk : awk -F'[= ]' '/(tc|ud)p/{print $2}' file.
terdon

Ai đó đã sửa đổi bản gốc của tôi, có thêm một Dấu gạch chéo ngược trước sao, mà tôi vừa xóa Ngài.
mkzia

Cảm ơn bạn đã chỉnh sửa, nhưng tôi sợ rằng chỉ hoạt động tình cờ. Như tôi đã giải thích trước đây, [tc,ud]pcó nghĩa là "một trong những t, c, ,, uhoặc dtheo sau là một p. Vì vậy, nó phù hợp ở đây chỉ vì tcpcpudpdp. Nhưng nó cũng sẽ phù hợp ,phoặc tpvv Ngoài ra, bây giờ mà bạn có *, nó sẽ phù hợp pppcũng như (các *có nghĩa là "0 trở lên" vì vậy nó sẽ khớp ngay cả khi nó không khớp). Bạn không muốn có một lớp ký tự ( [ ]), thứ bạn muốn là một nhóm: (tc|ud)(sử dụng với -Ecờ của grep). Ngoài ra, .*làm cho nó phù hợp với toàn bộ dòng.
terdon

1
@Jesse_b: Mặc dù mkzia về mặt kỹ thuật không phải là người đóng góp mới, nhưng họ là một người dùng thiếu kinh nghiệm, bằng chứng là họ không sử dụng định dạng mã cho lệnh của họ. Tuy nhiên, họ đã đủ thông minh để gõ \*để lệnh đầu tiên *xuất hiện dưới dạng * và không phải là dấu in nghiêng. Khi bạn đặt lệnh thành định dạng mã, bạn đã gây ra \trước khi *xuất hiện (do đó khiến lệnh bị lỗi). Khi bạn chỉnh sửa bài đăng của người khác, vui lòng xem thay đổi giao diện của bài đăng như thế này.
G-Man nói 'Phục hồi Monica'

@terdon: (1) Không, thực sự nó sẽ không khớp ppp. Tất nhiên bạn nói đúng rằng nó sẽ phù hợp ,phoặc  tp- hoặc uucp, ttp, cutp, ductphoặc d,up.
G-Man nói 'Phục hồi Monica'


0
cat file| cut -f1 -d' '| cut -f2 -d'='
tcp/http
tcp/https
udp/dns

cắt tùy chọn:

  • -f - cánh đồng
  • -d - đồng hồ đo
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.