歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux基礎 >> 關於Linux >> Awk使用及網站日志分析

Awk使用及網站日志分析

日期:2017/3/1 12:08:14   编辑:關於Linux

Awk使用及網站日志分析

Awk簡介

概述

awk是一個強大的文本分析工具,相對於grep的查找,sed的編輯,awk在其對數據分析並生成報告時,顯得尤為強大。簡單來說awk就是把文件逐行的讀入,以空格為默認分隔符將每行切片,切開的部分再進行各種分析處理。awk有3個不同版本: awk、nawk和gawk,未作特別說明,一般指gawk。awk程序的報告生成能力通常用來從大文本文件中提取數據元素並將它們格式化成可讀的報告。最完美的例子是格式化日志文件。awk程序允許從日志文件中只過濾出你想要看的數據

Awk使用

awk命令格式和選項

語法形式

awk [options] 'script' var=value file(s)
awk [options] -f scriptfile var=value file(s)

常用命令選項

-F fs fs指定輸入分隔符,fs可以是字符串或正則表達式,如-F:-v var=value 賦值一個用戶定義變量,將外部變量傳遞給awk-f scripfile 從腳本文件中讀取awk命令-m[fr] val 對val值設置內在限制,-mf選項限制分配給val的最大塊數目;-mr選項限制記錄的最大數目。這兩個功能是Bell實驗室版awk的擴展功能,在標准awk中不適用。

模式:

/正則表達式/:使用通配符的擴展集。 關系表達式:使用運算符進行操作,可以是字符串或數字的比較測試。 模式匹配表達式:用運算符~(匹配)和~!(不匹配)。BEGIN語句塊、pattern語句塊、END語句塊。

awk腳本基本結構

awk 'BEGIN{ print "start" } pattern{ commands } END{ print "end" }' file

一個awk腳本通常由:BEGIN語句塊、能夠使用模式匹配的通用語句塊、END語句塊3部分組成,這三個部分是可選的。任意一個部分都可以不出現在腳本中,腳本通常是被單引號或雙引號中,例如:

awk 'BEGIN{ i=0 } { i++ } END{ print i }' filename
awk "BEGIN{ i=0 } { i++ } END{ print i }" filename

awk的工作原理

awk 'BEGIN{ commands } pattern{ commands } END{ commands }'

第一步:執行BEGIN{ commands }語句塊中的語句;

第二步:從文件或標准輸入(stdin)讀取一行,然後執行pattern{ commands }語句塊,它逐行掃描文件,從第一行到最後一行重復這個過程,直到文件全部被讀取完畢。

第三步:當讀至輸入流末尾時,執行END{ commands }語句塊。

BEGIN語句塊在awk開始從輸入流中讀取行之前被執行,這是一個可選的語句塊,比如變量初始化、打印輸出表格的表頭等語句通常可以寫在BEGIN語句塊中。

END語句塊在awk從輸入流中讀取完所有的行之後即被執行,比如打印所有行的分析結果這類信息匯總都是在END語句塊中完成,它也是一個可選語句塊。

pattern語句塊中的通用命令是最重要的部分,它也是可選的。如果沒有提供pattern語句塊,則默認執行{ print },即打印每一個讀取到的行,awk讀取的每一行都會執行該語句塊。

示例

echo -e "A line 1\nA line 2" | awk 'BEGIN{ print "Start" } { print } END{ print "End" }'

打印結果:

Start
A line 1
A line 2
End

當使用不帶參數的print時,它就打印當前行,當print的參數是以逗號進行分隔時,打印時則以空格作為定界符。在awk的print語句塊中雙引號是被當作拼接符使用,例如:

echo | awk '{ var1="v1"; var2="v2"; var3="v3"; print var1,var2,var3; }'

打印結果:

v1 v2 v3

雙引號拼接使用:

echo | awk '{ var1="v1"; var2="v2"; var3="v3"; print var1"="var2"="var3; }'
打印結果:
v1=v2=v3

{ }類似一個循環體,會對文件中的每一行進行迭代,通常變量初始化語句(如:i=0)以及打印文件頭部的語句放入BEGIN語句塊中,將打印的結果等語句放在END語句塊中。

