Grep Match dan ekstrak

10

Saya memiliki file yang berisi baris sebagai

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

Saya perlu untuk mengambil nilai proto yang tcp/http, tcp/https, udp/dns.

Sejauh ini saya sudah mencoba ini grep -o 'proto=[^/]*/'tetapi hanya mampu mengekstraksi nilai sebagai proto=tcp/.

pengguna356831
sumber
Ini adalah pekerjaan untuk sed, awkatau perl, tidak grep.
OrangeDog

Jawaban:

1

Dengan asumsi ini terkait dengan pertanyaan Anda sebelumnya , Anda salah jalan. Daripada mencoba untuk mengumpulkan potongan-potongan skrip yang agak / akan melakukan apa yang paling Anda inginkan sepanjang waktu dan perlu untuk mendapatkan skrip yang sama sekali berbeda setiap kali Anda perlu melakukan apa pun yang sedikit berbeda, buat saja 1 skrip yang dapat menguraikan Anda masukan file ke dalam array (di f[]bawah) yang memetakan nama bidang Anda (tag) ke nilai-nilai mereka dan kemudian Anda bisa melakukan apa pun yang Anda inginkan dengan hasilnya, misalnya memberikan file input ini dari pertanyaan Anda sebelumnya:

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

kita bisa menulis skrip awk yang membuat array nilai yang diindeks dengan nama / tag mereka:

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

dan mengingat bahwa Anda dapat melakukan apa pun yang Anda suka dengan data Anda, rujuk saja dengan nama bidang, mis. menggunakan GNU awk -euntuk memudahkan pencampuran skrip dalam file dengan skrip baris perintah:

$ 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
Ed Morton
sumber
2
Ini luar biasa, Terima kasih banyak :)
user356831
Untuk pekerjaan semacam ini, perlmungkin lebih mudah digunakan.
OrangeDog
1
@OrangeDog mengapa Anda berpikir begitu? Saya sebenarnya ingin melihat yang setara dalam perl jika Anda tidak keberatan memposting jawaban seperti itu. Perl pasti tidak akan lebih mudah digunakan jika saya tidak memilikinya di kotak saya dan tidak dapat menginstalnya, yang merupakan sesuatu yang sering saya harus tangani selama bertahun-tahun. Awk di sisi lain adalah utilitas wajib dan selalu hadir pada instalasi UNIX, seperti halnya sed, grep, sortir, dll.
Ed Morton
@ Edortort benar, meskipun saya tidak pernah secara pribadi menemukan distribusi di mana perl tidak dimasukkan secara default. Kompleks awkdan sedskrip biasanya lebih sederhana perlkarena pada dasarnya merupakan superset dari mereka, dengan fitur tambahan untuk tugas-tugas umum.
OrangeDog
@OrangeDog tidak seorang pun boleh menulis skrip sed yang lebih rumit daripada s/old/new/gdan sed tidak awk jadi mari kita kesampingkan itu. Saya sama sekali tidak setuju bahwa skrip awk yang kompleks lebih mudah di perl. Mereka bisa lebih singkat tentu saja tetapi singkatnya bukan atribut yang diinginkan dari perangkat lunak, keringkasannya, dan sangat jarang bagi mereka untuk mendapatkan manfaat nyata ditambah mereka biasanya jauh lebih sulit untuk membaca itulah sebabnya orang memposting hal-hal seperti zoitz.com / archives / 13 tentang perl dan menyebutnya sebagai bahasa tulis saja, tidak seperti awk. Saya masih ingin melihat perl yang setara dengan ini
Ed Morton
13

Dengan grep -o, Anda harus mencocokkan apa yang ingin Anda ekstrak. Karena Anda tidak ingin mengekstraksi proto=string, Anda tidak harus mencocokkannya.

Ekspresi reguler yang diperluas yang akan cocok dengan tcpatau udpdiikuti oleh garis miring dan beberapa string alfanumerik yang tidak kosong adalah

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

Menerapkan ini pada data Anda:

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

Untuk memastikan bahwa kami hanya melakukan ini pada baris yang dimulai dengan string proto=:

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

Dengan sed, menghapus semuanya sebelum karakter pertama =dan sesudah karakter kosong pertama:

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

Untuk memastikan bahwa kami hanya melakukan ini pada baris yang dimulai dengan string proto=, Anda bisa memasukkan langkah pra-pemrosesan yang sama dengan grepseperti di atas, atau Anda bisa menggunakan

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

Di sini, kami menekan output default dengan -nopsi, dan kemudian kami memicu penggantian dan cetak eksplisit baris hanya jika baris cocok ^proto=.


Dengan awk, menggunakan pemisah bidang default, dan kemudian membelah bidang pertama =dan mencetak bit kedua:

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

Untuk memastikan bahwa kami hanya melakukan ini pada baris yang dimulai dengan string proto=, Anda bisa memasukkan langkah pra-pemrosesan yang sama dengan grepseperti di atas, atau Anda bisa menggunakan

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

Jika Anda menggunakan GNU grep (untuk -Popsi), Anda dapat menggunakan:

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

Di sini kita mencocokkan proto=string, untuk memastikan bahwa kita mengekstrak kolom yang benar, tetapi kemudian kita membuangnya dari output dengan \Kbendera.

Di atas mengasumsikan bahwa kolom dipisahkan oleh ruang. Jika tab juga merupakan pemisah yang valid, Anda akan menggunakan \Suntuk mencocokkan karakter non-spasi, jadi perintahnya adalah:

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

Jika Anda juga ingin melindungi terhadap bidang yang cocok proto=dengan substring, seperti a thisisnotaproto=tcp/https, Anda dapat menambahkan batas kata dengan \bseperti:

