1. 程式人生 > >Linux文字處理三劍客之awk

Linux文字處理三劍客之awk

awk簡介

awk是一種解釋執行的程式語言,用來專門處理文字資料,其名稱是由它們設計者的名字縮寫而來 ———Afred Aho,Peter WeinbergerBrian Kernighan。常見版本有:
- awk: 最原初的版本,它由 AT&T 實驗室開發
- nawk:awk的改進增強版本
- gawk:GNU awk,所有的 GNU/Linux 發行版都包括gawk,且完全相容awk與nawk

工作原理

awk -v FS=”:” -v OFS=”,” ‘{print 1,3}’ /etc/passwd

1.awk對檔案或管道的內容一次只處理一行,將獲取搭配的這一行賦值給內部變數$0
2.將取到的一行的內容按awk內部變數FS定義的分割符(預設為空格,包含tab製表符,上面的例子通過-v

選項指定分解符為為分隔符)分為多個欄位,每一段儲存在12…開始的變數中
3.awk中print命令列印欄位;{print 1,3} 只取有用的第一段和第三段;在列印時13之間由空格間隔。“,”逗號是一個對映到內部的輸出欄位分隔符(OFS),OFS變數預設為空格,例子中指定為輸出分隔符。接下來,awk處理下一行資料,直到所有的行處理完。

語法

awk [option] program FILE1...
program:PATTERN{ACTION STATEMENTS}
欄位說明:

option:常用選項有-F-v
program :匹配並操作文字
- {action}

:每一次輸入的行都會執行一次主體部分的命令
- PATTERN:限定對於輸入的行的規則,可選

以下詳細說明awk的各個欄位…

用法

option

-F:指明輸入時用到的欄位分隔符,預設為空白字元
-v var=value:自定義變數
-f:讀取檔案中的awk規則

awk -v str='hello' '{print str,$1}' file1       
awk 'BEGIN{str="hello"}{print str,$1}' file1    # 效果同上
awk -f file1                                    # awk規則寫到指令碼中讀取

program

1.PATTERN

empty:空模式,匹配每一行
/regular expression/:僅處理能夠被此處的模式匹配到的行
relational expression: 關係表示式;結果有“真”有“假”;結果為“真”才會被處理;真:結果為非0值,非空字串;
line ranges:行範圍
startline,endline:/pat1/,/pat2/ #指明起始行結束行模式 note: 不支援直接給出數字的格式

BEGIN/END模式
BEGIN{}: 僅在開始處理檔案中的文字之前執行一次
END{}:僅在文字處理完成之後執行一次

awk '/UUID/{print $1}' /etc/fstab   #處理匹配到UUID的行
awk '!/UUID/{print $1}' /etc/fstab  #處理UUID以外的行
awk -F : '$3<=500{print $1}' /etc/passwd  #關係表示式匹配

awk -F : '$NF=="/bin/bash"{print $1,$NF}' /etc/passwd       #關係表達是用法
awk -F : '$NF~/bash$/{print $1,$NF}' /etc/passwd            #用模式匹配符匹配
awk -F : '/root/,/ftp/{print $1,$NF}' /etc/passwd
awk -F: '(NR>=2&&NR<=10){print $1}' /etc/passwd

2.常用action

print
print item1, item2, ...

要點:
- 逗號分隔符;
- 輸出的各item可以字串,也可以是數值;當前記錄的欄位、變數或awk的表示式;
- 如省略item,相當於print $0;

awk '{print "line one\nline two\nline three"}'

printf
格式化輸出:printf FORMAT, item1, item2, ...
要點:
- FORMAT必須給出
- 不會自動換行,需要顯式給出換行控制符,\n
- FORMAT中需要分別為後面的每個item指定一個格式化符號

格式符:
%c: 顯示字元的ASCII碼
%d, %i: 顯示十進位制整數
%e, %E: 科學計數法數值顯示
%f:顯示為浮點數
%g, %G:以科學計數法或浮點形式顯示數值
%s:顯示字串
%u:無符號整數
%%: 顯示%自身

修飾符:
#[.#]:第一個數字控制顯示的寬度;第二個#表示小數點後的精度;
- : 左對齊
+:顯示數值的符號

awk -F: '{printf "%s\n",$1}' /etc/passwd 
awk -F: '{printf "Username: %s\n",$1}' /etc/passwd
awk -F : '{printf "Username:%15s\n",$1}' /etc/passwd  #用修飾符控制輸出字元的寬度
awk -F : '{printf "Username:%15s\n",$1}' /etc/passwd  #左對齊

操作符

算術操作符:X+, -, *,/,%,**,^ Y (X^Y Y為X的次方)
賦值操作符:X =, +=, -=, *=, /=, %=, **=, ^=, Y ++,--
比較操作符:X >,>=,<,<=,==,!=,~,!~ Y
邏輯關係符:X &&,||Y
三元運算子:selector? if-true-exp: if-false-exp

#輸出系統使用者
awk -F: '{$3>=1000?usertype="Common User":usertype="Sysadmin or SysUser";printf "%15s:%-s\n",$1,usertype}' /etc/passwd

條件控制

  • if-else: if (condition) {then-body} else {[ else-body ]}
awk -F: '{if ($1=="root") print $1, "Admin"; else print $1, "Common User"}' /etc/passwd
awk -F: '{if ($1=="root") printf "%-15s: %s\n", $1,"Admin"; else printf "%-15s: %s\n", $1, "Common User"}' /etc/passwd
awk -F: -v sum=0 '{if ($3>=500) sum++}END{print sum}' /etc/passwd
  • while:while (condition){statement1; statment2; …}
