AWK模式處理語言

AWK模式處理語言

簡介

AWK——一個強大的文本分析工具,是一種模式掃描和處理語言,它搜索一個或者多個文件,以查看這些文件中是否存在匹配指定模式的記錄(通常是文本行)。每次發現匹配記錄時,它通過執行動作的方式(比如將該記錄寫到標準輸出或者將某個計數器遞增)來處理文本行。與過程語言相反,AWK屬於數據驅動語言:用戶描述想要處理的數據並告訴AWK當它發現這些數據時如何處理他們。

使用AWK可以生成報告或者過濾文本。它在處理時不區分數字和文本,如果將兩者混在一起,AWK通常可以得出正確的答案。

awk有3個不同版本: awk、nawk和gawk,一般指gawk,gawk 是 AWK 的 GNU 版本。

AWK的作者:Alfred V. Aho、Peter J. Weinberger、Brian W.Kernighan

語法

gawk [options] [program] [file-list]

gawk [options] -f program-file [file-list]

參數

program是用戶在命令行中包含的gawk程序。

program-file是存放gawk程序的文件的名稱。在命令行上使用gawk,就可以編寫出簡短的gawk程序,而不用創建單獨的program-file文件。爲了防止shell將gawk命名解釋成shell命令,要將program用單引號引起來,將較長或者較複雜的程序放在文件中可以減少錯誤和重複輸入。

file-list包含gawk要處理的普通文件的路徑名。這些文件就是輸入文件。如果用戶沒有指定file-list,gawk就從標準輸入或者由getline或協進程指定輸入。

選項

選項 選項 含義
–field-seperator fs -F fs 將fs作爲輸入字段分隔符(FS變量)的值。
–file program-file -f program-file 從program-file文件中而不是命令行中讀取gawk程序。用戶可以在命令行上多次指定這個選項。
–help -W help 總結如何使用gawk(僅用於gawk)。
–lint -W lint 對不正確或者能移植的結構發出警告(僅用於gawk)。
–posix -W posix 運行POSIX兼容版gawk。這個選項引入了一些限制。
–traditional -W traditional 忽略gawk程序中較新的GNU特性,使得程序與UNIX awk兼容(僅用於gawk)。
–assign var=value -v var=value 把value賦予變量var。在gawk程序執行之前進行賦值,它可以用於BEGIN模式中。可以在命令行上多次指定這個選項。

語言基礎

gawk程序由一行或者多行文本構成,其中包含一個模式和/或者動作,格式如下:

pattern { action }

模式(pattern)用來從輸入中選取文本行。對於由模式選中的每行文本,gawk實用程序都執行動作(action)。動作兩邊的花括號使gawk將動作與模式區分開來。如果程序行沒有包含模式,gawk就選擇輸入中的所有行。如果程序行沒有包含動作,gawk就把選中的行復制到標準輸出中。

模式

!~ 用斜槓把正則表達式括起來,就可以將其看做模式。~運算符用於測試某個字段或者變量是否匹配正則表達式。!~運算符用於測試不匹配。可以使用關係運算符進行數值比較和字符串比較。可以使用布爾運算符||(OR)或者&&(AND)來組合任何模式。

BEGINEND BEGIN和END是兩種獨特的模式,分別執行在gawk開始處理輸入信息之前和處理完畢輸入信息之後的命令。在處理所有輸入信息之前,gawk實用程序執行BEGIN模式關聯的動作,在處理完之後執行END模式關聯的動作。

,(逗號) 逗號是範圍運算符。如果在一個gawk程序行上用逗號將兩種模式隔開,gawk就選取從匹配第1種模式的第1行開始的一系列文本行。gawk選取的最後一行是隨後匹配第2種模式的下一行文本。如果沒有匹配第2種模式的文本行,gawk就選取直到輸入末尾的所有文本行。在gawk找到第2中模式之後,它將再次查找第1中模式以再次開始這個過程。

動作

如果gawk匹配某種模式,它就執行gawk命令的動作部分所指定的動作。如果沒有指定動作,gawk就執行默認動作,即print命令(可用{print}顯式表示)。這個動作將記錄從輸入複製到標準輸出。

註釋

使用#開頭可以不處理程序行後面的內容。

變量