grep -oP '\bproto=\K\S*' file
pengguna000001
sumber
1
Anda dapat memperbaikinya hanya dengan menulis grep -oP 'proto=\K\S+'. The proto=tcp/httpdapat diikuti oleh tab bukannya ruang, dan \Stidak seperti [^ ]akan ditemukan karakter non-ruang.
Mosvy
@mosvy: Itu saran yang bagus, terima kasih.
user000001
1
Bagaimanapun, -oini adalah GNUisme juga. -Phanya didukung oleh GNU grepjika dibangun dengan dukungan PCRE (opsional saat membangun).
Stéphane Chazelas
6

Menggunakan awk:

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

$1 ~ "proto"akan memastikan kami hanya mengambil tindakan sesuai dengan yang ada protodi kolom pertama

sub(/proto=/, "")akan menghapus proto=dari input

print $1 mencetak kolom yang tersisa


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

Kode golf pada grepsolusinya

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

atau bahkan

grep -Po "..p/\S+" file
bu5hman
sumber
3

Menggunakan cutperintah:

cut -b 7-15 foo.txt
Capeya
sumber
3
Ini akan mencakup spasi tambahan pada garis httpdan dns.
G-Man Mengatakan 'Reinstate Monica'
2

Hanya grepsolusi lain :

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

Dan yang serupa dengan sedmencetak hanya grup yang ditangkap yang cocok:

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

awkPendekatan lain :

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

Itu akan mengatur pemisah bidang awk ke salah satu =atau spasi. Kemudian, jika garis cocok dengan a =, maka salah satu udatau tcdiikuti oleh p, cetak bidang ke-2.

sedPendekatan lain (tidak portabel untuk semua versi sed, tetapi bekerja dengan GNU sed):

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

The -nberarti "tidak mencetak" dan -Ememungkinkan diperpanjang ekspresi reguler yang memberi kita \Suntuk "non-spasi", +untuk "satu atau lebih" dan tanda kurung untuk menangkap. Akhirnya, /ppada akhirnya akan membuat sed garis cetak hanya jika operasi berhasil jadi jika ada kecocokan untuk operator substitusi.

Dan, satu perl:

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

The -nberarti "membaca file baris demi baris masukan dan menerapkan script yang diberikan oleh -emasing-masing baris". The -lmenambahkan baris baru untuk setiap printpanggilan (dan menghapus keluar baris dari input). Script itu sendiri akan mencetak bentangan karakter non-spasi terpanjang yang ditemukan setelah a proto=.

terdon
sumber
1
-Esemakin portabel, tetapi \Stidak. [^[:space:]]adalah setara yang lebih portabel.
Stéphane Chazelas
1

Berikut ini solusi lain yang cukup mudah:

grep -o "[tc,ud]*p\\/.*  "   INPUTFile.txt  |   awk '{print $1}'
mkzia
sumber
Anda greptidak cocok dengan apa pun. [tc,ud]\*\\/.*mencari satu kemunculan baik t, atau c, ,atau uatau d, diikuti oleh *karakter literal , kemudian a pdan garis miring terbalik. Anda mungkin bermaksud grep -Eo '(tc|ud)p/.* ' file | awk '{print $1}'. Tapi kemudian, jika Anda menggunakan awk, Anda mungkin juga melakukan semuanya dalam awk: awk -F'[= ]' '/(tc|ud)p/{print $2}' file.
terdon
Seseorang memodifikasi asli saya, ada Backslash tambahan sebelum bintang, yang baru saja saya hapus Sir.
mkzia
Terima kasih telah mengedit, tapi saya khawatir itu hanya bekerja secara kebetulan. Seperti yang saya jelaskan sebelumnya, [tc,ud]pberarti "salah t, c, ,, uatau ddiikuti oleh p. Jadi cocok di sini hanya karena tcpmemiliki cpdan udpmemiliki dp. Tapi itu juga akan cocok ,patau tpdll Juga, sekarang bahwa Anda memiliki *, maka akan cocok pppjuga ( *berarti "0 atau lebih" sehingga akan cocok bahkan ketika tidak cocok). Anda tidak ingin kelas karakter ( [ ]), yang Anda inginkan adalah grup: (tc|ud)(gunakan dengan -Ebendera grep). Juga, .*membuatnya cocokkan seluruh baris
terdon
1
@Jesse_b: Walaupun mkzia secara teknis bukan "kontributor baru", mereka adalah pengguna yang tidak berpengalaman, sebagaimana dibuktikan oleh fakta bahwa mereka tidak menggunakan format kode untuk perintah mereka. Namun mereka cukup pintar untuk mengetik \*untuk mendapatkan yang pertama *dalam perintah mereka untuk tampil sebagai * dan bukan sebagai markdown miring. Ketika Anda menempatkan perintah ke dalam format kode, Anda menyebabkan \sebelum *muncul (sehingga menyebabkan perintah gagal). Saat Anda mengedit posting orang lain, harap diperhatikan untuk mengubah tampilan posting seperti ini.
G-Man Mengatakan 'Reinstate Monica'
@terdon: (1) Tidak, sebenarnya itu tidak akan cocok ppp. Tentu saja Anda benar bahwa itu akan cocok ,patau  tp- atau uucp, ttp, cutp, ductpatau d,up.
G-Man Mengatakan 'Reinstate Monica'
0
awk '{print $1}' filename|awk -F "=" '{print $NF}'
Praveen Kumar BS
sumber
0
cat file| cut -f1 -d' '| cut -f2 -d'='
tcp/http
tcp/https
udp/dns

opsi memotong:

  • -f - bidang
  • -d - delimeter
Alexander
sumber