Grep Match และสารสกัด


10

ฉันมีไฟล์ที่มีบรรทัดเป็น

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

ฉันต้องการที่จะดึงค่าของโปรโตซึ่งเป็นtcp/http, ,tcp/httpsudp/dns

จนถึงขณะนี้ผมได้พยายามนี้แต่สามารถดึงค่าเป็นgrep -o 'proto=[^/]*/'proto=tcp/



นี่คืองานสำหรับsed, awkหรือไม่perl grep
OrangeDog

คำตอบ:


1

สมมติว่าสิ่งนี้เกี่ยวข้องกับคำถามก่อนหน้าของคุณคุณจะผิดทาง แทนที่จะพยายามที่จะรวมกันเป็นส่วนหนึ่งของสคริปต์ที่จะ kinda / sorta ทำสิ่งที่คุณต้องการมากที่สุดของเวลาและต้องการที่จะได้รับสคริปต์ที่แตกต่างกันอย่างสมบูรณ์ทุกครั้งที่คุณต้องทำอะไรที่แตกต่างกันเล็กน้อยเพียงแค่สร้างสคริปต์ที่ 1 ใส่ไฟล์ลงในอาร์เรย์ ( f[]ด้านล่าง) ที่แมปชื่อฟิลด์ของคุณ (แท็ก) กับค่าของพวกเขาและจากนั้นคุณสามารถทำสิ่งที่คุณต้องการด้วยผลลัพธ์เช่นให้ไฟล์อินพุตนี้จากคำถามก่อนหน้าของคุณ:

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

เราสามารถเขียนสคริปต์ awk ที่สร้างอาร์เรย์ของค่าที่จัดทำดัชนีโดยชื่อ / แท็ก:

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

และให้คุณสามารถทำสิ่งที่คุณต้องการกับข้อมูลของคุณเพียงแค่อ้างอิงโดยใช้ชื่อเขตข้อมูลเช่นใช้ GNU awk -eเพื่อความสะดวกในการผสมสคริปต์ในไฟล์ด้วยสคริปต์บรรทัดคำสั่ง:

$ 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
นี่มันยอดเยี่ยมมากขอบคุณมาก :)
user356831

สำหรับงานประเภทนี้perlอาจใช้งานง่ายกว่า
OrangeDog

1
@ OrangeDog ทำไมคุณถึงคิดอย่างนั้น? ฉันอยากจะเห็นความเท่าเทียมใน Perl ถ้าคุณไม่รังเกียจที่จะโพสต์คำตอบดังกล่าว Perl แน่นอนว่าจะไม่ใช้ง่ายกว่านี้ถ้าฉันไม่มีในกล่องและไม่สามารถติดตั้งได้ซึ่งเป็นสิ่งที่ฉันต้องรับมือเป็นประจำตลอดหลายปีที่ผ่านมา Awk เป็นเครื่องมือที่จำเป็นและมีอยู่เสมอในการติดตั้ง UNIX เช่น sed, grep, sort และอื่น ๆ
Ed Morton

@EdMorton จริง แต่ฉันไม่เคยพบการกระจายตัวที่ Perl ไม่รวมโดยค่าเริ่มต้น คอมเพล็กซ์awkและสคsedริปมักจะง่ายกว่าperlเพราะส่วนใหญ่เป็นชุดของมันพร้อมกับคุณสมบัติเพิ่มเติมสำหรับงานทั่วไป
OrangeDog

@OrangeDog ไม่มีใครเลยที่ควรจะเขียนสคริปต์ที่ซับซ้อนกว่าs/old/new/gและ sed ไม่ใช่ awk ดังนั้นให้ตั้งค่าไว้ ฉันไม่เห็นด้วยอย่างยิ่งว่าสคริปต์ awk ที่ซับซ้อนนั้นง่ายกว่าในภาษา Perl แน่นอนว่ามันสั้นกว่า แต่ความสั้นไม่ใช่คุณสมบัติที่ต้องการของซอฟต์แวร์ความรัดกุมคือและมันหายากมากสำหรับพวกเขาที่จะได้รับประโยชน์ที่แท้จริงและพวกเขามักอ่านยากมากซึ่งเป็นสาเหตุที่ผู้คนโพสต์สิ่งต่าง ๆ เช่นzoitz.com / archives / 13เกี่ยวกับ Perl และอ้างถึงเป็นภาษาเขียนอย่างเดียวซึ่งแตกต่างจาก awk ฉันยังคงต้องการที่จะเห็น perl เทียบเท่ากับสิ่งนี้
Ed Morton

13

ด้วยgrep -oคุณจะต้องตรงกับสิ่งที่คุณต้องการแยก เนื่องจากคุณไม่ต้องการแยกproto=สตริงคุณไม่ควรจับคู่ให้ตรงกัน

