awk--基本操作一

通過學(xué)習(xí)《awk精通》整理

作者: 駿馬金龍
學(xué)習(xí)鏈接: https://www.junmajinlong.com/how_to_nav_posts/
學(xué)習(xí)來源: 駿馬金龍
思維導(dǎo)圖查看

what

awk是一個文本處理工具

awk基本用法

鋪墊:文件讀取的幾種方式

1. 按字符數(shù)量讀取:每一次可以讀取一個字符,或者多個字符,直到把整個文件讀取完
         while read -n 1 char;do echo $char;done <a.txt
2. 按照分隔符進行讀取:一直讀取直到遇到了分隔符才停止,下次繼續(xù)從分隔的位置處向后讀取,直到讀完整個 文件
while read -d "m" chars;do echo "$chars";done <a.txt 
# 以字符'm'為分割點
3. 按行讀取:每次讀取一行,直到把整個文件讀完
是按照分隔符讀取的一種特殊情況:將分隔符指定為了換行符 \n while read line;do echo "$line";done <a.txt
4. 一次性讀取整個文件 是按字符數(shù)量讀取的特殊情況,也是按分隔符讀取的特殊情況
read -N 10000000 data <a.txt
echo "$data #展示

read -d '_' data <a.txt
echo "$data" #展示

awk用法入門

 awk 'awk_program' a.txt
  • a.txt是awk要讀取的文件,可以是0個文件或一個文件,也可以多個文件
    • 如果不給定任何文件,但又需要讀取文件,則表示從標(biāo)準(zhǔn)輸入中讀取
  • 單引號 包圍的是awk代碼,也稱為awk程序
    • 盡量使用單引號,因為在awk中經(jīng)常使用 符號,而符號在Shell是變量符號,如果使用雙引號包圍 awk代碼,
      符號會被Shell解析成Shell變量,然后進行Shell變量替換。使用單引號包圍awk代碼, 則 會脫離Shell的魔掌,使得$符號留給了awk去解析
  • awk程序中,大量使用大括號,大括號表示代碼塊,代碼塊中間可以之間連用,代碼塊內(nèi)部的多個語句需使用分 號";"分隔
awk '{print $0}' a.txt
awk '{print $0}{print $0;print $0}' a.txt

BEGIN和END語句塊

awk 'BEGIN{print "我在前面"}{print $0}' a.txt
awk 'END{print "我在后面"}{print $0}' a.txt
awk 'BEGIN{print "我在前面"}{print $0}END{print "我在后面"}' a.txt

BEGIN代碼塊:

  • 在讀取文件之前執(zhí)行,且執(zhí)行一次
  • 在BEGIN代碼塊中,無法使用 $0 或其它一些特殊變量

END代碼塊:

  • 在讀取文件完成之后執(zhí)行,且執(zhí)行一次
  • 有END代碼塊,必有要讀取的數(shù)據(jù)(可以是標(biāo)準(zhǔn)輸入)
  • END代碼塊中可以使用$0等一些特殊變量,只不過這些特殊變量保存的是最后一輪awk循環(huán)的數(shù)據(jù)

main代碼塊:

  • 讀取文件時循環(huán)執(zhí)行,(默認(rèn)情況)每讀取一行,就執(zhí)行一次main代碼塊
  • main代碼塊可有多個

安裝新版本gawk

# 1.下載
wget --no-check-certificate https://mirrors.tuna.tsinghua.edu.cn/gnu/gawk/gawk-4.2.0.tar.gz
# 2.解壓、進入解壓后目錄
tar xf gawk-4.2.0.tar.gz
cd gawk-4.2.0/
# 3.編譯
./configure --prefix=/usr/local/gawk4.2 && make && make install
# 4.創(chuàng)建一個軟鏈接:讓awk指向剛新裝的gawk版本
ln -fs /usr/local/gawk4.2/bin/gawk /usr/bin/awk
# 此時,調(diào)用awk將調(diào)用新版本的gawk,調(diào)用gawk將調(diào)用舊版本的gawk awk --version
gawk --version

