Linux文本編輯三劍客之---awk的使用

1、awk

1.1 認識awk

awk是一種編程語言,用於在linux/unix下對文本和數據進行處理。數據可以來自標準輸入(stdin)、一個或多個文件,或其它命令的輸出。它支持用戶自定義函數和動態正則表達式等先進功能,是linux/unix下的一個強大編程工具。它在命令行中使用,但更多是作爲腳本來使用。awk有很多內建的功能,比如數組、函數等,這是它和C語言的相同之處,靈活性是awk最大的優勢。

awk其實不僅僅是工具軟件,還是一種編程語言。不過,本文只介紹它的命令行用法,對於大多數場合,應該足夠用了。

1.2 使用awk

1.2.1 語法

awk [options] 'program' var=value file``…

awk [options] -f programfile var=value file``…

awk [options] 'BEGIN{ action;… } pattern{ action;… } END{ action;… }' file ...

1.2.2 常用命令選項

  • -F fs:fs指定輸入分隔符,fs可以是字符串或正則表達式,如-F:
  • -v var=value:賦值一個用戶定義變量,將外部變量傳遞給awk
  • -f scripfile:從腳本文件中讀取awk命令

1.3 awk變量

變量:內置和自定義變量,每個變量前加 -v 命令選項

1.3.1 內置變量

(1)格式

  • FS輸入字段分隔符默認爲空白字符
  • OFS輸出字段分隔符,默認爲空白字符
  • RS :輸入記錄分隔符,指定輸入時的換行符,原換行符仍有效
  • ORS :輸出記錄分隔符,輸出時用指定符號代替換行符
  • NF :字段數量,共有多少字段, NF引用最後一列,(NF-1)引用倒數第2列
  • NR行號,後可跟多個文件,第二個文件行號繼續從第一個文件最後行號開始
  • FNR :各文件分別計數, 行號,後跟一個文件和NR一樣,跟多個文件,第二個文件行號從1開始
  • FILENAME :當前文件名
  • ARGC :命令行參數的個數
  • ARGV :數組,保存的是命令行所給定的各參數,查看參數
$ cat awkdemo
hello:world
linux:redhat:lalala:hahaha
kevin:love:youou
  • -FS輸入字段分隔符默認爲空白字符
$ awk -v FS=":" '{print $1,$2}' awkdemo 
hello world
linux redhat
kevin love

當然也可以寫成另外一種形式:

$ awk -F: '{print $1,$2}' awkdemo 
hello world
linux redhat
kevin love

另外通過awk命令可以指定自己所需要的字符,比如e:

awk -v FS="e:" '{print $1,$2}' awkdemo 
hello:world 
linux:redhat:lalala:hahaha 
kevin:lov youou

但是如果用cut命令的話會導致報錯,因爲通過cut指定的分割符只能是單個字符,但是awk可以指定多個字符作爲分割符

$ cut -d "e:" -f1,2 awkdemo 
cut: the delimiter must be a single character
Try 'cut --help' for more information.
  • -OFS指定輸出分隔符
awk -v FS=':' -v OFS='---' '{print $1,$2}' awkdemo 

這樣便通過awk的方法將文本中的分隔符變成---

hello---world
linux---redhat
kevin---love
  • NF :字段數量,共有多少字段, $NF引用最後一列,$(NF-1)引用倒數第2列
$ awk -F: {'print NF'} awkdemo 
2
4
3
  • NR行號,後可跟多個文件,第二個文件行號繼續從第一個文件最後行號開始
$ awk '{print NR}' awkdemo awkdemo 
1
2
3
4
5
6
  • FNR :各文件分別計數, 行號,後跟一個文件和NR一樣,跟多個文件,第二個文件行號從1開始
$ awk '{print FNR}' awkdemo awkdemo 
1
2
3
1
2
3
  • 可以直接通過awk來統計這兩個文件一共多少行
$ awk END'{print NR}' awkdemo awkdemo 
6
  • FILENAME :當前文件名
$ awk "{print FILENAME}" awkdemo demo
awkdemo
awkdemo
awkdemo
demo
demo
demo
demo

1.3.2 自定義變量

自定義變量( 區分字符大小寫)

(1)-v var=value

① 先定義變量,後執行動作print

相當於在我的當前的文件的前面可以添加任何我想要的字段

