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
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,646評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,595評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,560評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,035評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,814評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,224評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,301評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,444評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,988評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,804評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,998評論 1 370
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,544評論 5 360
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,237評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,665評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,927評論 1 287
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,706評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,993評論 2 374

推薦閱讀更多精彩內容