awk內置變量

說明:[A][N][P][G]表示第一個支持變量的工具,[A]=awk、[N]=nawk、[P]=POSIXawk、[G]=gawk

$n 當前記錄的第n個字段,比如n為1表示第一個字段,n為2表示第二個字段。
$0 這個變量包含執行過程中當前行的文本內容。
[N] ARGC 命令行參數的數目。
[G] ARGIND 命令行中當前文件的位置(從0開始算)。
[N] ARGV 包含命令行參數的數組。
[G] CONVFMT 數字轉換格式(默認值為%.6g)。
[P] ENVIRON 環境變量關聯數組。
[N] ERRNO 最後一個系統錯誤的描述。
[G] FIELDWIDTHS 字段寬度列表(用空格鍵分隔)。
[A] FILENAME 當前輸入文件的名。
[P] FNR 同NR,但相對於當前文件。
[A] FS 字段分隔符(默認是任何空格)。
[G] IGNORECASE 如果為真,則進行忽略大小寫的匹配。
[A] NF 表示字段數,在執行過程中對應於當前的字段數。
[A] NR 表示記錄數,在執行過程中對應於當前的行號。
[A] OFMT 數字的輸出格式(默認值是%.6g)。
[A] OFS 輸出字段分隔符(默認值是一個空格)。
[A] ORS 輸出記錄分隔符(默認值是一個換行符)。
[A] RS 記錄分隔符(默認是一個換行符)。
[N] RSTART 由match函數所匹配的字符串的第一個位置。
[N] RLENGTH 由match函數所匹配的字符串的長度。
[N] SUBSEP 數組下標分隔符(默認值是34)。

示例:

echo -e "line1 f2 f3\nline2 f4 f5\nline3 f6 f7" | awk '{print "Line No:"NR", No of fields:"NF, "$0="$0, "$1="$1, "$2="$2, "$3="$3}'

打印結果:
Line No:1, No of fields:3 $0=line1 f2 f3 $1=line1 $2=f2 $3=f3
Line No:2, No of fields:3 $0=line2 f4 f5 $1=line2 $2=f4 $3=f5
Line No:3, No of fields:3 $0=line3 f6 f7 $1=line3 $2=f6 $3=f7

使用print $NF可以打印出一行中的最後一個字段,使用$(NF-1)則是打印倒數第二個字段,其他以此類推:

echo -e "line1 f2 f3\n line2 f4 f5" | awk '{print $NF}'
打印結果:
f3
f5
echo -e "line1 f2 f3\n line2 f4 f5" | awk '{print $(NF-1)}'

打印結果:

f2
f4

打印每一行的第二和第三個字段:

awk '{ print $2,$3 }' filename

統計文件中的行數:

awk 'END{ print NR }' filename

以上命令只使用了END語句塊,在讀入每一行的時,awk會將NR更新為對應的行號,當到達最後一行NR的值就是最後一行的行號,所以END語句塊中的NR就是文件的行數。

將外部變量值傳遞給awk

借助-v選項,可以將外部值(並非來自stdin)傳遞給awk:

VAR=10000 echo | awk -v VARIABLE=$VAR '{ print VARIABLE }'

另一種傳遞外部變量方法:

var1="aaa"
var2="bbb"
echo | awk '{ print v1,v2 }' v1=$var1 v2=$var2

當輸入來自於文件時使用:

awk '{ print v1,v2 }' v1=$var1 v2=$var2 filename

以上方法中,變量之間用空格分隔作為awk的命令行參數跟隨在BEGIN、{}和END語句塊之後。

awk高級輸入輸出

讀取下一條記錄

awk中next語句使用:在循環逐行匹配,如果遇到next,就會跳過當前行,直接忽略下面語句。而進行下一行匹配。net語句一般用於多行合並:

cat text.txt
a
b
c
d
e
awk 'NR%2==1{next}{print NR,$0;}'
text.txt
2 b
4 d

簡單地讀取一條記錄