儘管不需要在使用gawk變量之前聲明它們,但用戶可以選擇把初始值賦予這些變量。
沒有賦值的數值變量被初始化爲0,而字符串變量則被初始化爲空字符串。
除了支持用戶變量(user varialbe)之外,gawk維護程序變量(program variable)。在gawk程序的模式部分和動作部分中均可以使用用戶變量和程序變量。

如下是一些程序變量

變量 含義
$0 當前記錄(作爲單個變量)
1  n 當前記錄中的字段
FILENAME 當前輸入文件的名稱(null表示標準輸入)
FS 輸入字段分隔符(默認爲空格或製表符)
NF 當前記錄的字段數目
NR 當前記錄的記錄編號
OFS 輸出字段分隔符(默認爲空格)
ORS 輸出記錄分隔符(默認爲換行)
RS 輸入記錄分隔符(默認爲換行)



除了在程序中初始化變量之外,還可以在命令行上使用–assign(-v)選項初始化變量。如果某個變量的值在gawk的兩次運行之間發生改變,這個功能非常有用。

記錄分隔符:默認情況下,輸入記錄和輸出記錄的分隔符均爲換行符。因此gawk將每行輸入作爲單獨的一個記錄,並在每條輸出記錄後面追加一個換行符。默認情況下,輸入字段分隔符爲空和製表符。默認的輸出字段分隔符是空格。在任意時刻都可以更改分隔符的值,方法是在程序中或者命令行中使用–assign(-v)選項,將一個新的值賦予與這些分隔符相關聯的變量。

函數

函數 含義
length(str) 返回str中的字符個數,如果沒有帶參數,則返回當前記錄中的字符個數
int(num) 返回num的整數部分
index(str1, str2) 返回str2在str1中的索引,如果str2不存在就返回0
split(str, arr, del) 用del作爲分隔符,將str元素放到數組arr[1]…arr[n]中,返回數組中的元素個數
sprintf(fmt, args) 根據fmt格式化args並返回格式化後的字符串;模仿C語言中的同名函數
substr(str, pos, len) 返回str中從pos開始、長度爲len個字符的字符串
tolower(str) 返回str的副本,但是其中的所有大寫字母被替換成相應的小寫字母
toupper(str) 返回str的副本,但是其中的所有小寫字母被替換成相應的大寫字母

算數運算符

與C語言相同

關聯數組

關聯數組是gawk最強大的功能之一。這些數組使用字符串作爲索引。在使用關聯數組時,用戶可以用數值字符串作爲索引來模仿傳統數組。

可以給關聯數組中的某個元素賦值。其語法如下:
array[string] = value
其中,array爲數組的名稱,string爲用戶將要賦值的元素在數組中的索引,value爲將要賦予該元素的值。

可以將for結構用於關聯數組。其語法如下:
for (elem in array) action
其中,for結構遍歷數組中的元素時,elem表示接收數組中每個元素的值的變量,array爲數組的名稱,action爲gawk對數組中每個元素所採取的行動。可以在action中使用elem變量。

printf

可以使用printf命令來代替print控制gawk產生的輸出的格式。gawk版的printf類似於C語言中的printf。printf命令的語法如下:

printf “control-string”, arg1, arg2, arg3, …, argn

control-string決定printf如何格式化arg1,arg2, …, argn。這些參數既可以是變量也可以是其他表達式。可以在control-string中使用\n來表示換行符,使用\t來表示製表符。control-string包含轉換說明,每個參數對應一個表達式。轉換格式如下:

%[-][x[.y]] conv

其中,“-”使printf將參數左對齊,x表示最小字段寬度,“.y”表示數字中小數點右邊的位數。conc指示數值轉換的類型。

conv 轉換類型
d 十進制
e 指數表示
f 浮點數字
g 使用f或者e中較短的那個
o 無符號八進制
s 字符串
x 無符號十六進制

控制結構

控制(流)語句將改變gawk程序中命令的執行順序。

  1. if…else
    語法結構如下:
if (condition)
        { commands }
    [else
        { commands }]
  1. while
    語法結構如下:
while (condition)
    { commands }
  1. for
    語法結構如下:
for (init; condition; increment)
    { commands }
  1. break
    break語句將控制權轉移到for或者while循環之外,終止它所在的最內層循環執行。

  2. continue
    continue語句將控制權轉移到for或者while循環的末尾,使它所在的最內層循環繼續執行下一次迭代。

示例

car數據文件