系統(tǒng)深入awk

awk命令行結(jié)構(gòu)和語法結(jié)構(gòu)

在Shell命令行當(dāng)中,雙短橫線 -- 表示選項到此結(jié)束,后面的都是命令的參數(shù)。

awk [ -- ] program-text file ...
awk -f program-file [ -- ] file ...
awk -e program-text [ -- ] file ...

cmd -x -r root -ppassword a.txt b.txt c.txt
# 1.選項分為長選項和短選項 
# 2.選項分為3種:
#       (1).不帶參數(shù)的選項
#       (2).是帶參數(shù)的選項,如果該選項后面沒有給參數(shù),則報錯
#       (3).參數(shù)可選的選項,選項后面可以跟參數(shù),也可以不跟參數(shù)
#          參數(shù)可選選項,如果要接參數(shù),則必須將參數(shù)緊緊跟在選項后面,不能使用空格分隔選項和參數(shù)
#3.兩種參數(shù):
#   (1).選項型參數(shù)
#   (2).非選項型參數(shù)

awk的語法充斥著 pattern{action} 的模式,它們稱為awk rule:

awk 'BEGIN{n=3} /^[0-9]/{$1>5}{$1=333;print $1} /Alice/{print "Alice"} END{print "hello"}' a.txt
  • pattern部分用于測試篩選數(shù)據(jù),action表示在測試通過后執(zhí)行的操作
    • 省略 pattern ,等價于對每一行數(shù)據(jù)都執(zhí)行action
      • awk '{print $0}' a.txt
    • 省略代碼塊{action} ,等價于 {print} 即輸出所有行
      • awk '/Alice/' a.txt 等價于awk '/Alice/{print $0}' a.txt
    • 省略代碼塊中的 ,表示對篩選的行什么都不做
      • awk '/Alice/{}' a.txt
    • pattern{action}任何一部分都可以省略
      + awk '' a.txt
    
  • 多個 pattern{action} 可以直接連接連用

pattern和action

對于 pattern{action} 語句結(jié)構(gòu)(都稱之為語句塊),其中的pattern部分可以使用下面列出的模式:

# 特殊pattern BEGIN
END
# 布爾代碼塊
/regular expression/ # 正則匹配成功與否 /a.*ef/{action}
relational expression # 即等值比較、大小比較 3>2{action}
pattern && pattern   # 邏輯與 3>2 && 3>1 {action}

pattern || pattern # 邏輯或 3>2 || 3<1 {action}
! pattern # 邏輯取反 !/a.*ef/{action} 
(pattern) # 改變優(yōu)先級
pattern ? pattern : pattern # 三目運算符決定的布爾值

# 范圍pattern,非布爾代碼塊
pattern1, pattern2 # 范圍,pat1打開、pat2關(guān)閉,即flip,flop模式

action部分,可以是任何語句,例如print語句。

awk讀取文件

詳細(xì)分析awk如何讀取文件

awk讀取輸入文件時,每次讀取一條記錄(record)(默認(rèn)情況下按行讀取,所以此時記錄就是行)。
每讀取一條記錄,將其保存到 $0中,然后執(zhí)行一次main代碼段。

awk '{print $0}' a.txt

如果是空文件,則因為無法讀取到任何一條記錄,將導(dǎo)致直接關(guān)閉文件,而不會進入main代碼段。
但是BEGIN END會進行執(zhí)行。

touch x.log # 創(chuàng)建一個空文件
awk '{print "hello world"}' x.log

可設(shè)置表示輸入記錄分隔符的預(yù)定義變量RS(Record Separator)來改變每次讀取的記錄模式。

# RS="\n" 、 RS="m"
awk 'BEGIN{RS="\n"}{print $0}' a.txt 
awk 'BEGIN{RS="m"}{print $0}' a.txt

RS通常設(shè)置在BEGIN代碼塊中,因為要先于讀取文件就確定好RS分隔符。