$ awk -v name="kevin" -F: '{print name":"$0}' awkdemo 
kevin:hello:world
kevin:linux:redhat:lalala:hahaha
kevin:kevin:love:youou

② 在執行動作print後定義變量

$ awk -F: '{print name":"$0, name="kevin"}' awkdemo 
:hello:world kevin
kevin:linux:redhat:lalala:hahaha kevin
kevin:kevin:love:youou kevin

(2)在program 中直接定義

可以把執行的動作放在腳本中,直接調用腳本 -f

$ awk -F: -f awk.txt awkdemo 
kevin hello
kevin linux
kevin kevin

1.4 printf命令

比print更強大

1.4.1 格式

(1)格式化輸出

printf "FORMAT"``, item1,item2, ...

① 必須指定FORMAT

不會自動換行,需要顯式給出換行控制符,\n

③ FORMAT 中需要分別爲後面每個item 指定格式符

(2)格式符:與item 一一對應

  • %c: 顯示字符的ASCII碼
  • %d, %i: 顯示十進制整數
  • %e, %E: 顯示科學計數法數值
  • %f :顯示爲浮點數,小數 %5.1f,帶整數、小數點、整數共5位,小數1位,不夠用空格補上
  • %g, %G :以科學計數法或浮點形式顯示數值
  • %s :顯示字符串;例:%5s最少5個字符,不夠用空格補上,超過5個還繼續顯示
  • %u :無符號整數
  • %%: 顯示% 自身

(3)修飾符:放在%c[/d/e/f...]之間

  • #[.#]:第一個數字控制顯示的寬度;第二個# 表示小數點後精度,%5.1f
  • -:左對齊(默認右對齊) %-15s
  • +:顯示數值的正負符號 %+d

1.4.2 演示

當使用print的時候:

$ awk -F: '{print $1,$3}' /etc/passwd | head -n 2
root 0
bin 1
  • 第一列顯示小於20的字符串;第2列顯示整數並換行
$ awk -F: '{printf "%20s---%u\n",$1,$3}' /etc/passwd | head -n 2
                root---0
                 bin---1
  • 使用-進行左對齊;第2列顯示浮點數
$ awk -F: '{printf "%-20s---%-15.3f\n",$1,$3}' /etc/passwd | head -n 2
root                ---0.000          
bin                 ---1.000  

1.5 操作符

1.5.1 格式

  • 算術操作符:
    • x+y, x-y, x*y, x/y, x^y, x%y
    • -x: 轉換爲負數
    • +x: 轉換爲數值
  • 字符串操作符:沒有符號的操作符,字符串連接
  • 賦值操作符:
    • =, +=, -=, *=, /=, %=, ^=
    • ++, --
  • 比較操作符:
    • ==, !=, >, >=, <, <=
  • 模式匹配符:~ :左邊是否和右邊匹配包含 !~ :是否不匹配
  • 邏輯操作符:與&& ,或|| ,非!
  • 函數調用: function_name(argu1, argu2, ...)
  • 條件表達式(三目表達式):selector?if-true-expression:if-false-expression
    • 註釋:先判斷selector,如果符合執行 ? 後的操作;否則執行 : 後的操作

1.5.2 演示

(1)模式匹配符

  • 模式匹配符:~ :左邊是否和右邊匹配包含 !~ :是否不匹配
$ df -h |awk -F: '$0 ~ /^\/dev/'
/dev/mapper/cl-root                   165G   81G   84G  50% /
/dev/sdb1                              37T   30T  7.0T  81% /GP
/dev/sda1                             197M  155M   43M  79% /boot
/dev/mapper/cl-var                     50G  1.8G   49G   4% /var
/dev/sdc1                             3.7T  2.9T  788G  79% /media/sdisk/usb2
/dev/sdd1                             1.9T  1.2T  728G  61% /media/sdisk/usb3

這種操作模式有些類似於

$ df -h |awk -F: '$0' | grep '/dev/'
tmpfs                                 126G   21G  106G  17% /dev/shm
/dev/mapper/cl-root                   165G   81G   84G  50% /
/dev/sdb1                              37T   30T  7.0T  81% /GP
/dev/sda1                             197M  155M   43M  79% /boot
/dev/mapper/cl-var                     50G  1.8G   49G   4% /var
/dev/sdc1                             3.7T  2.9T  788G  79% /media/sdisk/usb2
/dev/sdd1                             1.9T  1.2T  728G  61% /media/sdisk/usb3