cars文件
plym    fury            1970    73      2500
chevy   malibu          1999    60      3000
ford    mustang         1965    45      10000
volvo   s80             1998    102     9850
ford    thundbdd        2003    15      10500
chevy   malibu          2000    50      2500
bmw     325i            1985    115     450
honda   accord          2001    30      6000
ford    taurus          2004    10      17000
toyota  rav4            2002    180     750
chevy   impata          1985    85      1550
 ford   explor          2003    25      9500

缺失模式

一個簡單的gawk程序如下:

{ print }

這個程序由單行程序組成,這行程序爲一個動作。因爲沒有模式,所以gawk選擇輸入中的所有行。

$ gawk '{ print }' cars

缺失動作

$ gawk '/chevy/' cars
chevy   malibu      1999    60  3000
chevy   malibu      2000    50  2500
chevy   impata      1985    85  1550

字段

下面是沒有模式的文件所有行。用花括號將動作括起來。必須使用花括號限定動作,這樣gawk纔可以將動作與模式部分區分開。示例顯示每一行的第3個字段(3)1( 1)。

$ gawk '{print $3, $1}' cars
1970 plym
1999 chevy
1965 ford
......

下面示例包含模式和動作,它選中包含字符串chevy的所有行並顯示選中行的第3個字段和第1個字段:

$ gawk '/chevy/ {print $3,$1}' cars 
1999 chevy
2000 chevy
1985 chevy

下面示例中,gawk選中包含與正則表達式h匹配的行。因爲沒有顯式指定動作,所以gawk顯示它選中的所有行。

$ gawk '/h/' cars
chevy   malibu      1999    60  3000
ford    thundbdd    2003    15  10500
chevy   malibu      2000    50  2500
honda   accord      2001    30  6000
chevy   impata      1985    85  1550

~(匹配運算符)

下面示例中,使用匹配運算符(~)來選擇在第1個字段中包含字符h的所有行:

$ gawk '$1 ~ /h/' cars
chevy   malibu      1999    60  3000
chevy   malibu      2000    50  2500
honda   accord      2001    30  6000
chevy   impata      1985    85  1550

正則表達式中的脫字符(^)強制在行首進行匹配,在這個示例中,從第1個字段的起始出匹配:

$ gawk '$1 ~ /^h/' cars
honda   accord      2001    30  6000

字符兩邊使用方括號。表示方括號內的任意一個都匹配。

$ gawk '$2 ~ /^[tm]/ {print $3,$2,"$" $5}' cars
1999 malibu $3000
1965 mustang $10000
2003 thundbdd $10500
2000 malibu $2500
2004 taurus $17000

美元符號

美元符號在gawk程序中所起的3中作用。1)、美元符號後面緊跟一個數字來表示某個字段。2)、在正則表達式中,美元符號強制在行尾或者字段末尾($5)進行匹配。3)、在字符串中美元符號代表自身。

$ gawk '$3 ~ /5$/ {print $3, $1 " $" $5}' cars
1965 ford $10000
1985 bmw $450
1985 chevy $1550

gawk使用等於關係運算符(==)對每行的第3個字段與數字1985進行數值比較。

$ gawk '$3==1985' cars
bmw     325i        1985    115 450
chevy   impata      1985    85  1550

文本比較

使用文本比較要加引號

$ gawk '"2000"<=$5 && $5<"9000"' cars
plym    fury        1970    73  2500
chevy   malibu      1999    60  3000
chevy   malibu      2000    50  2500
bmw     325i        1985    115 450
honda   accord      2001    30  6000
toyota  rav4        2002    180 750

,(範圍運算符)

範圍運算符(,)用來選取一組文本行。選中的第1行是逗號之前的模式所指定的行,選中的最後一行是逗號之後的模式所指定的行。如果沒有匹配逗號之後模式的文本行,gawk就選取直到輸入末尾額所有行。下面是從包含volvo的行開始到包含bmw的行結束的所有文本行。

$ gawk '/volvo/ , /bmw/' cars
volvo   s80         1998    102 9850
ford    thundbdd    2003    15  10500
chevy   malibu      2000    50  2500
bmw     325i        1985    115 450

–file選項

如果用戶正在編寫較長的gawk程序,那麼可以將程序放在一個文件中,然後在命令行上引用該文件。使用-f(–file)選項,後面緊跟着包含該gawk程序的文件的名稱。

