首頁 > linux教程 > Linux文本處理(Linux三劍客) 閱讀:0更新時間:2020-03-28 00:57:19

Linux awk命令的高級玩法

前面一節已經介紹了 awk 的基本用法,其實在 awk 腳本程序中,還支持使用一些編程語言,比如變量、數組、分支結構(if-then-else)、循環結構(while)、函數等,下面一一給大家介紹。

awk 使用變量

在 awk 的腳本程序中,支持使用變量來存取值。awk 支持兩種不同類型的變量:

  • 內建變量:awk 本身就創建好,用戶可以直接拿來用的變量,這些變量用來存放處理數據文件中的某些字段和記錄的信息。

  • 自定義變量:awk 支持用戶自己創建變量。

內建變量

awk 程序使用內建變量來引用程序數據里的一些特殊功能。常見的一些內建變量,包括上一節介紹的數據字段變量($0、$1、$2...$n)以及表 1 、表 2 中所示的這些變量。

表 1 字段和記錄分隔符變量
變量功能
FIELDWIDTHS由空格分隔的一列數字,定義了每個數據字段的確切寬度。
FNR當前輸入文檔的記錄編號,常在有多個輸入文檔時使用。
NR輸入流的當前記錄編號。
FS輸入字段分隔符
RS輸入記錄分隔符,默認為換行符 \n。
OFS輸出字段分隔符,默認為空格。
ORS輸出記錄分隔符,默認為換行符 \n。


在表 1 中,變量 FS 和 OFS 定義了 awk 如何處理數據流中的數據字段。我們已經知道了如何使用變量 FS 來定義記錄中的字段分隔符,變量 OFS 具備相同的功能,只不過是用在 print 命令的輸出上,例如:

[root@localhost ~]# cat data1
data11,data12,data13,data14,data15
data21,data22,data23,data24,data25
data31,data32,data33,data34,data35
[root@localhost ~]# awk 'BEGIN{FS=","; OFS="-"} {print $1,$2,$3}' data1
data11-data12-data13
data21-data22-data23
data31-data32-data33
[root@localhost ~]# awk 'BEGIN{FS=","; OFS="--"} {print $1,$2,$3}' data1
data11--data12--data13
data21--data22--data23
data31--data32--data33

可以看到,print 命令會自動將 OFS 變量的值放置在輸出中的每個字段間。通過設置 OFS 變量,可以在輸出中使用任意字符串來分隔字段。

FIELDWIDTHS 變量允許用戶不依靠字段分隔符來讀取記錄。在一些應用程序中,數據并沒有使用字段分隔符,而是被放置在了記錄中的特定列,這種情況下,必須設定 FIELDWIDTHS 變量來匹配數據在記錄中的位置。

一旦設置了 FIELDWIDTH 變量,awk 就會忽略 FS 變量,并根據提供的字段寬度來計算字段,下面是個采用字段寬度而非字段分隔符的例子:

[root@localhost ~]# cat data1b
1005.3247596.37
115-2.349194.00
05810.1298100.1
[root@localhost ~]# awk 'BEGIN{FIELDWIDTHS="3 5 2 5"}{print $1,$2,$3,$4}' data1b
100 5.324 75 96.37
115 -2.34 91 94.00
058 10.12 98 100.1

注意,一旦設定了 FIELDWIDTHS 變量的值,就不能再改變了,因此,這種方法并不適用于變長的字段。

變量 RS 和 ORS 定義了 awk 程序如何處理數據流中的字段,默認情況下,awk 將 RS 和 ORS 設為換行符。默認的 RS 值表明,輸入數據流中的每行新文本就是一條新紀錄。 有時,你會在數據流中碰到占據多行的字段。典型的例子是包含地址和電話號碼的數據,其中地址和電話號碼各占一行,例如:

Riley Mullen
123 Main Street
Chicago, IL 60601
(312)555-1234

如果你用默認的 FS 和 RS 變量值來讀取這組數據,awk 就會把每行作為一條單獨的記錄來讀取,并將記錄中的空格當作字段分隔符,這并不是用戶想要的。

要解決這個問題,只需把 FS 變量設置成換行符,這就表明數據流中的每行都是一個單獨的字段,每行上的所有數據都屬于同一個字段;與此同時,把 RS 變量設置成空字符串,然后在數據記錄間留一個空白行,awk 會把每個空白行當作一個記錄分隔符。例如:

[root@localhost ~]# cat data2
Riley Mullen
123 Main Street
Chicago, IL  60601
(312)555-1234

Frank Williams
456 Oak Street
Indianapolis, IN  46201
(317)555-9876

Haley Snell
4231 Elm Street
Detroit, MI 48201
(313)555-4938
[root@localhost ~]# awk 'BEGIN{FS="\n"; RS=""} {print $1,$4}' data2
Riley Mullen (312)555-1234
Frank Williams (317)555-9876
Haley Snell (313)555-4938