結合之前提到的NF :字段數量,共有多少字段, NF引用最後一列,(NF-1)引用倒數第2列

只顯示磁盤使用狀況和磁盤名

$ df -h | awk '$0 ~ /^\/dev/ {print $(NF-1)"---"$1}'
50%---/dev/mapper/cl-root
81%---/dev/sdb1
79%---/dev/sda1
4%---/dev/mapper/cl-var
79%---/dev/sdc1
61%---/dev/sdd1

找出磁盤佔比大於40%的
相當於在之前的命令的基礎上添加了應該對第一行的篩選

$ df -h | awk '$0 ~ /^\/dev/ {print $(NF-1)"---"$1}' |awk -F%  '$1>40'
50%---/dev/mapper/cl-root
81%---/dev/sdb1
79%---/dev/sda1
79%---/dev/sdc1
61%---/dev/sdd1

(2)邏輯操作符

$ awk -F: '$3 >=6 && $3<=66 {print $1,$3}' /etc/passwd 
shutdown 6
halt 7
mail 8
uucp 10
operator 11
games 12
gopher 13
ftp 14
rpc 32
oprofile 16
rpcuser 29
gdm 42
ntp 38
apache 48
mailnull 47
smmsp 51
mysql 27
pegasus 66
postgres 26
$ awk -F: '$3==0 || $3>1000 {print $1,$3}' /etc/passwd 
root 0
nfsnobody 65534

(3)條件表達式(三目表達式)

  • 先判斷selector,如果符合執行 ? 後的操作;否則執行 : 後的操作
$ awk -F: '{$3 >= 1000?usertype="common user":usertype="sysadmin user";print usertype,$1,$3}' /etc/passwd | head -n 30
sysadmin user root 0
sysadmin user bin 1
sysadmin user daemon 2
sysadmin user adm 3
sysadmin user lp 4
sysadmin user sync 5
sysadmin user shutdown 6
sysadmin user halt 7
sysadmin user mail 8
sysadmin user uucp 10
sysadmin user operator 11
sysadmin user games 12
sysadmin user gopher 13
sysadmin user ftp 14
sysadmin user nobody 99
sysadmin user dbus 81
sysadmin user usbmuxd 113
sysadmin user rpc 32
sysadmin user oprofile 16
sysadmin user vcsa 69
sysadmin user rtkit 499
sysadmin user abrt 173
sysadmin user hsqldb 96
sysadmin user avahi-autoipd 170
sysadmin user saslauth 498
sysadmin user postfix 89
sysadmin user rpcuser 29
common user nfsnobody 65534
sysadmin user haldaemon 68
sysadmin user gdm 42

1.6 awk PATTERN 匹配部分

1.6.1 格式

PATTERN:根據pattern 條件,過濾匹配的行,再做處理

(1)如果未指定:空模式,匹配每一行

(2)/regular expression/ :僅處理能夠模式匹配到的行,正則,需要用/ / 括起來

(3)relational expression:關係表達式,結果爲“真”纔會被處理

真:結果爲非0值,非空字符串

假:結果爲空字符串或0值

(4)line ranges:行範圍

startline(起始行),endline(結束行):/pat1/,/pat2/ 不支持直接給出數字,可以有多段,中間可以有間隔

(5)BEGIN/END 模式

BEGIN{}: 僅在開始處理文件中的文本之前執行一次

END{} :僅在文本處理完成之後執行

1.6.2 演示

$ awk -F: '{print $1,$2}' awkdemo 
hello world
linux redhat
kevin love
  • /regular expression/ :僅處理能夠模式匹配到的行,正則,需要用/ / 括起來
$ awk -F: '/kevin/{print $1,$2}' awkdemo 
kevin love

relational expression:關係表達式,結果爲“真”纔會被處理 ; 真:結果爲非0值,非空字符串

$ awk -F: "1{print $1}" awkdemo 
hello:world
linux:redhat:lalala:hahaha
kevin:love:youou

結果爲空字符串或0值時,以下代碼則不會被執行