RS指定輸入記錄分隔符時,所讀取的記錄中是不包含分隔符字符的。
例如 RS="a" ,則 $0 中一定不可能出現(xiàn) 字符a。

RS兩種可能情況:

  • RS為單個字符:直接使用該字符來分割記錄
  • RS為多個字符:將其當(dāng)做正則表達式,只要匹配正則表達式的符號,都用來分割記錄
    • 設(shè)置預(yù)定義變量IGNORECASE為非零值,正則匹配時表示忽略大小寫
    • 兼容模式下,只有首字符才生效,不會使用正則模式去分割記錄
      特殊的RS值用來解決特殊讀取需求:
    • 按段落讀取:RS=''
    • RS='\0' 一次性讀取所有數(shù)據(jù),但有些特殊文件中包含了空字符 \0
    • RS="^$" 真正的一次性讀取所有數(shù)據(jù),因為非空文件不可能匹配成功
    • RS='\n+'按行讀取,但忽略所有空行
# 按段落讀取:RS=''
$ awk 'BEGIN{RS=''}{print $0"------"}' a.txt
# 一次性讀取所有數(shù)據(jù):RS='\0' RS="^$"
$ awk 'BEGIN{RS='\0'}{print $0"------"}' a.txt $ awk 'BEGIN{RS='^$'}{print $0"------"}' a.txt
# 忽略空行:RS='\n+'
$ awk 'BEGIN{RS='\n+'}{print $0"------"}' a.txt
# 忽略大小寫:預(yù)定義變量IGNORECASE設(shè)置為非0值
$ awk 'BEGIN{IGNORECASE=1}{print $0"------"}' RS='[ab]' a.txt
預(yù)定義變量RT:
在awk每次讀完一條記錄時,會設(shè)置一個稱為RT的預(yù)定義變量,表示Record Termination。
當(dāng)RS為單個字符時,RT的值和RS的值是相同的。
當(dāng)RS為多個字符(正則表達式)時,則RT設(shè)置為正則匹配到記錄分隔符之后,真正用于劃分記錄時的字符。 
當(dāng)無法匹配到記錄分隔符時,RT設(shè)置為控制空字符串(即默認(rèn)的初始值)。
awk 'BEGIN{RS="(fe)?male"}{print RT}' a.txt

兩種行號:NR和FNR

在讀取每條記錄之后,將其賦值給$0,同時還會設(shè)置NR、FNR、RT。

  • NR:所有文件的行號計數(shù)器
  • FNR:是各個文件的行號計數(shù)器
awk '{print NR}' a.txt a.txt
awk '{print FNR}' a.txt a.txt

詳細(xì)的分段字段分割

awk讀取每一條記錄之后,會將其賦值給 0 ,同時還會對這條記錄按照預(yù)定義變量FS劃分字段,將劃分好的各個\ 字段分別賦值給1 23 4...N ,同時將劃分的字段數(shù)量賦值給預(yù)定義變量NF。

引用字段的方式

$N 引用字段:

  • N=0 :即 $0 ,引用記錄本身
  • 0<N<=NF :引用對應(yīng)字段
  • N>NF :表示引用不存在的字段,返回空字符串 N<0 :報錯
    可使用變量或計算的方式指定要獲取的字段序號。
awk '{n = 5;print $n}' a.txt
awk '{print $(2+2)}' a.txt # 括號必不可少,用于改變優(yōu)先級 
awk '{print $(NF-3)}' a.txt

分割字段的方式

讀取record之后,將使用預(yù)定義變量FS、FIELDWIDTHS或FPAT中的一種來分割字段。分割完成之后,再進入
main代碼段(所以,在main中設(shè)置FS對本次已經(jīng)讀取的record是沒有影響的,但會影響下次讀取)。

FS或-F