表 2 環境信息變量
變量名功能
ARGC命令行參數個數。
ARGIND當前文件在 ARGC 中的位置。
ARGV包含命令行參數的數組。
CONVFMT數字的轉換格式,默認值為 %.6g。
ENVIRON當前 shell 環境變量及其值組成的關聯數組。
ERRNO當讀取或關閉輸入文件發生錯誤時的系統錯誤號。
FILENAME當前輸入文檔的名稱。
FNR當前數據文件中的數據行數。
IGNORECASE設成非 0 值時,忽略 awk 命令中出現的字符串的字符大小寫。
NF數據文件中的字段總數。
NR已處理的輸入記錄數。
OFMT數字的輸出格式,默認值為 %.6g。
RLENGTH由 match 函數所匹配的子字符串的長度。
TSTART由 match 函數所匹配的子字符串的起始位置。


其中,FNR 和 NR 變量雖然類似,但又略有不同。FNR 變量含有當前數據文件中已處理過的記錄數,NR 變量則含有已處理過的記錄總數。舉個例子:

[root@localhost ~]# cat data1
data11,data12,data13,data14,data15
data21,data22,data23,data24,data25
data31,data32,data33,data34,data35
[root@localhost ~]# awk '
> BEGIN {FS=","}
> {print $1,"FNR="FNR,"NR="NR}
> END{print "There were",NR,"records processed"}' data1 data1
data11 FNR=1 NR=1
data21 FNR=2 NR=2
data31 FNR=3 NR=3
data11 FNR=1 NR=4
data21 FNR=2 NR=5
data31 FNR=3 NR=6
There were 6 records processed

由此可以看出,當只使用一個數據文件作為輸入時,FNR 和 NR 的值是相同的;如果使用多個數據文件作為輸入,FNR 的值會在處理每個數據文件時被重置,而 NR 的值則會繼續計數直到處理完所有的數據文件。

自定義變量

和其他典型的編程語言一樣,awk 允許用戶定義自己的變量在腳本程序中使用。awk 自定義變量名可以是任意數目的字母、數字和下劃線,但不能以數字開頭。更重要的是,awk 變量名區分大小寫。

舉個簡單的例子:

[root@localhost ~]# awk '
> BEGIN{
> testing="This is a test"
> print testing
> testing=45
> print testing
> }'
This is a test
45

可以看到,print 語句的輸出是 testing 變量的當前值。

也可以用 awk 命令行來給程序中的變量賦值,這允許我們在正常的代碼之外賦值,即時改變變量的值,比如:

[root@localhost ~]# cat script1
BEGIN{FS=","} {print $n}
[root@localhost ~]# awk -f script1 n=2 data1
data12
data22
data32
[root@localhost ~]# awk -f script1 n=3 data1
data13
data23
data33

需要注意的是,使用命令行參數來定義變量值會有一個問題,即設置了變量后,這個值在代碼的 BEGIN 部分不可用,如下所示:

[root@localhost ~]# cat script2
BEGIN{print "The starting value is",n; FS=","}
{print $n}
[root@localhost ~]# awk -f script2 n=3 data1
The starting value is
data13
data23
data33

解決這個問題,可以用 -v 命令行參數,它可以實現在 BEGIN 代碼之前設定變量。在命令行上,-v 命令行參數必須放在腳本代碼之前,如下所示:

[root@localhost ~]# awk -v n=3 -f script2 data1
The starting value is 3
data13
data23
data33

注意,awk 腳本程序中輸出函數還可以使用 C 語言中的 printf 函數,具體用法可閱讀《C語言 printf 函數》 和《C語言輸出大匯總》兩篇文章,系統學習此函數的用法。

awk 使用數組

為了在單個變量中存儲多個值,許多編程語言都提供數組,awk 使用關聯數組提供數組功能。

關聯數組跟數字數組不同之處在于,它的索引值可以是任意文本字符串。用戶不需要用連續的數字來標識數組中的數據元素;相反,關聯數組用各種字符串來引用值。每個索引字符串都必須能夠唯一地標識出賦給它的數據元素。

如果你熟悉其他編程語言的話,其實關聯數組和散列表、字典的用法類似。

關聯數組的定義和使用

在 awk 腳本程序中,定義一個數組變量可以使用標準復制語句,其基本格式為:

var[index]=element

其中,var 是數組名,index 是關聯數組的索引值,element 是數據元素值。例如:

capital["Illinois"] = "Springfield"
capital["Indiana"] = "Indianapolis"
capital["Ohio"] = "Columbus"

在引用數組變量時,必須用索引值(index)來提取相應的數據元素值,例如:

[root@localhost ~]# awk 'BEGIN{
> capital["Illinois"] = "Springfield"
> print capital["Illinois"]
> }'
Springfield