awk getline用法:輸出重定向需用到getline函數。getline從標准輸入、管道或者當前正在處理的文件之外的其他輸入文件獲得輸入。它負責從輸入獲得下一行的內容,並給NF,NR和FNR等內建變量賦值。如果得到一條記錄,getline函數返回1,如果到達文件的末尾就返回0,如果出現錯誤,例如打開文件失敗,就返回-1。

當其左右無重定向符|或<時:getline作用於當前文件,讀入當前文件的第一行給其後跟的變量var或$0(無變量),應該注意到,由於awk在處理getline之前已經讀入了一行,所以getline得到的返回結果是隔行的。當其左右有重定向符|或<時:getline則作用於定向輸入文件,由於該文件是剛打開,並沒有被awk讀入一行,只是getline讀入,那麼getline返回的是該文件的第一行,而不是隔行。

示例:

執行linux的date命令,並通過管道輸出給getline,然後再把輸出賦值給自定義變量out,並打印它:

awk 'BEGIN{ "date" | getline out; print out }'


執行shell的date命令,並通過管道輸出給getline,然後getline從管道中讀取並將輸入賦值給out,split函數把變量out轉化成數組mon,然後打印數組mon的第二個元素:

awk 'BEGIN{ "date" | getline out; split(out,mon); print mon[2] }'

命令ls的輸出傳遞給geline作為輸入,循環使getline從ls的輸出中讀取一行,並把它打印到屏幕。這裡沒有輸入文件,因為BEGIN塊在打開輸入文件前執行,所以可以忽略輸入文件。

awk 'BEGIN{ while( "ls" | getline) print }'

關閉文件

awk中允許在程序中關閉一個輸入或輸出文件,方法是使用awk的close語句。

close("filename")

filename可以是getline打開的文件,也可以是stdin,包含文件名的變量或者getline使用的確切命令。或一個輸出文件,可以是stdout,包含文件名的變量或使用管道的確切命令。

輸出到一個文件

awk中允許用如下方式將結果輸出到一個文件:

echo | awk '{printf("hello word!\n") > "datafile"}'

echo | awk '{printf("hello word!\n") >> "datafile"}'


設置字段定界符

默認的字段定界符是空格,可以使用-F "定界符" 明確指定一個定界符:

awk -F: '{ print $NF }' /etc/passwd

awk 'BEGIN{ FS=":" } { print $NF }' /etc/passwd

在BEGIN語句塊中則可以用OFS=“定界符”設置輸出字段的定界符。

流程控制語句

在linux awk的while、do-while和for語句中允許使用break,continue語句來控制流程走向,也允許使用exit這樣的語句來退出。break中斷當前正在執行的循環並跳到循環外執行下一條語句。if 是流程選擇用法。awk中,流程控制語句,語法結構,與c語言類型。有了這些語句,其實很多shell程序都可以交給awk,而且性能是非常快的。下面是各個語句用法。

條件判斷語句

if(表達式)
    語句1
else
    語句2

格式中語句1可以是多個語句,為了方便判斷和閱讀,最好將多個語句用{}括起來。

awk分枝結構允許嵌套,其格式為:

if(表達式)
    {語句1}
else if(表達式)
    {語句2}
else
    {語句3}

示例:

awk 'BEGIN{
test=100;
if(test>90){
    print "very good";
}
else if(test>60){
    print "good";
} else{
    print "no pass";
    }
}'

每條命令語句後面可以用;分號結尾。

循環語句

while語句:

while(表達式)
    {語句}

示例:

awk 'BEGIN{
test=100;
total=0;
while(i<=test){
    total+=i;
    i++;
}
print total;
}'


for循環

for循環有兩種格式:

格式一:

for(變量 in 數組)
    {語句}


格式二:

for(變量;條件;表達式)
{語句}

do循環

do
{語句} while(條件)

示例:

awk 'BEGIN{
total=0;
i=0;
do {total+=i;i++;} while(i<=100)
    print total;
}'


其他語句

break 當 break 語句用於 while 或 for 語句時,導致退出程序循環。continue 當 continue 語句用於 while 或 for 語句時,使程序循環移動到下一個迭代。exit 語句使主輸入循環退出並將控制轉移到END,如果END存在的話。如果沒有定義END規則,或在END中應用exit語句,則終止腳本的執行。