FS 或者 -F :字段分隔符

  • FS為單個字符時,該字符即為字段分隔符
  • FS為多個字符時,則采用正則表達式模式作為字段分隔符
  • 特殊的,也是FS默認(rèn)的情況,F(xiàn)S為單個空格時,將以連續(xù)的空白(空格、制表符、換行符)作為字段分隔符
  • 特殊的,F(xiàn)S為空字符串""時,將對每個字符都進行分隔,即每個字符都作為一個字段
  • 設(shè)置預(yù)定義變量IGNORECASE為非零值,正則匹配時表示忽略大小寫(只影響正則,所以FS為單字時無影響)
  • 如果record中無法找到FS指定的分隔符(例如將FS設(shè)置為"\n"),則整個記錄作為一個字段,即 1 和0 相 等

FIELDWIDTHS

指定預(yù)定義變量FIELDWIDTHS按字符寬度分割字段,這是gawk提供的高級功能。在處理某字段缺失時非常好用。
用法:

  • FIELDWIDTHS="3 5 6 9" 表示第一個字段3字符,第二字段5字符...
  • FIELDWIDTHS = "8 1:5 6 2:33"表示:
    • 第一個字段讀8個字符
    • 然后跳過1個字符再讀5個字符作為第二個字段
    • 然后讀6個字符作為第三個字段
    • 然后跳過2個字符在讀33個字符作為第四個字段(如果不足33個字符,則讀到結(jié)尾)
  • FIELDWIDTHS="2 3 *" :
    • 第一個字段2個字符
    • 第二個字段3個字符
    • 第三個字段剩余所有字符 星號只能放在最后,且只能單獨使用,表示剩余所有
  • 設(shè)置該變量后,F(xiàn)S失效
  • 之后再設(shè)置FS或FPAT,
    示例1:
# 沒取完的字符串DDD被丟棄,且NF=3
$ awk 'BEGIN{FIELDWIDTHS="2 3 2"}{print $1,$2,$3,$4}' <<<"AABBBCCDDDD" AA BBB CC
# 字符串不夠長度時無視
$ awk 'BEGIN{FIELDWIDTHS="2 3 2 100"}{print $1,$2,$3,$4"-"}' <<<"AABBBCCDDDD" AA BBB CC DDDD-
# *號取剩余所有,NF=3
$ awk 'BEGIN{FIELDWIDTHS="2 3 *"}{print $1,$2,$3}' <<<"AABBBCCDDDD" AA BBB CCDDDD
# 字段數(shù)多了,則取完字符串即可,NF=2
$ awk 'BEGIN{FIELDWIDTHS="2 30 *"}{print $1,$2,NF}' <<<"AABBBCCDDDD" AA BBBCCDDDD 2

示例2:處理某些字段缺失的數(shù)據(jù)。
如果按照常規(guī)的FS進行字段分割,則對于缺失字段的行和沒有缺失字段的行很難統(tǒng)一處理,但使用FIELDWIDTHS則非常方便。
假設(shè)a.txt文本內(nèi)容如下:

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   aaa@163.com    17048792503
4   Kevin   male    21                  17023929033
5   Alex    male    18   ccc@xyz.com    18185904230
6   Andy    female  22   ddd@139.com    18923902352
7   Jerry   female  25   exdsa@189.com  18785234906
8   Peter   male    20   bax@qq.com     17729348758
9   Steven  female  23   bc@sohu.com    15947893212
10  Bruce   female  27   bcbd@139.com   13942943905

因為email字段有的是空字段,所以直接用FS劃分字段不便處理。可使用FIELDWIDTHS。

# 字段1:4字符
# 字段2:8字符
# 字段3:8字符
# 字段4:2字符
# 字段5:先跳過3字符,再讀13字符,該字段13字符
# 字段6:先跳過2字符,再讀11字符,該字段11字符
awk '
BEGIN{FIELDWIDTHS="4 8 8 2 3:13 2:11"}
NR>1{
    print "<"$1">","<"$2">","<"$3">","<"$4">","<"$5">","<"$6">"
}' a.txt
# 如果email為空,則輸出它
awk '
BEGIN{FIELDWIDTHS="4 8 8 2 3:13 2:11"} NR>1{
    if($5 ~ /^ +$/){print $0}
}' a.txt

