18個awk經典實戰案例

18個Awk實戰案例精講

這些案例是我收集起來的,大多都是我本身遇到過的,有些比較經典,有些比較具備表明性。mysql

插入幾個新字段

在"a b c d"的b後面插入3個字段e f gsql

echo a b c d|awk '{$3="e f g "$3}1'

格式化空白

移除每行的前綴、後綴空白,並將各部分左對齊。centos

aaaa        bbb     ccc                 
   bbb     aaa ccc
ddd       fff             eee gg hh ii jj
awk 'BEGIN{OFS="\t"}{$1=$1;print}' a.txt

執行結果:api

aaaa    bbb     ccc
bbb     aaa     ccc
ddd     fff     eee     gg      hh      ii      jj

篩選IPv4地址

從ifconfig命令的結果中篩選出除了lo網卡外的全部IPv4地址。bash

## 1.法一:
ifconfig | awk '/inet / && !($2 ~ /^127/){print $2}'

# 按段落讀取
## 2.法二:
ifconfig | awk 'BEGIN{RS=""}!/lo/{print $6}'

## 3.法三:
ifconfig |\
awk '
  BEGIN{RS="";FS="\n"}
  !/lo/{$0=$2;FS=" ";$0=$0;print $2}
'

讀取.ini配置文件中的某段

ini文件內容以下:app

[base]
name=os_repo
baseurl=https://xxx/centos/$releasever/os/$basearch
gpgcheck=0

enable=1

[mysql]
name=mysql_repo
baseurl=https://xxx/mysql-repo/yum/mysql-5.7-community/el/$releasever/$basearch

gpgcheck=0
enable=1

[epel]
name=epel_repo
baseurl=https://xxx/epel/$releasever/$basearch
gpgcheck=0
enable=1
[percona]
name=percona_repo
baseurl = https://xxx/percona/release/$releasever/RPMS/$basearch
enabled = 1
gpgcheck = 0

awk篩選代碼以下:ssh

awk '
  index($0,"[mysql]"){
    print;
    while( (getline)>0 ){
      if(/\[.*\]/){ exit }
      print
    }
}' a.txt

根據某字段去重

去掉uid=xxx重複的行。tcp

2019-01-13_12:00_index?uid=123
2019-01-13_13:00_index?uid=123
2019-01-13_14:00_index?uid=333
2019-01-13_15:00_index?uid=9710
2019-01-14_12:00_index?uid=123
2019-01-14_13:00_index?uid=123
2019-01-15_14:00_index?uid=333
2019-01-16_15:00_index?uid=9710
awk -F"?" '!arr[$2]++{print}' a.txt

結果:ide

2019-01-13_12:00_index?uid=123
2019-01-13_14:00_index?uid=333
2019-01-13_15:00_index?uid=9710

次數統計

假設有以下文件內容,統計每一行出現的次數:函數

portmapper
portmapper
portmapper
portmapper
portmapper
portmapper
status
status
mountd
mountd
mountd
mountd
mountd
mountd
nfs
nfs
nfs_acl
nfs
nfs
nfs_acl
nlockmgr
nlockmgr
nlockmgr
nlockmgr
nlockmgr

awk代碼以下:

awk '{a[$1]++}END{for(i in arr){print a[i],i}}' a.txt

統計TCP鏈接狀態數量

以下tcp鏈接狀態信息:

$ netstat -tnap
Proto Recv-Q Send-Q Local Address   Foreign Address  State       PID/Program name
tcp        0      0 0.0.0.0:22      0.0.0.0:*        LISTEN      1139/sshd
tcp        0      0 127.0.0.1:25    0.0.0.0:*        LISTEN      2285/master
tcp        0     96 192.168.2.17:22 192.168.2.1:2468 ESTABLISHED 87463/sshd: root@pt
tcp        0      0 192.168.2017:22 192.168.201:5821 ESTABLISHED 89359/sshd: root@no
tcp6       0      0 :::3306         :::*             LISTEN      2289/mysqld
tcp6       0      0 :::22           :::*             LISTEN      1139/sshd
tcp6       0      0 ::1:25          :::*             LISTEN      2285/master

統計但願獲得的結果:

5: LISTEN
2: ESTABLISHED
netstat -tnap |\
awk '
  /^tcp/{arr[$6]++}
  END{
    for(state in arr){
      print arr[state] ": " state
    }
  }