數組變量也是變量,也可以使用其進行基本的算術運算,例如:

[root@localhost ~]# awk 'BEGIN{
> var[1] = 34
> var[2] = 3
> total = var[1] + var[2]
> print total
> }'
37

關聯數組的遍歷

在 awk 中遍歷關聯數組,可以用 for 語句的一種特殊形式:

for (var in array)
{
    statements
}

這個 for 語句會在每次循環時將關聯數組 array 的下一個索引值賦給變量 var,然后執行一遍 statements。

再次強調,整個遍歷過程中,傳給 var 的都是每個數組元素的索引值(也就是 index),不是數組元素的值。

舉個例子:

[root@localhost ~]# awk 'BEGIN{
> var["a"] = 1
> var["g"] = 2
> var["m"] = 3
> var["u"] = 4
> for (test in var)
> {
>    print "Index:",test," - Value:",var[test]
> }
> }'
Index: u  - Value: 4
Index: m  - Value: 3
Index: a  - Value: 1
Index: g  - Value: 2

注意,索引值不會按任何特定順序返回,但它們都能夠指向對應的數據元素值。

刪除數組變量

awk腳本程序還支持從關聯數組中刪除某個數組索引,使用 delete 命令就可以,此命令會從數組中刪除指定的索引值及相關的數據元素的值。

delete 命令的基本格式如下:

delete array[index]

舉個例子:

[root@localhost ~]# awk 'BEGIN{
> var["a"] = 1
> var["g"] = 2
> for (test in var)
> {
>    print "Index:",test," - Value:",var[test]
> }
> delete var["g"]
> print "---"
> for (test in var)
> {
>    print "Index:",test," - Value:",var[test]
> }
> }'
Index: a  - Value: 1
Index: g  - Value: 2
---
Index: a  - Value: 1

需要注意的是,一旦從關聯數組中刪除了索引值,就沒法再用它來提取元素值。

awk使用分支結構

awk 支持標準的 if-then-else 格式的 if 語句,其基本格式為:

if (condition)
    statement1
else
    statements

也可以將它放在一行上,像這樣:

if (condition) statement1;else statement2

舉個簡單的例子:

[root@localhost ~]# cat data4
10
5
13
50
34
[root@localhost ~]# awk '{if ($1 > 20) print $1 * 2; else print $1 / 2}' data4
5
2.5
6.5
100
68

awk使用循環結構

awk 腳本程序中,可以使用 while、do-while、for 這 3 種循環結構,它們各自的基本格式分別如表 3 所示。

表 3 循環結構基本格式及實例
基本格式實例
while (條件) {
   運行代碼;
}

[root@localhost ~]# cat data5
130 120 135
160 113 140
145 170 215
[root@localhost ~]# awk '{
> total = 0
> i = 1
> while (i < 4)
> {
>    total += $i
>    i++
> }
> avg = total / 3
> print "Average:",avg
> }' data5
Average: 128.333
Average: 137.667
Average: 176.667

do
{
運行代碼;
}while(條件)

[root@localhost ~]# awk '{
> total = 0
> i = 1
> do
> {
>    total += $i
>    i++
> } while (total < 150)
> print total }' data5
250
160
315

for(變量;條件;計數器)
{
    運行代碼;
}

[root@localhost ~]# awk '{
> total = 0
> for (i = 1; i < 4; i++)
> {
>    total += $i
> }
> avg = total / 3
> print "Average:",avg
> }' data5
Average: 128.333
Average: 137.667
Average: 176.667


從表 3 中可以看出,awk 支持使用的循環結構的用法和 C 語言完全一樣,除此之外,awk 還支持使用 break(跳出循環)、continue(終止當前循環)關鍵字,其用法和 C 語言中也完全相同,這里不再過多贅述,讀者可以閱讀《C語言循環結構和選擇結構》一章系統學習。

awk使用函數

內建函數

和內建變量類似,awk 也提供了不少內建函數,可進行一些常見的數學、字符串以及時間函數運算,如表 4 所示。