นิพจน์ปกติที่ขยายซึ่งจะจับคู่กับtcpหรือudpตามด้วยเครื่องหมายสแลชและสตริงตัวอักษรผสมตัวเลขที่ไม่ว่างเปล่าคือ

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

ใช้สิ่งนี้กับข้อมูลของคุณ:

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

เพื่อให้แน่ใจว่าเราจะทำเช่นนี้กับบรรทัดที่ขึ้นต้นด้วยสตริงproto=:

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

ด้วยsedการลบทุกอย่างก่อน=อักขระตัวแรกและหลังอักขระตัวแรก:

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

เพื่อให้แน่ใจว่าเราจะทำสิ่งนี้ในบรรทัดที่ขึ้นต้นด้วยสตริงproto=คุณสามารถแทรกขั้นตอนก่อนการประมวลผลเดียวกันกับgrepข้างต้นหรือคุณสามารถใช้

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

ที่นี่เราปราบปรามออกเริ่มต้นที่มีตัวเลือกและจากนั้นเราเรียกแทนและการพิมพ์อย่างชัดเจนของเส้นเฉพาะในกรณีที่ตรงกับสาย-n^proto=


ด้วยawkโดยใช้ตัวคั่นฟิลด์เริ่มต้นจากนั้นแยกฟิลด์แรกบน=และพิมพ์บิตที่สองของมัน:

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

เพื่อให้แน่ใจว่าเราจะทำสิ่งนี้ในบรรทัดที่ขึ้นต้นด้วยสตริงproto=คุณสามารถแทรกขั้นตอนก่อนการประมวลผลเดียวกันกับgrepข้างต้นหรือคุณสามารถใช้

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

10

หากคุณใช้ GNU grep (สำหรับ-Pตัวเลือก) คุณสามารถใช้:

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

ที่นี่เราจับคู่proto=สตริงเพื่อให้แน่ใจว่าเรากำลังแยกคอลัมน์ที่ถูกต้อง แต่จากนั้นเราจะละทิ้งมันจากเอาต์พุตด้วย\Kแฟล็ก

ด้านบนจะถือว่าคอลัมน์นั้นถูกคั่นด้วยช่องว่าง หากแท็บเป็นตัวคั่นที่ถูกต้องคุณจะใช้\Sเพื่อจับคู่อักขระที่ไม่ใช่ช่องว่างดังนั้นคำสั่งจะเป็น:

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

หากคุณต้องการป้องกันเขตข้อมูลที่ตรงกันซึ่งproto=เป็นสตริงย่อยเช่น a thisisnotaproto=tcp/httpsคุณสามารถเพิ่มขอบเขตของคำด้วย\b:

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

1
grep -oP 'proto=\K\S+'คุณสามารถปรับปรุงที่โดยการเขียนเพียง proto=tcp/httpอาจจะตามด้วยแท็บแทนของพื้นที่และ\Sแตกต่าง[^ ]จะตรงกับตัวละครที่ไม่ใช่พื้นที่ใด
mosvy

@mosvy: นั่นเป็นคำแนะนำที่ดีขอบคุณ
user000001

1
อย่างไรก็ตาม-oมันก็เป็น GNUism เช่นกัน -Pได้รับการสนับสนุนโดย GNU grepหากสร้างขึ้นด้วยการสนับสนุน PCRE (เป็นทางเลือกในเวลาสร้าง)
Stéphane Chazelas

6

การใช้awk:

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

$1 ~ "proto"จะทำให้แน่ใจว่าเราจะดำเนินการกับบรรทัดที่มีprotoในคอลัมน์แรกเท่านั้น

sub(/proto=/, "")จะลบออกproto=จากอินพุต

print $1 พิมพ์คอลัมน์ที่เหลือ


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

3

รหัสกอล์ฟในการgrepแก้ปัญหา

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

หรือแม้กระทั่ง

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


2

อีกgrepวิธีหนึ่ง:

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

และกลุ่มที่คล้ายกันที่มีsedการพิมพ์เฉพาะกลุ่มที่จับคู่ที่ตรงกัน:

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

1

awkวิธีอื่น:

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

ที่จะตั้งคั่นฟิลด์ของ awk เป็นอย่างใดอย่างหนึ่ง=หรือช่องว่าง แล้วถ้าเส้นตรง=แล้วทั้งudหรือtcตามด้วยpพิมพ์เขต 2

sedวิธีอื่น(ไม่สามารถพกพาได้ในทุกรุ่นsedแต่ใช้ได้กับ GNU sed):

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