awk -F: '{i=1;while (i<=3) {print $i;i++}}' /etc/passwd
awk -F: '{i=1;while (i<=NF) { if (length($i)>=4) {print $i}; i++ }}' /etc/passwd
  • do-while:do {statement1, statement2, …} while (condition)
awk -F: '{i=1;do {print $i;i++}while(i<=3)}' /etc/passwd
  • for:
    for ( variable assignment; condition; iteration process) { statement1, statement2, ...}
    for (i in array) {statement1, statement2, ...}
awk -F: '{for(i=1;i<=3;i++) print $i}' /etc/passwd
awk -F: '{for(i=1;i<=NF;i++) { if (length($i)>=4) {print $i}}}' /etc/passwd
awk -F: '$NF!~/^$/{BASH[$NF]++}END{for(A in BASH){printf "%15s:%i\n",A,BASH[A]}}' /etc/passwd
  • case:switch (expression) { case VALUE or /REGEXP/: statement1, statement2,... default: statement1, ...}
awk 'BEGIN{foo=0;switch(foo){case 0:print "yes";break; case 1:print "no";break; default: print "unknow"}}'
  • next:提前結束對本行文字的處理,並接著處理下一行
awk -F: '{if($3%2==0) next;print $1,$3}' /etc/passwd    # 顯示其ID號為奇數的使用者

awk高階應用

內建變數
$0:匹配到的行的全部欄位
$1~$#:匹配到行分割後的指定欄位
NF:number of field,欄位個數
FS:input field seperator,輸入的分隔符,預設為空白字元
OFS:output field seperator,輸出的分隔符,預設為空白字元
RS:input record seperator,輸入的換行符
ORS:output record seperator,輸出時的換行符
NR:number of record,當前記錄的欄位數,如果多檔案時編號累加
FNR:與NR不同的是,FNR用於記錄正處理的行是當前這一檔案中被總共處理的行數
FILENAME:awk命令所處理的檔案的名稱

陣列
array[index]
index可以使用任意字串,當某資料組元素不存在時,要引用其時,awk會自動建立此元素並初始化為空串,因此,要判斷某資料組中是否存在某元素,需要使用index in array的方式。
- 遍歷:for (var in array) { statement1, … }
- 刪除陣列變數:delete array[index]

 #每出現一被/^tcp/模式匹配到的行,陣列S[$NF]就加1,NF為當前匹配到的行的最後一個欄位,此處用其值做為陣列S的元素索引;
 netstat -ant | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'    

 #統計某日誌檔案中IP地的訪問量
awk '{counts[$1]++}; END {for(url in counts) print counts[url], url}' /var/log/httpd/access_log  

ARGC:命令列中引數的個數,其awk命令也算一個引數
ARGV:其是一個數組,儲存的是命令列所給定的各引數

awk '{print NF}' /etc/fstab         # 每行欄位數,這裡是數量引用,不是對應的值引用
awk '{print $NF}' /etc/fstab        # 每行中的最後一個欄位的值
awk '{print NR}' file1 file2        # 會每把所有文件進行總的編號,而不是單獨對檔案進行編號
awk '{print FNR}' file1 file2       # 會對每個檔案的行數進行單獨的編號顯示
awk '{print FILENAME}' file1        # 顯示當前檔名,但會每行顯示一次
awk 'END{print FILENAME}' file1     # 顯示當前檔名,但只會顯示一次
awk 'END{print ARGC}' /etc/fstab    # 顯示共有幾個引數

內建函式

rand():隨機數,大於等於0,小於1
gsub(str1, str2, str):在str中將str1全部替換為str2,支援配符查詢
index(str1, str2 ):在str1內找首次出現str2出現的位置,沒有則返回0
split(str,array[,fieldsep[,seps]]):將str以fieldsep為分隔符分隔,並將結果儲存至array陣列中,下標為從0開始
length([str]):返回string字串中字元的個數
substr(str, start [, length]):擷取字串,從start開始,取length個,start從1開始計數
system(command):執行系統命令並將結果返回至awk
systime():系統當前時間戳
mktime("YYYY MM DD HH MM SS[ DST]"):指定日期時間戳,注意加引號
strftime(format [,timestamp [,utc-flage]]):生成指定格式時間,具體格式參考date命令
tolower(str):轉為小寫
toupper(str):轉為大寫

awk 'BEGIN{str="this is a test";split(str,A," ");print length(A);for(i in A){print i,A[i];}}'
awk 'BEGIN{str="str std str2";gsub("str","STR",str);print str}' 
awk 'BEGIN{str="this is a test";b=match(str,"is");print b}'
awk 'BEGIN{str="this is a test2016";print substr(str,4,6)}' 
netstat -ant | awk '/:80\>/{split($5,clients,":");IP[clients[1]]++}END{for(i in IP){print IP[i],i}}' | sort -rn | head -50
awk 'BEGIN{print mktime("2016 06 21 11 22 33")}'
awk 'BEGIN{print strftime("%Y-%m-%d %H:%M:%S",systime())}'
awk '{system("mkdir " ($NF-1)}' filename    # 倒數第二個欄位做為目錄名建立目錄

格式總是有問題,更好的閱讀體驗歡迎到訪
以上內容參考馬哥筆記。。。