shell三劍客之awk 資料擷取工具 詳解
目錄
awk 資料擷取工具
①awk簡介
>>>awk是一種程式語言(解釋性語言,不需要編譯),用於資料擷取和報告的工具
>>>awk自動搜尋輸入的檔案,並把每個輸入行切分成多個欄位(欄位:根據分隔符分割成的單元)
>>>擅長處理資料庫和表型資料。適合編寫短小一次性程式
>>>centos預設的awk使用的是gawk,系統將awk通過軟連結來指向gawk
>>>可以使用新的內建函式和變數
>>>
>>>awk不需要宣告變數的資料型別,它內建字串型別和數值型別
②awk語法結構
awk [OPTIONS] [--] program filename_list
一個案例理解awk
>>>date檔案第一列為員工名字,第二列為時薪,第三列為工作時長
>>>需求:列印輸出時薪大於等於50,的員工名字和總薪資
[[email protected] test]$ cat date
Sam 50 8
Bei 55 7
Tom 48 9
Tim 80 8
[[email protected] test]$ awk '$2 >= 50 { print $1,$2*$3 }' date
Sam 400
Bei 385
Tim 640
說明:awk命令後面的程式用單引號引起來,單引號後面的部分為輸入檔案
單引號中包圍的內容是一個awk程式,是一個program即模式-動作語句(pattern-action)
其中模式pattern為'$2>=50',表示掃描每一個輸入的行,如果第二列大於0,則執行動作action,否則不執行action
動作action為'{ print $1,$2*$3 }',模式匹配成功後執行的對應動作,該動作用於列印輸出第一個欄位和第二、三欄位的乘積
OPTIONS
選項 |
描述 |
-V |
顯示awk的版本 |
-f program-file |
指定包含了awk命令的檔案,不是從命令列引數中去讀取 可以通過-f選項指定多個包含awk命令檔案 |
-F'fs' --field-separator |
指定分隔符即為定義FS變數對應的值 |
-v var=val |
在程式開始之前,將val這個值賦給var這個變數 |
-d[file] |
將全域性變數的型別和最終值排序,並將排序好後的結果列印輸出到檔案中 |
PROGRAM——'pattern{ action }'(awk的核心)
關於pattern { action }的說明:
>>>pattern與action並非同時存在,可以省略其中一個
>>>如果action被省略,預設動作是將每個匹配的行輸出
>>>如果pattern被省略,對於每行都會執行動作
awk基本操作流程:
>>>從輸入流中讀取一行內容,然後使用pattern{action}去處理
>>>對讀取的行進行掃描搜尋,搜尋行中是否有內容被pattern匹配
>>>如果行被pattern匹配成功,則執行動作{action}
如果行中內容不被pattern匹配,則不輸出
>>>繼續從輸入流中讀取新的一行,重複上述幾個步驟
pattern
>>>pattern,即模式,是決定awk程式中定義的動作action是否能夠被執行的關鍵
>>>pattern支援的規則:正則表示式,字串與數字比較,流程控制語句
模式表示方式彙總:
BEGIN
END
/regexp/
relational expression :
pattern && pattern
pattern || pattern
BEGIN :放在程式開頭,當awk從輸入流中讀取資料之前,BEGIN語句開始執行(初始化)
常見用法:改變分隔符 (FS、OFS、ORS)
可以執行多個BEGIN,按順序執行,多個END同樣適用
END :放在程式末尾,當所有輸入流被讀取完畢,END語句開始執行(掃尾)
>>>案例
[[email protected] test]$ cat date
Sam 50 8
Bei 55 7
Tom 48 9
Tim 80 8
[[email protected] test]$ awk 'BEGIN{OFS=":";ORS="\n\n"} BEGIN{print "name:total_salary"} {print $1,$2*$3}' date
name:total_salary
Sam:400
Bei:385
Tom:432
Tim:640
說明:第一個BEGIN是對輸出欄位分隔符OFS和輸出記錄分隔符ORS的賦值
第二個BEGIN語句是輸出字串 "name:total_salary"
執行完BEGIN後,awk再從輸入流中讀取處理資料
正則之字串匹配模式/regexp/(使用的是拓展的正則表示式)
>>>/regexp/
當前輸入行包含能被regexp匹配的子字串時,該模式被匹配
>>>expression ~ /regexpr/
允許正則表示式限定在記錄中的特定欄位
例如$1 ~ /regexp/ 只匹配第一個欄位符合表示式的記錄,若$1匹配/regexp/,則模式被匹配
>>>expression !~ /regexpr/
若expression不包含regexp字元,則模式被匹配
模式表示式 relational expression
>>>當表示式擁有一個數值形式的值,運算子要求一個字串值,則awk會將該數值自動轉換成字串
>>>當表示式擁有一個字串形式的值,運算子要求一個數值,則awk會將該字串值自動轉換成數值
>>>關於表示式真與假的說明:如果一個表示式,對當前述入行的求值結果非零或不為空,那麼該行就被匹配
>>>如果比較字串,比較時是逐字元依賴ASCII字元表比較
比較運算子彙總
運算子 |
描述 |
< |
小於 |
<= |
小於等於 |
== |
等於 |
!= |
不等於 |
> |
大於 |
>= |
大於等於 |
~ |
匹配 |
!~ |
不匹配 |
表示式運算子彙總(也可以用在action)
操作 |
運算子 |
例子 |
賦值 |
=、+=、-=、*=、/=、^= |
sum+=i 即sum=sum+i |
條件表示式 |
?: |
x?y:z 若x為真則為y,否則為z |
邏輯或 |
|| |
x||y 若x或y為真,則表示式為真 |
邏輯與 |
&& |
x&&y 若x與y都為真,則表示式為真 |
陣列成員 |
in |
i in a |
匹配 不匹配 |
~ !~ |
|
自增自減 |
++、-- |
|
欄位 |
$ |
$1 表示輸入行的第一個欄位 |
組合 |
( ) |
($2+1) 表示第三個欄位 |
複合模式
>>>通過()括號、||或、&&與、!非 組合
pattern && pattern
pattern || pattern
需求:輸出txt第一和二個欄位都為數字或第三和四個欄位都為字母的行
[[email protected] test]$ cat txt
1 2 1 2
2 3 2 a
3 2 a b
4 a b d
a b 1 2
[[email protected] test]$ awk '($1~/[0-9]/&&$2~/[0-9]/)||($3~/[a-zA-Z]/&&$4~/[a-zA-Z]/){print $0}' txt
1 2 1 2
2 3 2 a
3 2 a b
4 a b d
說明:$1~/[0-9]/與$2~/[0-9]/之間和$3~/[a-zA-Z]/與$4~/[a-zA-Z]之間是與的關係
($1~/[0-9]/&&$2~/[0-9]/)和($3~/[a-zA-Z]/&&$4~/[a-zA-Z]/)之間是或的關係
action
格式化輸出
>>>在print語句中,多個變數之間使用逗號作為分隔符,對變數進行分隔
案例
[[email protected] test]$ cat awk.txt
Beth 3.00 0
Susie 4.50 40
Bei 4.00 50
Dan 4.00 50
[[email protected] test]$ cat awk.txt | awk '{print $1,$2}'
Beth 3.00
Susie 4.50
Bei 4.00
Dan 4.00
>>>NF(number of field)表示欄位數,$NF表示最後一個欄位
[[email protected] test]$ awk '{print NF,$1,$NF}' awk.txt
3 Beth 0
3 Susie 40
3 Bei 50
3 Dan 50
>>>awk提供了一個內建變數NR(number of row),該變數用於記錄當前從輸入流中讀取的行的行號
需求:輸出時在每一行行首加上行號
[[email protected] test]$ awk '{print NR,$0}' awk.txt
1 Beth 3.00 0
2 Susie 4.50 40
3 Bei 4.00 50
4 Dan 4.00 50
#可以將字串放在欄位中的任意位置
[[email protected] test]$ awk '{print ("The line_number is",NR,$0)}' awk.txt
The line_number is 1 Beth 3.00 0
The line_number is 2 Susie 4.50 40
The line_number is 3 Bei 4.00 50
The line_number is 4 Dan 4.00 50
>>>awk分隔符:FS變數、OFS變數和ORS變數
FS(Field Separator)欄位分隔符,即讀取輸入流時,以FS作為欄位分隔的依據,預設情況下為一個空格符
OFS(Output Field Separator)輸出欄位分隔符,預設情況下為一個空格符
ORS(Output Record Separator)輸出記錄分隔符,預設情況下為一個換行符
#需求:print輸出/etc/passwd的第一列和最後一列,欄位之間用冒號隔開,每一行之間還有一個空行
[[email protected] test]$ awk 'BEGIN{FS=":";OFS=":";ORS="\n\n"}{print $1,$NF}' /etc/passwd
root:/bin/bash
bin:/sbin/nologin
daemon:/sbin/nologin
adm:/sbin/nologin
……
注意:對變數FS、OFS和ORS賦值時,需要使用雙引號,不能沒有引號或使用單引號
>>>printf語句
語法格式:printf(format,expression1,expression2…)
>>>引數format是必須的,包含百分號%、對齊方式、格式控制字元、字串最大寬度(format最後需要新增\n才會換行)
>>>format 之間不用 ","隔開 而是用空格隔開
printf格式控制字元
字元 |
表示式將被列印成 |
$1 |
printf(format,$1) |
%c |
ASCII字元 |
97 |
a |
%d |
十進位制整數 |
97.5 |
97 |
%5d |
97.5 |
97 |
|
%e |
[-]d.dddddd[+-]dd |
97.5 |
9.750000e+01 (01表示10的次方為1) |
%f |
[-]ddd.dddddd |
97.5 |
97.500000 |
%7.2f |
97.5 |
97.50 |
|
%g |
按照e或f進行轉換 |
97.5 |
97.5 |
%o |
無符號八進位制數 |
97 |
141 |
%06o |
97 |
000141 |
|
%x |
無符號十六進位制數 |
97 |
61 |
%s |
字串 |
January |
January |
%10s |
January |
January |
|
%-10s |
January |
January (後面還有三個空格) |
|
%.3s |
January |
Jan |
|
%10.3s |
January |
Jan |
|
%-10.3s |
January |
Jan (後面還有七個空格) |
|
% |
列印一個百分號%,不會有引數被吸收 |
對齊方式:
連字元 '-' 表示左對齊,不使用連字元預設是右對齊
字串最大寬度:
為了達到規定寬度,必要時填充空格,前導的0用零填充
>>>awk程式可以使用重定向運算子>或>> 使用>>重定向,開啟檔案時,不會清空檔案內容,並追加檔案到尾部 使用> 重定向,開啟檔案時,會清空檔案內容,再把內容追加到檔案
awk '$3 > 100 {print $0 >> "file_01"}'
注意:重定向時,檔名必須使用雙引號引起來,否則會將其當做一個未初始化的變數來使用
awk '$3 > 100 {print $0 > "file_01"}'
重定向時,會先將檔案file_01原來的內容刪除,相當於覆蓋原來的內容
輸出到管道|,並排序sort
>>>語句: {print(…)|"sort …"} 注意:sort命令需要用雙引號引起來
需求:date檔案是三門科目的考試成績,需要格式化輸出(名字,三科成績,總成績),再以總成績降序排序
[[email protected] test]$ cat date
Simon 96 97 99
Bei 92 100 95
Tom 100 92 97
Tim 80 99 99
[[email protected] test]$ awk '{printf("%-7s %-3d %-3d %-3d %-3d\n",$1,$2,$3,$4,$2+$3+$4)|"sort -nr -k 5"}' date
Simon 96 97 99 292
Tom 100 92 97 289
Bei 92 100 95 287
Tim 80 99 99 278
#或者將awk的輸出通過管道符傳遞給sort命令實現將格式化輸出後的內容進行排序
[[email protected] test]$ awk '{printf("%-7s %-3d %-3d %-3d %-3d\n",$1,$2,$3,$4,$2+$3+$4)}' date | sort -nr -k 5
③變數
>>>表示式可以包含若干種類型的變數:內建的,欄位或使用者定義的
>>>變數不需要事先宣告,所以awk需要根據上下文環境推斷出變數的型別(字串或數值)
內建變數
>>>內鍵變數可以簡單理解是awk專屬的環境變數,
變數 |
意義 |
預設值 |
ARGC |
命令列引數的個數 |
- |
ARGV |
命令列引數陣列 |
- |
FILENAME |
當前輸入檔名 |
- |
FNR |
當前輸入檔案記錄個數 |
- |
FS |
輸入行欄位分隔符 |
" " |
NF |
當前記錄的欄位個數 |
- |
NR |
到目前為止讀的記錄數量 |
- |
OFMT |
數值的輸出格式 |
"%.6g" |
OFS |
輸出欄位分隔符 |
" " |
ORS |
輸出的記錄分隔符 |
"\n" |
RLENGTH |
被函式match匹配的字串的長度 |
- |
RS |
控制著輸入行的記錄分隔符 |
"\n" |
案例:ARGC與ARGV
需求:格式化輸出檔案date算出總數,並在每一行尾加上hello world兩個欄位,最後一行輸出ARGC和ARGV
[[email protected] test]$ cat date
Simon 96 97 99
Bei 92 100 95
Tom 100 92 97
Tim 80 99 99
[[email protected] test]$ cat awk.sh
#!/bin/bash
awk 'BEGIN{
printf("%-10s%-10s%-10s%-10s%-10s\n","name","chinese","maths","english","total")
}
{
total = $2 + $3 + $4
printf("%-10s%-10.0f%-10.0f%-10.0f%-10.0f%-10s%-10s\n",$1,$2,$3,$4,total,a,b)
}
END{
print(ARGC,ARGV[0],ARGV[1],ARGV[2],ARGV[3])
}' a=hello b=world date
[[email protected] test]$ bash awk.sh
name chinese maths english total
Simon 96 97 99 292 hello world
Bei 92 100 95 287 hello world
Tom 100 92 97 289 hello world
Tim 80 99 99 278 hello world
4 awk a=hello b=world date
說明:print(ARGC,ARGV[0],ARGV[1],ARGV[2],ARGV[3]) 對於這一行輸出的理解
4 awk a=hello b=world date
ARGC是命令列引數個數,除了自己定義的兩個變數"a","b",還有一個引數儲存程式名,一個引數儲存檔名
ARGV[0],即第一個引數,表示awk程式的程式名,為"awk"這個字串
ARGV[1],ARGV[2] ,表示屬組中自己定義的第一個引數和第二個引數
ARGV[3],即最後一個引數,表示檔案的檔名
欄位變數
>>>對記錄通過欄位分隔符分隔後的一個個單元,即為欄位,對欄位進行引用即為欄位變數
>>>欄位變數可以用在算術或字串運算中,也可以被賦值,即欄位變數是可以修改的
④流程控制
>>>流程控制語句中,if-else用於決策,while、for、do用於迴圈
if-else語句
語法結構:
{
if (expression)
statements
else
statements
}
或者
{if (expression) statements;else statements}
案例
[[email protected] test]$ cat date
Simon 96 97 99
Bei 92 100 95
Tom 100 92 97
Tim 80 99 99
[[email protected] test]$ cat control.awk
#!/bin/bash
{
if ($2==100||$3==100||$4==100)
print ( $1 " has a full score in subjects." )
else
print ( $1 " need to work hard." )
}
[[email protected] test]$ awk -f control.awk date
Simon need to work hard.
Bei has a full score in subjects.
Tom has a full score in subjects.
Tim need to work hard.
while語句
語法結構:
{
while (expression)
{
statement1
statement2
……
}
}
或者
{while (expression) {statements;else statements}}
說明:
>>>在while迴圈中,expression被求值,如果expression的值為真則statements會被執行,然後expression再被求值,只要expression為真,迴圈就一直被執行下去
>>>每從輸入流讀取一行,while迴圈就會被執行
案例:對txt每行求和
[[email protected] test]$ cat txt
1 2 1 2
2 3 2 5 3
3 2
4 3 2
1 1 2
[[email protected] test]$ cat control.awk
{
i=1
sum=0
while (i<=NF)
{
sum+=$i
i++
}
print("line"NR,"Sum="sum)
}
[[email protected] test]$ awk -f control.awk txt
line1 Sum=6
line2 Sum=15
line3 Sum=5
line4 Sum=9
line5 Sum=4
for語句
語法結構:
{
for (expression1;expression2;expression3)
{
statement1;
statement2;
……
}
}
或者
{for (expression1;expression2;expression3) {statement1;statement2;statement3}}
expression1對變數初始化
expression2用來做條件判斷
expression3用來做變數變化,例如自增自減
案例:對txt每行求和
[[email protected] test]$ cat txt
1 2 1 2
2 3 2 5 3
3 2
4 3 2
1 1 2
[[email protected] test]$ cat control.awk
{
sum=0
for (i=1;i<=NF;i++)
{
sum+=$i
}
print("line"NR,"Sum="sum)
}
[[email protected] test]$ awk -f control.awk txt
line1 Sum=6
line2 Sum=15
line3 Sum=5
line4 Sum=9
line5 Sum=4
do-while語句
語法結構:
{
do
{
statement1;
statement2;
……
}
while (expression)
}
或者
{do{statement1;statement2;……} while (expression)}
說明
>>>do-while語句與while、for迴圈相比,它的測試條件在迴圈體底部,所以迴圈體至少會被執行一次
break、continue語句
>>>這兩個語句用於影響迴圈的執行
>>>break會導致控制流馬上從包圍著它的迴圈內退出,迴圈包括while、for和do-while
>>>continue導致下一次迭代開始,即為提前進入當前迴圈的下一個輪循
break案例:只對txt檔案,每行的前三個欄位求和
[[email protected] test]$ cat txt
1 2 1 2
2 3 2 5 3
3 2
4 3 2
1 1 2
[[email protected] test]$ cat control.awk
{
sum=0
for (i=1;i<=NF;i++)
{
if (i>3)
{
break
}
sum+=$i
}
print("line"NR,"Sum="sum)
}
[[email protected] test]$ awk -f control.awk txt
line1 Sum=4
line2 Sum=7
line3 Sum=5
line4 Sum=9
line5 Sum=4
continue案例:只對txt檔案,值小於3的欄位求和
[[email protected] test]$ cat txt
1 2 1 2
2 3 2 5 3
3 2
4 3 2
1 1 2
[[email protected] test]$ cat control.awk
{
sum=0
for (i=1;i<=NF;i++)
{
if ($i>=3)
{
continue
}
sum+=$i
}
print("line"NR,"Sum="sum)
}
[[email protected] test]$ awk -f control.awk txt
line1 Sum=6
line2 Sum=4
line3 Sum=2
line4 Sum=2
line5 Sum=4
exit、next語句
>>>next使得awk抓取下一個輸入行
>>>exit會導致程式終止,如果END語句存在會執行END動作
⑤陣列
>>>awk提供了一維陣列,用於存放字串與數值
>>>在awk中,陣列可以稱之為關聯陣列(association array),關聯陣列在陣列下標index和元素value之間建立了一種關聯
>>>陣列與陣列元素不需要事先宣告,也不需要說明陣列有多少個元素,即為無須定義並且可以直接使用,自動擴充套件
>>>awk的陣列下標可以是字串也可以是數值
例如:arrat[1]="bei"為數值下標;array["first"]="bei"為字串下標
注意:因為1的字串值與"1"是相同的,所以array[1]與array["1"]是同一個元素,但01和1的字串值不相同
>>>如果下標使用的是字串,一定要用雙引號將字串引起來,否則會把字串當做一個未初始化過的變數(變數為空字串),於是value會賦值到array[""]中
案例:使用陣列倒序輸出檔案date
[[email protected] test]$ cat date
IP1 1.1.1.1
IP2 192.168.1.1
IP3 172.168.1.1
[[email protected] test]$ awk '{x[NR]=$0} END{for(i=NR;i>0;i--){print x[i]}}' date
IP3 172.168.1.1
IP2 192.168.1.1
IP1 1.1.1.1
說明:x[NR]=$0將行號作為index,行的內容作為value,建立關聯陣列x
for(i=NR;i>0;i--){print x[i]},這是個for迴圈,用來遍歷陣列x,此時i=NR等於尾行的行號,即迴圈第一次輸出的是尾行,然後i自減,遍歷倒數第二行,以此迴圈,實現倒序輸出
案例:將第一個欄位相同的行的第二個欄位累加作為value,賦值給下標為第一個欄位的陣列size
[[email protected] test]$ cat txt
file 20
file 30
file 10
directory 14
directory 8
symbolic_link 4
symbolic_link 18
[[email protected] test]$ cat array.awk
{
size[$1]+=$2
}
END {
for (i in size)
{
print (i,size[i])
}
}
[[email protected] test]$ awk -f array.awk txt
file 60
directory 22
symbolic_link 22
運算元組
>>>陣列引用是如下所示的表示式:array[index]
array即為陣列的名稱,index即為元素的索引,可以理解為下標
判斷陣列是否存在某個元素
>>>語法格式:index in array
>>>這個表示式用於測試特定索引是否存在,如果不存在不會產生建立該元素的副作用
例
if (2 in array)
print "Subscript 2 is present."
>>>但是以下方案會導致在陣列array中建立array[2]:
if (array[2] != "")
print "Subscript 2 is present."
刪除陣列中的元素
>>>delete array[index]
⑥內建函式
內建算術函式
字串函式
sub(regexp,replacement[,target])
>>>搜尋taget中最左並且是最長的,能被regexp匹配的子字串,使用replacement進行替換,返回替換髮生的次數
>>>regexp為一個正則表示式,格式為/regexp/,需要用//
>>>target可以省缺,搜尋匹配的物件是$0,即為輸入流那一行的內容
>>>當replacement中有&符號,表示對regexp的反向引用,用來拼接使用
(如