FPAT

FS是指定字段分隔符,來取得除分隔符外的部分作為字段。
FPAT是取得匹配的字符部分作為字段。它是gawk提供的一個高級功能。
FPAT根據(jù)指定的正則來全局匹配record,然后將所有匹配成功的部分組成 1、2... ,不會修改 $0 。

  • awk 'BEGIN{FPAT="[0-9]+"}{print $3"-"}' a.txt
  • 之后再設(shè)置FS或FPAT,該變量將失效
    FPAT常用于字段中包含了字段分隔符的場景。例如,CSV文件中的一行數(shù)據(jù)如下:
Robbins,Arnold,"1234 A Pretty Street, NE",MyTown,MyState,12345-6789,USA

其中逗號分隔每個字段,但雙引號包圍的是一個字段整體,即使其中有逗號。
這時使用FPAT來劃分各字段比使用FS要方便的多。

echo 'Robbins,Arnold,"1234 A Pretty Street, NE",MyTown,MyState,12345-6789,USA' |\
awk '
    BEGIN{FPAT="[^,]*|(\"[^\"]*\")"}
    {
        for (i=1;i<NF;i++){
            print "<"$i">"
        } 
    }
'

最后,patsplit()函數(shù)和FPAT的功能一樣。

檢查字段分隔的方式

有FS、FIELDWIDTHS、FPAT三種獲取字段的方式,可使用 PROCINFO 數(shù)組來確定本次使用何種方式獲得字段。
PROCINFO是一個數(shù)組,記錄了awk進程工作時的狀態(tài)信息。

  • PROCINFO["FS"]=="FS",表示使用FS分割獲取字段
  • PROCINFO["FPAT"]=="FPAT" ,表示使用FPAT匹配獲取字段
  • PROCINFO["FIELDWIDTHS"]=="FIELDWIDTHS",表示使用FIELDWIDTHS分割獲取字段
    例如:
if(PROCINFO["FS"]=="FS"){
    ...FS spliting...
} else if(PROCINFO["FPAT"]=="FPAT"){
    ...FPAT spliting...
} else if(PROCINFO["FIELDWIDTHS"]=="FIELDWIDTHS"){
    ...FIELDWIDTHS spliting...
}

修改字段或NF值的聯(lián)動效應(yīng)

注意下面的分割和計算兩詞:分割表示使用FS(field Separator),計算表示使用預(yù)定義變量OFS(Output Field Separator)。

  1. 修改 0 ,將使用 FS 重新分割字段,所以會影響1、$2...
  2. 修改 1、2 ,將根據(jù) 1 到NF 來重新計算 $0
    • 即使是 1 =1 這樣的原值不變的修改,也一樣會重新計算 $0
  3. 為不存在的字段賦值,將新增字段并按需使用空字符串填充中間的字段,并使用 OFS 重新計算 $0
    • awk '{(NF+2)=5;print0}' OFS='-' a.txt
  4. 增加NF值,將使用空字符串新增字段,并使用 OFS 重新計算 $0
    • awk '{NF+=3;print $0}' OFS='-' a.txt
  5. 減小NF值,將丟棄一定數(shù)量的尾部字段,并使用 OFS 重新計算 $0
    • awk '{NF-=3;print $0}' OFS='-' a.txt

關(guān)于$0

當(dāng)讀取一條record之后,將原原本本地被保存到 $0 當(dāng)中。

awk '{print $0}' a.txt

但是,只要出現(xiàn)了上面所說的任何一種導(dǎo)致 0 重新計算的操作,都會立即使用OFS去重建0 。
換句話說,沒有導(dǎo)致 0 重建,0就一直是原原本本的數(shù)據(jù),所以指定OFS也無效。

awk '{print $0}' OFS="-" a.txt # OFS此處無效