'

一行式:

netstat -tna | awk '/^tcp/{arr[$6]++}END{for(state in arr){print arr[state] ": " state}}'
netstat -tna | /usr/bin/grep 'tcp' | awk '{print $6}' | sort | uniq -c

統計日誌中各IP訪問非200狀態碼的次數

日誌示例數據:

111.202.100.141 - - [2019-11-07T03:11:02+08:00] "GET /robots.txt HTTP/1.1" 301 169

統計非200狀態碼的IP,並取次數最多的前10個IP。

# 法一
awk '
  $8!=200{arr[$1]++}
  END{
    for(i in arr){print arr[i],i}
  }
' access.log | sort -k1nr | head -n 10

# 法二:
awk '
  $8!=200{arr[$1]++}
  END{
    PROCINFO["sorted_in"]="@val_num_desc";
    for(i in arr){
      if(cnt++==10){exit}
      print arr[i],i
    }
}' access.log

統計獨立IP

假設有以下文件內容,總共4個字段,第一個字段是URL,第二個字段是訪問IP,第三個字段是訪問時間,第四個字段是訪問人。

a.com.cn|202.109.134.23|2015-11-20 20:34:43|guest
b.com.cn|202.109.134.23|2015-11-20 20:34:48|guest
c.com.cn|202.109.134.24|2015-11-20 20:34:48|guest
a.com.cn|202.109.134.23|2015-11-20 20:34:43|guest
a.com.cn|202.109.134.24|2015-11-20 20:34:43|guest
b.com.cn|202.109.134.25|2015-11-20 20:34:48|guest

需求:統計每一個URL的獨立訪問IP有多少個(去重),而且要爲每一個URL保存一個對應的文件,獲得的結果相似:

a.com.cn  2
b.com.cn  2
c.com.cn  1

而且有三個對應的文件:

a.com.cn.txt
b.com.cn.txt
c.com.cn.txt

代碼:

BEGIN{
  FS="|"
}

!arr[$1,$2]++{
  arr1[$1]++
}

END{
  for(i in arr1){
    print i,arr1[i] >(i".txt")
  }
}

處理字段缺失的數據

有以下文件內容,其中幾個字段是缺失數據的:

ID  name    gender  age  email          phone
1   Bob     male    28   abc@qq.com     18023394012
2   Alice   female  24   def@gmail.com  18084925203
3   Tony    male    21                  17048792503
4   Kevin   male    21   bbb@189.com    17023929033
5   Alex    male    18   ccc@xyz.com    18185904230
6   Andy    female       ddd@139.com    18923902352
7   Jerry   female  25   exdsa@189.com  18785234906
8   Peter   male    20   bax@qq.com     17729348758
9   Steven          23   bc@sohu.com    15947893212
10  Bruce   female  27   bcbd@139.com   13942943905

當字段缺失時,直接使用FS劃分字段來處理會很是棘手。gawk爲了解決這種特殊需求,提供了FIELDWIDTHS變量。

FIELDWIDTH能夠按照字符數量劃分字段。

awk '{print $4}' FIELDWIDTHS="2 2:6 2:6 2:3 2:13 2:11" a.txt

此處FIELDWIDTHS變量的值表示的含義:

  • 去兩個字符做爲第一個字段的內容
  • 跳過2個字符後取6個字符做爲第二個字段的內容
  • 跳過2個字符後取6個字符做爲第三個字段的內容
  • 跳過2個字符後取3個字符做爲第四個字段的內容
  • 跳過2個字符後取13個字符做爲第五個字段的內容
  • 跳過2個字符後取11個字符做爲第六個字段的內容

這樣按給定字符數量取字段數據後,即便字段缺失,也不會影響其它字段的數據。

處理字段中包含了字段分隔符的數據

下面是CSV文件中的一行,該CSV文件以逗號分隔各個字段。

Robbins,Arnold,"1234 A Pretty Street, NE",MyTown,MyState,12345-6789,USA

需求:取得第三個字段"1234 A Pretty Street, NE"。

當字段中包含了字段分隔符時,直接使用FS劃分字段來處理會很是棘手。gawk爲了解決這種特殊需求,提供了FPAT變量。