$ awk -F: "0{print $1}" awkdemo
  • 匹配第一個首字母爲h到第一個首字母爲a的之間所有字符
$ awk -F: '/^h/,/^a/ {print $1}' awkdemo 
hello
linux
kevin
  • BEGIN/END 模式

- BEGIN{}: 僅在開始處理文件中的文本之前執行一次

- END{} :僅在文本處理完成之後執行

$ awk -F: 'BEGIN{print "第一列"}{print $1} END{print "結束"}' awkdemo
第一列
hello
linux
kevin
結束

1.7 awk有意思的案例

$ seq 10
1
2
3
4
5
6
7
8
9
10

因爲i=1爲真,所以全部打印

$ seq 10 | awk 'i=1'
1
2
3
4
5
6
7
8
9
10

因爲i=0爲假,所以不打印

$ seq 10 | awk 'i=0'

只打印奇數行;奇數行i進入時本身爲空,被賦爲!i,即不爲空,所以打印;偶數行i進入時本身不爲空,被賦爲!i,即爲空,所以不打印

$ seq 10 | awk 'i=!i'
1
3
5
7
9

解釋上一個操作,i在奇偶行的時候到底是一個什麼樣的值

$ seq 10 |awk '{i=!i;print i}'
1
0
1
0
1
0
1
0
1
0

當然你也可以選擇只打印偶數行,相當於是上邊打印奇數行的取反

$ seq 10 | awk '!(i=!i)'
2
4
6
8
10

當然爲了打印出偶數行,我們也可以對i進行賦值,這樣i在最開始的時候就不爲空,剛好與打印奇數行的時候相反

$ seq 10 | awk -v 'i=1' 'i=!i'
2
4
6
8
10

1、awk高階用法

1.1 awk控制語句—if-else判斷

(1)語法

if (condition){statement;…} [else statement] 雙分支

if (condition1){statement1} else if (condition2){statement2} else {statement3} 多分支

(2)使用場景:對awk 取得的整行或某個字段做條件判斷

(3)演示

$ awk -F: '{if($3>10 && $3<20)print $1,$3}' /etc/passwd
operator 11
games 12
gopher 13
ftp 14
oprofile 16

打印出所有路徑爲/bin/bash的所有用戶名以及路徑

$ awk -F: '{if($NF=="/bin/bash") print $1,$NF}' /etc/passwd | head -n 10
root /bin/bash
chenys /bin/bash
liutao /bin/bash
git-admin /bin/bash
sysadmin /bin/bash
zhanglc /bin/bash
mysql /bin/bash
changlp /bin/bash
zhanghc /bin/bash
wangt /bin/bash

輸出總例數大於3的行

$ awk -F: '{if(NF>2) print $0}' awkdemo
linux:redhat:lalala:hahaha
kevin:love:youou

第三列大於等於100的爲Common user用戶, 反之則爲root or Sysuser用戶

$ awk -F: '{if($3>=100) {printf "Common user: %s\n",$1} else{printf "root or Sysuser : %s\n",$1}}' 
/etc/passwd | head -n 20
root or Sysuser : root
root or Sysuser : bin
root or Sysuser : daemon
root or Sysuser : adm
root or Sysuser : lp
root or Sysuser : sync
root or Sysuser : shutdown
root or Sysuser : halt
root or Sysuser : mail
root or Sysuser : uucp
root or Sysuser : operator
root or Sysuser : games
root or Sysuser : gopher
root or Sysuser : ftp
root or Sysuser : nobody
root or Sysuser : dbus
Common user: usbmuxd
root or Sysuser : rpc
root or Sysuser : oprofile
root or Sysuser : vcsa

找到磁盤利用率超過40的設備名以及利用率

$ df -h | awk -F% '/^\/dev/ {print $1}' | awk '$NF>40{print $1,$NF}'
/dev/sda3 63
/dev/sdb2 74
/dev/sda2 69

做一個判斷,當test>=90的時候爲excellent,當60 < test <90的時候爲pass,當test<60的時候爲failed

$ awk 'BEGIN{test=100; if(test>=90){print "excellent"} else if(test>=60 && test <90){print "pass"} else{print "failed"}}'
excellent
$ awk 'BEGIN{test=55; if(test>=90){print "excellent"} else if(test>=60 && test <90){print "pass"} else{print "failed"}}'
failed
$ awk 'BEGIN{test=65; if(test>=90){print "excellent"} else if(test>=60 && test <90){print "pass"} else{print "failed"}}'
pass