數組應用

數組是awk的靈魂,處理文本中最不能少的就是它的數組處理。因為數組索引(下標)可以是數字和字符串在awk中數組叫做關聯數組(associative arrays)。awk 中的數組不必提前聲明,也不必聲明大小。數組元素用0或空字符串來初始化,這根據上下文而定。

數組的定義

數字做數組索引(下標):

Array[1]="sun"
Array[2]="kai"


字符串做數組索引(下標):

Array["first"]="www"
Array["last"]="name"
Array["birth"]="1987"

讀取數組的值

{ for(item in array) {print array[item]}; } #輸出的順序是隨機的
{ for(i=1;i<=len;i++) {print array[i]}; } #len是數組的長度

數組相關函數

得到數組長度:

awk 'BEGIN{info="it is a test";lens=split(info,tA," ");print length(tA),lens;}'

length返回字符串以及數組長度,split進行分割字符串為數組,也會返回分割得到數組長度。

awk 'BEGIN{info="it is a test";split(info,tA," ");print asort(tA);}'

asort對數組進行排序,返回數組長度。

輸出數組內容(無序,有序輸出):

awk 'BEGIN{info="it is a test";split(info,tA," ");for(k in tA){print k,tA[k];}}'

打印結果:

1 it
2 is
3 a
4 test
注意:數組下標是從1開始,與C數組不一樣。

判斷鍵值存在以及刪除鍵值:

awk 'BEGIN{tB["a"]="a1";tB["b"]="b1";if( "c" in tB){print "ok";};for(k in tB){print k,tB[k];}}'

打印結果:

a a1
b b1

#刪除鍵值:

awk 'BEGIN{tB["a"]="a1";tB["b"]="b1";delete tB["a"];for(k in tB){print k,tB[k];}}'

打印結果:

b b1



字符串操作

查找字符串(index使用)

awk 'BEGIN{info="this is a test2010test!";print index(info,"test")?"ok":"no found";}'
未找到,返回0

正則表達式匹配查找(match使用)

awk 'BEGIN{info="this is a test2010test!";print match(info,/[0-9]+/)?"ok":"no found";}'


截取字符串(substr使用)

awk 'BEGIN{info="this is a test2010test!";print substr(info,4,10);}'

從第 4個 字符開始,截取10個長度字符串

字符串分割(split使用)

awk 'BEGIN{info="this is a test";split(info,tA," ");print length(tA);for(k in tA){print k,tA[k];}}'

打印結果:

4
4 test
1 this
2 is
3 a
分割info,動態創建數組tA,這裡比較有意思,awk for …in循環,是一個無序的循環。 並不是從數組下標1…n ,因此使用時候需要注意。

一般函數

打開外部文件(close用法)

awk 'BEGIN{while("cat /etc/passwd"|getline){print $0;};close("/etc/passwd");}'

逐行讀取外部文件(getline使用方法)

awk 'BEGIN{while(getline < "/etc/passwd"){print $0;};close("/etc/passwd");}'

awk 'BEGIN{print "Enter your name:";getline name;print name;}'


調用外部應用程序(system使用方法)

awk 'BEGIN{b=system("ls -al");print b;}'


網站日志分析

簡介

下面使用Linux中的Awk對tomcat中日志文件做一些分析,主要統計pv,uv等。

日志文名稱:access_2013_05_30.log,大小57.7 MB 。

這次分析只是簡單演示,所以不是太精確地處理數據。

日志地址:http://download.csdn.net/detail/u011204847/9496357

日志數據示例:

\

日志總行數:

\

打印的第七列數據為日志的URL:

\

分析中用到的一些知識:

擴展,

shell中的管道|

command 1 | command 2     #他的功能是把第一個命令command 1執行的結果作為command 2的輸入傳給command 2

wc -l    #統計行數

uniq -c     #在輸出行前面加上每行在輸入文件中出現的次數

uniq -u      #僅顯示不重復的行

sort -nr

-n:依照數值的大小排序-r:以相反的順序來排序-k:按照哪一列進行排序