FPAT能夠收集正則匹配的結果,並將它們保存在各個字段中(就像grep匹配成功的部分會加顏色顯示,而使用FPAT劃分字段,則是將匹配成功的部分保存在字段$1 $2 $3...中)。

echo 'Robbins,Arnold,"1234 A Pretty Street, NE",MyTown,MyState,12345-6789,USA' |\
awk 'BEGIN{FPAT="[^,]+|\".*\""}{print $1,$3}'

取字段中指定字符數量

給定數據:

16  001agdcdafasd
16  002agdcxxxxxx
23  001adfadfahoh
23  001fsdadggggg

但願獲得:

16  001
16  002
23  001
23  002

awk的代碼:

awk '{print $1,substr($2,1,3)}'
awk 'BEGIN{FIELDWIDTH="2 2:3"}{print $1,$2}' a.txt

行列轉換

給定數據:

name age
alice 21
ryan 30

但願轉換後獲得:

name alice ryan
age 21 30

awk代碼:

awk '
  {
    for(i=1;i<=NF;i++){
      if(!(i in arr)){
        arr[i]=$i
      } else {
        arr[i]=arr[i]" "$i
      }
    }
  }
    END{
    for(i=1;i<=NF;i++){
      print arr[i]
    }
    }
' a.txt

行列轉換2

文件內容:

74683 1001
74683 1002
74683 1011
74684 1000
74684 1001
74684 1002
74685 1001
74685 1011
74686 1000
....
100085 1000
100085 1001

文件就兩列,但願處理成:

74683 1001 1002 1011
74684 1000 1001 1002
...

即,只要第一列數字相同,就把它們的第二列放一行上,中間空格分開。

代碼:

{
  if($1 in arr){
    arr[$1] = arr[$1]" "$2
  } else {
    arr[$1] = $2
  }

}

END{
  for(i in arr){
    printf "%s %s\n",i,arr[i]
  }
}

篩選給定時間範圍內的日誌

grep/sed/awk用正則去篩選日誌時,若是要精確到小時、分鐘、秒,則很是難以實現。

可是awk提供了mktime()函數,它能夠將時間轉換成epoch時間值。

# 2019-11-10 03:42:40轉換成epoch
$ awk 'BEGIN{print mktime("2019 11 10 03 42 40")}'
1573328560

藉此,能夠取得日誌中的時間字符串部分,再將它們的年、月、日、時、分、秒都取出來,而後放入mktime()構建成對應的epoch值。由於epoch值是數值,因此能夠比較大小,從而決定時間的大小。

下面strptime1()實現的是將2019-11-10T03:42:40+08:00格式的字符串轉換成epoch值,而後和which_time比較大小便可篩選出精確到秒的日誌。

BEGIN{
  # 要篩選什麼時間的日誌,將其時間構建成epoch值
  which_time = mktime("2019 11 10 03 42 40")
}

{
  # 取出日誌中的日期時間字符串部分
  match($0,"^.*\\[(.*)\\].*",arr)

  # 將日期時間字符串轉換爲epoch值
  tmp_time = strptime1(arr[1])

  # 經過比較epoch值來比較時間大小
  if(tmp_time > which_time){print}
}

# 構建的時間字符串格式爲:"2019-11-10T03:42:40+08:00"
function strptime1(str   ,arr,Y,M,D,H,m,S) {
  patsplit(str,arr,"[0-9]{1,4}")
  Y=arr[1]
  M=arr[2]
  D=arr[3]
  H=arr[4]
  m=arr[5]
  S=arr[6]
  return mktime(sprintf("%s %s %s %s %s %s",Y,M,D,H,m,S))
}

下面strptime2()實現的是將10/Nov/2019:23:53:44+08:00格式的字符串轉換成epoch值,而後和which_time比較大小便可篩選出精確到秒的日誌。

BEGIN{
  which_time = mktime("2019 11 10 03 42 40")
}

{
  match($0,"^.*\\[(.*)\\].*",arr)

  tmp_time = strptime2(arr[1])

  if(tmp_time > which_time){
    print 
  }
}

# 構建的時間字符串格式爲:"10/Nov/2019:23:53:44+08:00"
function strptime2(str   ,dt_str,arr,Y,M,D,H,m,S) {
  dt_str = gensub("[/:+]"," ","g",str)
  # dt_sr = "10 Nov 2019 23 53 44 08 00"
  split(dt_str,arr," ")
  Y=arr[3]
  M=mon_map(arr[2])
  D=arr[1]
  H=arr[4]
  m=arr[5]
  S=arr[6]
  return mktime(sprintf("%s %s %s %s %s %s",Y,M,D,H,m,S))
}

