
ログ ファイルがあり、私は頻繁に tail でライブで追跡し、grep を使用して関心のある行をフィルター処理しています。ただし、行には必ずしも関心があるわけではないデータが大量に含まれており、解析するのが難しいため、必要な行の部分のみを表示しています。各行エントリの形式は、主に引用符で囲まれたタグとデータ (スペースが含まれる場合もあります) のリストです。次に、(サニタイズされた) ログ行のサンプルをいくつか示します。
2017:11:29-11:29:56 filter-1 httpproxy[3194]: id="0001" severity="info" sys="SecureWeb" sub="http" name="http access" action="pass" method="CONNECT" srcip="10.11.12.13" dstip="14.3.1.4" user="" group="" ad_domain="" statuscode="200" cached="0" profile="REF_HttPro1234 (Campus2)" filteraction="REF_HttStu (Allow Policy)" size="6518" request="0x915a3e00" url="https://website.net/" referer="" error="" authtime="0" dnstime="1" cattime="73" avscantime="0" fullreqtime="61576999" device="0" auth="6" ua="" exceptions="" category="9998" reputation="unverified" categoryname="Uncategorized" country="United States" application="krux" app-id="826"
2017:11:29-11:29:56 filter-1 httpproxy[3194]: id="0001" severity="info" sys="SecureWeb" sub="http" name="http access" action="pass" method="GET" srcip="10.13.14.15" dstip="154.6.75.10" user="" group="" ad_domain="" statuscode="200" cached="0" profile="REF_HttPro1235 (Campus1)" filteraction="REF_HttStu (Allow Policy)" size="3161" request="0x6b4d5610" url="http://host.com/mini_banner.png" referer="http://www.web.com/computers.htm" error="" authtime="0" dnstime="0" cattime="64" avscantime="848" fullreqtime="50046" device="0" auth="6" ua="Mozilla/5.0 (X11; CrOS x86_64 9765.85.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.123 Safari/537.36" exceptions="" category="111" reputation="trusted" categoryname="Education/Reference" sandbox="-" content-type="image/png"
注意すべき点は、すべてのタグがすべての行に存在するわけではないということです。たとえば、application と app-id は最初の行には存在しますが、2 番目の行には存在しません。
上記の行を入力例として使用すると、出力として srcip、categoryname、url タグのみをこの順序で表示したいという例が考えられます。望ましい出力は次のようになります。
10.11.12.13 Uncategorized https://website.net/
10.13.14.15 Education/Reference http://host.com/mini_banner.png
表示するタグをその場で調整できる、簡単に適応できるソリューションを探しています。
答え1
データは高度に構造化されており、キー="値"したがって、キー名のリストを引数として受け取り、それらの値を出力するだけの小さなシェル スクリプトを gnu awk で記述できます。例myscript
:
#!/bin/bash
awk -v lhs="$*" '
BEGIN{ FPAT = "[a-z-]*=\"[^\"]*\""
nwant = split(lhs,want)
}
{ for(i=1;i<=NF;i++){
start = match($i,/([a-z-]*)="([^"]*)"/,a)
key[a[1]] = a[2]
}
for(i=1;i<=nwant;i++){printf "%s ",key[want[i]]; key[want[i]] = ""}
printf "\n"
}'
これを として呼び出しますmyscript srcip categoryname url
。これにより、awk 変数がlhs
引数の 1 つの文字列として設定されますwant
。引数は先頭で配列に分割されます。行は awk によってパターンに一致するフィールドに分割されます。キー="値"組み込みFPAT
変数を使用します。
各行で、各フィールドをmatch()
キーと二重引用符で囲まれた部分の 2 つのキャプチャ グループに分割します。これらは awk によって配列に格納されa
、キー文字列でインデックス付けされた連想配列に保存されますkey
。
次に、必要なキーごとに値を出力し、次の行のために値をクリアします (その行にこのキーがない場合)。明らかに、これはすべてのデータが必要な構造を持っていることを前提としており、値内の (") やアルファベット以外の文字を含むキーを処理するために変更が必要になります。
gnu awk (gawk) の 4.0 より前のバージョンには、行をパターンに一致するフィールドに分割する組み込み機能がないFPAT
ため、自分で行う必要があります。
#!/bin/bash
awk -v lhs="$*" '
BEGIN{ nwant = split(lhs,want) }
{ input = $0
while(match(input,"[a-z-]*=\"[^\"]*\"")>0){
field = substr(input,RSTART,RLENGTH)
input = substr(input,RSTART+RLENGTH)
start = match(field,/([a-z-]*)="([^"]*)"/,a)
key[a[1]] = a[2]
}
for(i=1;i<=nwant;i++){printf "%s ",key[want[i]]; key[want[i]] = ""}
printf "\n"
}'
もちろん、2 つのマッチ呼び出しを 1 つに組み合わせることもできますが、これはオリジナルとの違いを示しています。
答え2
(POSIX 準拠) を使用するsed
...
sed 's/.* srcip="\([^"]*\)" .* url="\([^"]*\)" .* categoryname="\([^"]*\)" .*/\1 \3 \2/' logfile
ここでは特別なことは何もしていません。キーを見つけて、値を括弧で囲むだけで、\(..\)
バック参照として使用できます。次に、スペースで区切られ、要件に応じて順序付けられたバック参照のみを含む文字列を置き換えます\1 \3 \2
。
出力:
10.11.12.13 Uncategorized https://website.net/
10.13.14.15 Education/Reference http://host.com/mini_banner.png
ログにこれらのキーがすべて含まれていない文字列が含まれている場合は、次を使用できます。
sed -n 's/.* srcip="\([^"]*\)" .* url="\([^"]*\)" .* categoryname="\([^"]*\)" .*/\1 \3 \2/p' logfile
これにより、パターンに一致する行のみが印刷されます。
もちろん、ストリーミング形式で使用したい場合は、ファイル名を削除して[something sending logs to stdout] | sed ...