-nหมายถึง "ไม่ได้พิมพ์" และ-Eช่วยให้การแสดงออกปกติขยายที่ให้เรา\Sสำหรับ "ไม่ใช่ช่องว่าง" +สำหรับ "หนึ่งหรือมากกว่าหนึ่ง" และวงเล็บสำหรับการจับภาพ ในที่สุด/pตอนท้ายจะทำการพิมพ์บรรทัดต่อเมื่อการดำเนินการสำเร็จดังนั้นหากมีการจับคู่สำหรับผู้ดำเนินการทดแทน

และหนึ่ง perl:

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

-nหมายถึง "อ่านบรรทัดแฟ้มใส่โดยสายและใช้สคริปต์ที่กำหนดโดย-eแต่ละเส้น" -lเพิ่มขึ้นบรรทัดใหม่ให้กับแต่ละprintโทร (การขึ้นบรรทัดใหม่และลบออกจากการป้อนข้อมูล) proto=สคริปต์ที่ตัวเองจะพิมพ์ยาวที่สุดของตัวละครที่ไม่ใช่ช่องว่างที่พบหลังจากที่


1
-Eกำลังพกพาเพิ่มขึ้นเรื่อย ๆ แต่\Sไม่ใช่ [^[:space:]]เป็นแบบพกพาที่เทียบเท่า
Stéphane Chazelas

1

นี่เป็นอีกวิธีหนึ่งที่ค่อนข้างง่าย:

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

คุณgrepไม่ตรงกับอะไรเลย [tc,ud]\*\\/.*ค้นหาหนึ่งเหตุการณ์ที่เกิดขึ้นอย่างใดอย่างหนึ่งtหรือcหรือ,หรือuหรือdตามด้วยตัวอักษร*ตัวอักษรแล้วpและและเครื่องหมายทับขวา grep -Eo '(tc|ud)p/.* ' file | awk '{print $1}'คุณอาจหมายถึง แต่แล้วถ้าคุณกำลังใช้ awk คุณอาจเป็นอย่างดีเลยสิ่งที่ทั้งใน awk -F'[= ]' '/(tc|ud)p/{print $2}' fileawk:
terdon

มีคนแก้ไขต้นฉบับของฉันมีแบ็กสแลชเสริมก่อนที่จะติดดาวซึ่งฉันเพิ่งลบเซอร์
mkzia

ขอบคุณสำหรับการแก้ไข แต่ฉันกลัวว่าจะได้ผลโดยบังเอิญเท่านั้น ขณะที่ผมอธิบายก่อน[tc,ud]pหมายถึง "หนึ่งt, c, ,, uหรือdตามด้วยp. ดังนั้นมันตรงกับที่นี่เพียงเพราะtcpมีcpและudpมีdp. แต่ก็ยังจะตรง,pหรือtpฯลฯ นอกจากนี้ตอนนี้ที่คุณมี*มันจะตรงกับการpppได้เป็นอย่างดี (คน*หมายถึง "0 หรือมากกว่า" ดังนั้นมันจะจับคู่แม้ว่าจะไม่ตรงกัน) คุณไม่ต้องการคลาสอักขระ ( [ ]) สิ่งที่คุณต้องการคือกลุ่ม: (tc|ud)(ใช้กับ-Eธงของgrep) นอกจากนี้ยัง.*ทำให้ จับคู่ทั้งบรรทัด
terdon

1
@Jesse_b: ในขณะที่ mkzia ไม่ได้เป็น“ ผู้มีส่วนร่วมใหม่” ในทางเทคนิคพวกเขาเป็นผู้ใช้ที่ไม่มีประสบการณ์ตามหลักฐานจากข้อเท็จจริงที่ว่าพวกเขาไม่ได้ใช้การจัดรูปแบบรหัสสำหรับคำสั่งของพวกเขา และถึงกระนั้นพวกเขาก็ฉลาดพอที่จะพิมพ์\*เพื่อให้ได้คนแรก*ในคำสั่งของพวกเขาที่จะปรากฏเป็น * และไม่เป็นตัวเอียงที่เป็นตัวเอียง เมื่อคุณวางคำสั่งในรูปแบบรหัสคุณทำให้คำสั่ง\ก่อนหน้า*ปรากฏขึ้น (ซึ่งทำให้คำสั่งล้มเหลว) เมื่อคุณแก้ไขโพสต์ของคนอื่นโปรดระวังการเปลี่ยนแปลงลักษณะที่ปรากฏของโพสต์เช่นนี้
G-Man กล่าวว่า 'Reinstate Monica'

@terdon: (1) pppไม่จริงมันจะไม่ตรงกับ แน่นอนคุณขวากำลังว่ามันจะตรง,pหรือ  tp- หรือuucp, ttp, cutp, หรือductp d,up
G-Man กล่าวว่า 'Reinstate Monica'


โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.