BEGIN

$ cat pr_header
BEGIN {print "Make  Model       Year    Miles   Price"}
    {print}

$ gawk -f pr_header cars
Make    Model       Year    Miles   Price
plym    fury        1970    73  2500
chevy   malibu      1999    60  3000
ford    mustang     1965    45  10000
......

END

END模式的工作方式與BEGIN模式類似,但是gawk在處理完輸入的最後一行之後才執行與該模式關聯的動作。

$ gawk 'END {print NR, "cars for sale. "}' cars
12 cars for sale.

length()函數

如果用戶不帶參數調用length()函數,那麼它將返回當前文本行中的字符個數,包含字段分隔符。$0變量總是包含當前文本行的內容。在下一個示例中,gawk將行長度添加到每行的開頭,然後通過管道將輸出從gawk發送到sort(-n 指定數值排序)。

$ gawk '{print length, $0}' cars | sort -n
22 bmw      325i        1985    115 450
23 plym     fury        1970    73  2500
24 volvo    s80         1998    102 9850
25 ford     explor      2003    25  9500
25 toyota   rav4        2002    180 750
26 chevy    impata      1985    85  1550
26 chevy    malibu      1999    60  3000
26 chevy    malibu      2000    50  2500
26 ford     taurus      2004    10  17000
26 honda    accord      2001    30  6000
27 ford     mustang     1965    45  10000
27 ford     thundbdd    2003    15  10500

NR(記錄編號)

NR變量包含當前行的記錄編號(行號)。下面的模式選取字符數多餘24的所有行。動作則顯示選中的每一行的行號。

$ gawk 'length > 24 {print NR}' cars 
2
3
5
6
8
9
10
11
12

範圍(,)和NR變量可以一起使用,根據行號來顯示一組文本行。下面這個示例顯示第2~4行之間的行。

$ gawk 'NR==2,NR==4' cars
chevy   malibu      1999    60  3000
ford    mustang     1965    45  10000
volvo   s80         1998    102 9850

OFS變量

可以通過某個值賦予OFS變量來更改輸出字段分隔符的值。
下面示例使用反斜槓轉義序列\t將製表符賦予OFS。這樣就改善了報告的輸出格式,但並沒有正確的對齊。

$ gawk -f ofs_demo cars
plymouth    fury    1970    73  2500
chevrolet   malibu  1999    60  3000
ford    mustang     1965    45  10000
volvo   s80     1998    102 9850
ford    thundbdd    2003    15  10500
chevrolet   malibu  2000    50  2500
bmw 325i        1985    115 450
honda   accord      2001    30  6000
ford    taurus      2004    10  17000
toyota  rav4        2002    180 750
chevrolet   impata  1985    85  1550
ford    explor      2003    25  9500

printf

可以使用printf進一步改善輸出格式。下面的示例在幾個程序行的末尾使用反斜槓轉義隨後的換行符。可以使用這種方法來續寫較長的一行或者多行,而不會影響到程序的輸出結果。

$ cat printf_demo 
BEGIN{
    print "             Miles"
    print "Make Mode    Year    (000)   price"
    print \
    "------------------------------------------------"
    }
    {
    if($1 ~ /ply/) $1 = "plymouth"
    if($1 ~ /chev/) $1 = "chevrolet"
    printf "%-10s %-8s %-2d %5d $ $ %8.2f\n",\
        $1,$2,$3,$4,$5
    }

$ gawk -f printf_demo cars 
                Miles
Make    Mode    Year    (000)   price
------------------------------------------------
plymouth   fury     1970    73 $ $  2500.00
chevrolet  malibu   1999    60 $ $  3000.00
ford       mustang  1965    45 $ $ 10000.00
volvo      s80      1998   102 $ $  9850.00
ford       thundbdd 2003    15 $ $ 10500.00
chevrolet  malibu   2000    50 $ $  2500.00
bmw        325i     1985   115 $ $   450.00
honda      accord   2001    30 $ $  6000.00
ford       taurus   2004    10 $ $ 17000.00
toyota     rav4     2002   180 $ $   750.00
chevrolet  impata   1985    85 $ $  1550.00
ford       explor   2003    25 $ $  9500.00

重定向輸出

下面定義了兩個文件:一個文件存放包含chevy的所有行;另一個文件存放包含ford的所有行。