當(dāng) $0 重建后,將自動使用OFS重建,所以即使沒有指定OFS,它也會采用默認(rèn)值(空格)進行重建。

awk '{$1=$1;print $0}' a.txt # 輸出時將以空格分隔各字段
awk '{print $0;$1=$1;print $0}' OFS="-" a.txt

如果重建 $0 之后,再去修改OFS,將對當(dāng)前行無效,但對之后的行有效。所以如果也要對當(dāng)前行生效,需要再次重 建。

# OFS對第一行無效
awk '{$4+=10;OFS="-";print $0}' a.txt
# 對所有行有效
awk '{$4+=10;OFS="-";$1=$1;print $0}' a.txt

關(guān)注 0 重建是一個非常有用的技巧。\ 例如,下面通過重建0 的技巧來實現(xiàn)去除行首行尾空格并壓縮中間空格

$ echo " a b c d " | awk '{$1=$1;print}'
a b c d
$ echo " a b c d " | awk '{$1=$1;print}' OFS="-" 
a-b-c-d

awk數(shù)據(jù)篩選示例

篩選行

# 1.根據(jù)行號篩選
awk 'NR==2' a.txt # 篩選出第二行
awk 'NR>=2' a.txt # 輸出第2行和之后的行

# 2.根據(jù)正則表達式篩選整行
awk '/qq.com/' a.txt # 輸出帶有qq.com的行 
awk '$0 ~ /qq.com/' a.txt # 等價于上面命令
awk '/^[^@]+$/' a.txt # 輸出不包含@符號的行 
awk '!/@/' a.txt # 輸出不包含@符號的行

# 3.根據(jù)字段來篩選行
awk '($4+0) > 24{print $0}' a.txt # 輸出第4字段大于24的行 
awk '$5 ~ /qq.com/' a.txt # 輸出第5字段包含qq.com的行

# 4.將多個篩選條件結(jié)合起來進行篩選
awk 'NR>=2 && NR<=7' a.txt
awk '$3=="male" && $6 ~ /^170/' a.txt 
awk '$3=="male" || $6 ~ /^170/' a.txt

# 5.按照范圍進行篩選 flip flop
# pattern1,pattern2{action}
awk 'NR==2,NR==7' a.txt # 輸出第2到第7行 
awk 'NR==2,$6 ~ /^170/' a.txt

處理字段

修改字段時,一定要注意,可能帶來的聯(lián)動效應(yīng):即使用OFS重建$0。

awk 'NR>1{$4=$4+5;print $0}' a.txt
awk 'NR>1{$6=$6"*";print $0}' a.txt

awk運維面試試題

從ifconfig命令的結(jié)果中篩選出除了lo網(wǎng)卡外的所有IPv4地址。

# 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}'

awk工作流程

參考自: man awk 的"AWK PROGRAM EXECUTION"段。

man --pager='less -p ^"AWK PROGRAM EXECUTION" awk

執(zhí)行步驟

  1. 解析 -v var=val... 選項中的變量賦值
  2. 編譯awk源代碼為awk可解釋的內(nèi)部格式,包括-v的變量
  3. 執(zhí)行BEGIN代碼段
  4. 根據(jù)輸入記錄分隔符RS讀取文件(根據(jù)ARGV數(shù)組的元素決定要讀取的文件),如果沒有指定文件,則從標(biāo)準(zhǔn) 輸入中讀取文件,同時執(zhí)行main代碼段
    • 如果文件名部分指定為 var=val 格式,則聲明并創(chuàng)建變量,此階段的變量在BEGIN之后聲明,所以 BEGIN中不可用,main代碼段可用
    • 每讀取一條記錄:
      • 都將設(shè)置NR、FNR、RT、$0等變量
      • (默認(rèn))根據(jù)輸入字段分隔符FS切割字段,將各字段保存到 1、2... 中
      • 測試main代碼段的pattern部分,如果測試成功則執(zhí)行action部分
  5. 執(zhí)行END代碼段
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容