function mon_map(str   ,mons){
  mons["Jan"]=1
  mons["Feb"]=2
  mons["Mar"]=3
  mons["Apr"]=4
  mons["May"]=5
  mons["Jun"]=6
  mons["Jul"]=7
  mons["Aug"]=8
  mons["Sep"]=9
  mons["Oct"]=10
  mons["Nov"]=11
  mons["Dec"]=12
  return mons[str]
}

去掉/**/中間的註釋

示例數據:

/*AAAAAAAAAA*/
1111
222

/*aaaaaaaaa*/
32323
12341234
12134 /*bbbbbbbbbb*/ 132412

14534122
/*
    cccccccccc
*/
xxxxxx /*ddddddddddd
    cccccccccc
    eeeeeee
*/ yyyyyyyy
5642341

awk代碼:

# 註釋內的行
/\/\*/{
  # 同行有"*/"
  if(/\*\//){
    print gensub("(.*)/\\*.*\\*/(.*)","\\1\\2","g",$0)
  } else {
  # 同行沒有"*/"

    # 1.去掉/*行後的內容
    print gensub("(.*)/\\*.*","\\1","g",$0)

    # 2.繼續讀取,直到出現*/,並去掉中間的全部數據
    while( ( getline ) > 0 ){
      # 出現了*/行
      if(/\*\//){
        print gensub(".*\\*/(.*)","\\1","g",$0)
      }
    }
  }
}
# 非註釋內容
!/\/\*/{print}

先後段落關係判斷

從以下類型的文件中,找出false段的前一段爲i-order的段,同時輸出這兩段。

2019-09-12 07:16:27 [-][
  'data' => [
    'http://192.168.100.20:2800/api/payment/i-order',
  ],
]
2019-09-12 07:16:27 [-][
  'data' => [
    false,
  ],
]
2019-09-21 07:16:27 [-][
  'data' => [
    'http://192.168.100.20:2800/api/payment/i-order',
  ],
]
2019-09-21 07:16:27 [-][
  'data' => [
    'http://192.168.100.20:2800/api/payment/i-user',
  ],
]
2019-09-17 18:34:37 [-][
  'data' => [
    false,
  ],
]

awk代碼:

BEGIN{
  RS="]\n"
  ORS=RS
}
{
  if(/false/ && prev ~ /i-order/){
    print tmp
    print
  }
  tmp=$0
}

兩個文件的處理

有兩個文件file1和file2,這兩個文件格式都是同樣的。

需求:先把文件2的第五列刪除,而後用文件2的第一列減去文件一的第一列,把所得結果對應的貼到原來第五列的位置,請問這個腳本該怎麼編寫?

file1:
50.481  64.634  40.573  1.00  0.00
51.877  65.004  40.226  1.00  0.00
52.258  64.681  39.113  1.00  0.00
52.418  65.846  40.925  1.00  0.00
49.515  65.641  40.554  1.00  0.00
49.802  66.666  40.358  1.00  0.00
48.176  65.344  40.766  1.00  0.00
47.428  66.127  40.732  1.00  0.00
51.087  62.165  40.940  1.00  0.00
52.289  62.334  40.897  1.00  0.00
file2:
48.420  62.001  41.252  1.00  0.00
45.555  61.598  41.361  1.00  0.00
45.815  61.402  40.325  1.00  0.00
44.873  60.641  42.111  1.00  0.00
44.617  59.688  41.648  1.00  0.00
44.500  60.911  43.433  1.00  0.00
43.691  59.887  44.228  1.00  0.00
43.980  58.629  43.859  1.00  0.00
42.372  60.069  44.032  1.00  0.00
43.914  59.977  45.551  1.00  0.00
# 方法一:

awk '{
  f1 = $1
  if( (getline <"file2") >= 0 ){
    $5 = $1 - f1
    print $0
  }
}' file1

# 方法二:
awk '
  NR==FNR{arr[FNR]=$1}
  NR!=FNR{$5=$1-arr[FNR];print}
' file1 file2
相關文章
相關標籤/搜索