$ cat redirect_out
/chevy/ {print > "chevfile"}
/ford/ {print > "fordfile"}
END {print "demo."}

$ gawk -f redirect_out cars
demo.

$ cat chevfile
chevy   malibu      1999    60  3000
chevy   malibu      2000    50  2500
chevy   impata      1985    85  1550

$ cat fordfile
ford    mustang     1965    45  10000
ford    thundbdd    2003    15  10500
ford    taurus      2004    10  17000
ford    explor      2003    25  9500

summary程序生成關於所有車和較新車的一個總結報告。

$ cat summary
BEGIN {
       yearsum = 0; costsum = 0
       newcostsum = 0; newcount = 0
      }
      {
       yearsum += $3
       costsum += $5
      }
$3 > 2000 { newcostsum += $5; newcount ++ }

END  {
      printf "Average age of cars is %4.1f years\n",\
          2006 - (yearsum/NR)
     printf "Average cost of cars is $%7.2f\n",\
         costsum/NR
         printf "Average cost of newer is $%7.2f\n",\
             newcostsum/newcount
     }

$ gawk -f summary cars
Average age of cars is 13.1 years
Average cost of cars is $6133.33
Average cost of newer is $8750.00

FS變量

下面這個示例揭示了在某個字段中查找最大數字的技巧。因爲它處理linux的password文件,這個文件使用冒號(:)限定各個字段,所以這個示例在讀取任何數據之前更改輸入字段分隔符(FS)。它讀取passwd文件並判斷下一個可用的用戶ID編號(第3個字段)。這個程序的運行,並不要求在passwd文件中的這些數字有序。

模式($3》> saveit)使gawk選取包含用戶ID編號大於任何它前面曾處理過的用戶ID編號的記錄。每次選中一行,gawk將這個新用戶ID編號賦予saveit變量。然後gawk使用saveit這個新值來測試所有後續記錄的用戶ID。最後gawk將saveit的值加1,並顯示該結果:

$ cat find_uid 
BEGIN {FS = ":"
        saveit = 0
      }
$3 > saveit {saveit = $3}
END {print "Next available UID is " saveit + 1}

$ gawk -f find_uid /etc/passwd
Next available UID is 65535

if-else

$ cat price_range 
{
if($5 <= 5000)   $5 = "indexpensive"
    else if(5000 < $5 && $5 < 10000)    $5 = "please ask"
    else if(10000 <= $5)    $5 = "expensive"
#
printf "%-10s %-8s    %2d    %5d    %-12s\n",\
$1, $2, $3, $4, $5
}

$ gawk -f price_range cars
plym       fury        1970       73    indexpensive
chevy      malibu      1999       60    indexpensive
ford       mustang     1965       45    expensive
volvo      s80         1998      102    please ask
ford       thundbdd    2003       15    expensive
chevy      malibu      2000       50    indexpensive
bmw        325i        1985      115    indexpensive
honda      accord      2001       30    please ask
ford       taurus      2004       10    expensive
toyota     rav4        2002      180    indexpensive
chevy      impata      1985       85    indexpensive
ford       explor      2003       25    please ask

關聯數組

下面這個manuf關聯數組使用cars文件中的每條記錄的第1個字段的內容作爲索引。這個數組由元素manuf[plym]、manuf[chevy]、manuf[ford]等組成。每個元素在創建時都被初始化爲0(零)。運算符++將遞增其後的變量。

for結構

END模式之後的動作是爲for結構準備的,該for結構循環遍歷關聯的數組的每個元素。通過管道將輸出送到sort,以生成按字母順序排列的文件和庫存量的有序列表。

$ cat price_range
{
if($5 <= 5000)   $5 = "indexpensive"
    else if(5000 < $5 && $5 < 10000)    $5 = "please ask"
    else if(10000 <= $5)    $5 = "expensive"
#
printf "%-10s %-8s    %2d    %5d    %-12s\n",\
$1, $2, $3, $4, $5
}

$ ./manuf
bmw 1
chevy 3
ford 4
honda 1
plym 1
toyota 1
volvo 1

下面名爲manuf.sh的程序是一個更加通用的shell腳本,它包含一些錯誤檢查功能。這個腳本列出文件中某一列的內容並對其進行計數,使用在命令行上指定的列號和文件名。

本篇博客的資料來源於《Linux命令、編輯器與Shell編程》

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章