1.2 awk控制語句—while循環

(1)語法

while``(condition){statement;…}

注:條件“真”,進入循環;條件“假”, 退出循環

(2)使用場景

對一行內的多個字段逐一類似處理時使用

對數組中的各元素逐一處理時使用

(3)演示

$ awk -F: '/^kevin/{i=1; while(i<=NF){print $i, length($i); i++}}' awkdemo
kevin 5
love 4
youou 5

爲分隔,顯示每一行的長度大於6的單詞和其長度

$ awk -F: '{i=1;while(i<=NF) {if(length($i)>=6){print $i,length($i)}; i++}}' 
awkdemo
redhat 6
lalala 6
hahaha 6

計算從1加到100的和

$ awk 'BEGIN{i=1;sum=0;while(i<=100){sum=sum+i;i++} {print sum}}'
5050

1.3 awk控制語句—do-while循環

(1)語法

do {statement;…} while (condition)

意義:無論真假,至少執行一次循環體

(2)計算1+2+3+...+100=5050

$ awk 'BEGIN{i=1; sum=0; do{sum=sum+i;i++}while(i<=100) {print sum}}'
5050

1.4 awk控制語句—for循環

(1)語法

for``(expr1;expr2;expr3) {statement;…}

(2)特殊用法:遍歷數組中的元素

for``(var in array) {``for``-body}

(3)演示

$ awk -F: '{for(i=1;i<=NF;i++){print $i,length($i)}}' awkdemo
hello 5
world 5
linux 5
redhat 6
lalala 6
hahaha 6
kevin 5
love 4
youou 5
$ cat sort.txt 
xiaoming m 90
xiaohong f 93
xiaohei m 80
xiaofang f 99

統計男m、女f 各自的平均分

$ awk '{m[$2]++; score[$2]+=$3} END{for(i in m){printf "%s:%6.2f\n",i,score[i]/m[i]}}' sort.txt 
m: 85.00
f: 96.00

1.5 數值\字符串處理

(1)數值處理

  • rand():返回0和1之間一個隨機數,需有個種子 srand(),沒有種子,一直輸出0.237788

演示:

$ awk 'BEGIN{print rand()}'
0.237788

當加了srand()之後,就可以正常輸出隨機數了

$ awk 'BEGIN{srand();print rand()}'
0.625523
$ awk 'BEGIN{srand();print rand()}'
0.592696
$ awk 'BEGIN{srand(); print int(rand()*100%50)+1}'
21

(2)字符串處理:

  • length([s]) :返回指定字符串的長度
  • sub(r,s,[t]) :對t 字符串進行搜索r 表示的模式匹配的內容,並將第一個匹配的內容替換爲s
  • gsub(r,s,[t]) :對t 字符串進行搜索r 表示的模式匹配的內容,並全部替換爲s 所表示的內容
  • split(s,array,[r]) :以r 爲分隔符,切割字符串s ,並將切割後的結果保存至array 所表示的數組中,第一個索引值爲1, 第二個索引值爲2,…

演示:
將前面文本中的第一個:改變成-

$ echo "2008:08:08 08:08:08" | awk 'sub(/:/,"-",$1)'
2008-08:08 08:08:08
$ echo "2008:08:08 08:08:08" | awk 'gsub(/:/,"-",$0)'
2008-08-08 08-08-08
  • split(s,array,[r]) :以r 爲分隔符,切割字符串s ,並將切割後的結果保存至array 所表示的數組中,第一個索引值爲1, 第二個索引值爲2,…
$ echo "2008:08:08 08:08:08" | awk '{split($0,i,":")} {for(n in i){print n,i[n]}}'
1 2008
2 08
3 08 08
4 08
5 08

1.8 awk中調用shell 命令

(1)system 命令

空格是awk 中的字符串連接符,如果system中需要使用awk中的變量可以使用空格分隔,或者說除了awk 的變量外其他一律用"" 引用 起來。

$ awk 'BEGIN{system("hostname")}'

R290-1.GenePlus
$ awk 'BEGIN{score=100; system("echo your score is " score)}'
your score is 100
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章