head -3      #取前三名

網站日志分析步驟

數據清洗:

1、第一次清洗:去除URL中以/static/開頭的URL

awk '($7 !~ /^\/static\//){print $0}' access_2013_05_30.log > clean_2013_05_30.log

去除前:

\

去除後:

\

2、第二次清洗:去除圖片、css和js

awk '($7 !~ /\.jpg|\.png|\.jpeg|\.gif|\.css|\.js/) {print $0}' clean_2013_05_30.log > clean2_201 3_05_30.log

\

PV

pv是指網頁訪問次數

方法:統計所有數據的總行數

數據清洗:對原始數據中的干擾數據進行過濾

awk 'BEGIN{pv=0}{pv++}END{print "pv:"pv}' clean2_2013_05_30.log > pv_2013_05_30

\

UV

uv指的是訪問人數,也就是獨立IP數

對ip重復的數據進行去重,然後再統計所有行數

awk '{print $1}' clean2_2013_05_30.log |sort -n |uniq -u |wc -l > uv_2013_05_30

\

訪問最多的IP(前10名)

對ip重復的數據進行去重的時候還要匯總,取前10名

awk '{print $1}' clean2_2013_05_30.log | sort -n | uniq -c |sort -nr -k 1|head -10 > top10_2013_05_30

\

訪問前十的URL(可以用來分析網站哪個模塊最受歡迎)

awk '{print $7}' clean2_2013_05_30.log | sort | uniq -c |sort -nr -k 1|head -10 > top10URL_2013_ 05_30

\

使用Java實現訪問量前十的IP地址。

\

代碼示例:

import java.io.*;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Created by Chen on 2016/4/13.
 */
public class RegexTest {
    public static void main(String[] args) throws IOException {

        //日志格式:27.19.74.143 - - [30/May/2013:17:38:20 +0800] "GET /static/image/common/faq.gif HTTP/1.1" 200 1127
        //需要解析的源文件
        File file = new File("D:\\clean2_2013_05_30.log");
        //高效字符讀入流
        BufferedReader bufr = new BufferedReader(new InputStreamReader(new FileInputStream(file)));
        String line = null;
        //建立TreeMap,Key為IP地址,Value為IP出現次數
        Map map = new TreeMap<>();
        while ((line = bufr.readLine()) != null) {
            String ip = parseIP(line);
            map.put(ip, map.get(ip) == null ? 1 : map.get(ip) + 1);
        }


        Set set = map.keySet();
        //創建一個具有匿名比較器的TreeSet集合 :作用是讓存入的元素先按照Key排序,相同則繼續按照Value排序。
        //這個TreeSet將用來存儲IP地址和出現次數反轉後的每個元素。
        TreeSet> treeSet = new TreeSet<>(new Comparator>() {
            @Override
            public int compare(Map.Entry o1, Map.Entry o2) {
                int result = o1.getKey() - o2.getKey();
                if (result == 0) {
                    result = o1.getValue().compareTo(o2.getValue());
                }
                return -result;
            }
        });


        //把IP地址和出現次數反轉,然後放入上面具有比較器的TreeSet中
        for (Iterator it = set.iterator(); it.hasNext(); ) {
            String ip = it.next();
            Integer n = map.get(ip);
            treeSet.add(new AbstractMap.SimpleEntry(n, ip));
        }


        //遍歷並打印出現次數和IP地址(次數最多的前十個)
        int count = 0;
        Iterator> itr = treeSet.iterator();
        while (itr.hasNext()) {
            if (count == 10) {
                break;
            }
            Map.Entry en = itr.next();
            int n = en.getKey();
            System.out.println(n + "\t\t\t" + en.getValue());
            count++;
        }
    }

    //解析IP地址
    public static String parseIP(String str) {
        Pattern compile = Pattern.compile("[0-9]+(\\.[0-9]+){3}");
        //解析IP地址
        Matcher matcher = compile.matcher(str);
        String group = "";
        if (matcher.find()) {
            group = matcher.group();
        }
        return group;
    }
}
Copyright © Linux教程網 All Rights Reserved