表 4 awk 內建函數
函數分類函數原型函數功能
數學函數atan2(x, y)x/y 的反正切,x 和 y 以弧度為單位。
cos(x)x 的余弦,x 以弧度為單位。
exp(x)x 的指數函數。
int(x)x 的整數部分,取靠近零一側的值。
log(x)x 的自然對數。
srand(x)為計算隨機數指定一個種子值。
rand()比 0 大比 1 小的隨機浮點值。
sin(x)x 的正弦,x 以弧度為單位。
sqrt(x)x 的平方根。
位運算函數and(v1, v2)執行值 v1 和 v2 的按位與運算。
compl(val)執行 val 的補運算。
lshift(val, count)將值 val 左移 count 位。
or(v1, v2)執行值 v1 和 v2 的按位或運算。
rshift(val, count)將值 val 右移 count 位。
xor(v1, v2)執行值 v1 和 v2 的按位異或運算。
字符串函數asort(s [,d])將數組 s 按數據元素值排序。索引值會被替換成表示新的排序順序的連續數字。另外,如果指定了 d,則排序后的數組會存儲在數組 d 中。
asorti(s [,d])將數組 s 按索引值排序。生成的數組會將索引值作為數據元素值,用連續數字索引來表明排序順序。另外如果指定了 d,排序后的數組會存儲在數組 d 中。
gensub(r, s, h [, t]) 查找變量 $0 或目標字符串 t(如果提供了的話)來匹配正則表達式 r。如果 h 是一個以 g 或 G 開頭的字符串,就用 s 替換掉匹配的文本。如果 h 是一個數字,它表示要替換掉第 h 處 r 匹配的地方。
gsub(r, s [,t])查找變量 $0 或目標字符串 t(如果提供了的話)來匹配正則表達式 r。如果找到了,就全部替換成字符串 s。
index(s, t)返回字符串 t 在字符串 s 中的索引值,如果沒找到的話返回 0。
length([s])返回字符串 s 的長度;如果沒有指定的話,返回 $0 的長度。
match(s, r [,a])返回字符串 s 中正則表達式 r 出現位置的索引。如果指定了數組 a,它會存儲 s 中匹配正則表達式的那部分。
split(s, a [,r])將 s 用 FS 字符或正則表達式 r(如果指定了的話)分開放到數組 a 中,并返回字段的總數。
sprintf(format, variables)用提供的 format 和 variables 返回一個類似于 printf 輸出的字符串。
sub(r, s [,t]) 在變量 $0 或目標字符串 t 中查找正則表達式 r 的匹配。如果找到了,就用字符串 s 替換掉第一處匹配。
substr(s, i [,n])返回 s 中從索引值 i 開始的 n 個字符組成的子字符串。如果未提供 n,則返回 s 剩下的部分。
tolower(s)將 s 中的所有字符轉換成小寫。
toupper(s)將 s 中的所有字符轉換成大寫。
時間函數mktime(datespec)將一個按 YYYY MM DD HH MM SS [DST] 格式指定的日期轉換成時間戳值。
strftime(format [,timestamp])將當前時間的時間戳或 timestamp(如果提供了的話)轉化格式化日期(采用 shell 函數 date() 的格式)。
systime()返回當前時間的時間戳。

時間戳指的是格林威治時間,即從 1970年1月1日8時1起到現在的總秒數。

自定義函數

除了awk 中的內建函數,還可以在 awk 腳本程序中自定義函數,創建自定義函數的基本格式為:

function 函數名(參數1,參數2,...)
{
    運行代碼;
}

注意,自定義函數的函數名必須能夠唯一標識此函數,換句話說,在同一個 awk 腳本程序中,多個函數的函數名不能相同。同時,函數的參數可以有多個(0 個、1 個或多個)。

例如:

function printthird()
{
    print $3
}

此函數會打印記錄中的第三個數據字段。

函數還能用 return 語句返回值,例如:

function myrand(limit) {
    return int(limit * rand())
}

需要注意的是,在定義函數時,它必須出現在所有代碼塊之前(包括 BEGIN 和 END代碼塊)。

創建函數庫

awk 提供了一種途徑來將多個函數放到一個庫文件中,這樣用戶就能在所有的 awk 腳本程序中使用了。為了方便大家理解,下面給大家舉個實例。

首先,我們需要創建一個存儲所有 awk 函數的文件:

[root@localhost ~]# cat funclib
function myprint() {
   printf "%-16s - %s\n", $1, $4
}
function myrand(limit)
{
   return int(limit * rand())
}
function printthird()
{
   print $3
}

要想讓 awk 成功讀取 funclib 函數庫文件,就需要使用 -f 選項,但此選項無法和 awk 腳本程序同時放到命令行中一起使用。因此,要使用庫函數文件,只能再創建一個腳本程序文件,例如:

[root@localhost ~]# cat script4
BEGIN{ FS="\n"; RS=""}
{
     myprint()
}
[root@localhost ~]# awk -f funclib -f script4 data2
Riley Mullen     - (312)555-1234
Frank Williams   - (317)555-9876
Haley Snell      - (313)555-4938

beylze編程學院,一個分享編程知識和seo優化知識的網站。跟著beylze一起學習,每天都有進步。

通俗易懂,深入淺出,一篇文章只講一個知識點。

文章不深奧,不需要鉆研,在公交、在地鐵、在廁所都可以閱讀,隨時隨地漲姿勢。

文章不涉及代碼,不燒腦細胞,人人都可以學習。

當你決定關注beylze(公眾號:beylze),你已然超越了90%的其他從業者!

相關文章

優秀教程

国产亚洲欧美日韩