Linux awk命令的高級玩法
前面一節已經介紹了 awk 的基本用法,其實在 awk 腳本程序中,還支持使用一些編程語言,比如變量、數組、分支結構(if-then-else)、循環結構(while)、函數等,下面一一給大家介紹。
awk 使用變量
在 awk 的腳本程序中,支持使用變量來存取值。awk 支持兩種不同類型的變量:
內建變量:awk 本身就創建好,用戶可以直接拿來用的變量,這些變量用來存放處理數據文件中的某些字段和記錄的信息。
自定義變量:awk 支持用戶自己創建變量。
內建變量
awk 程序使用內建變量來引用程序數據里的一些特殊功能。常見的一些內建變量,包括上一節介紹的數據字段變量($0、$1、$2...$n)以及表 1 、表 2 中所示的這些變量。
變量 | 功能 |
---|---|
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
變量名 | 功能 |
---|---|
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 所示。
基本格式 | 實例 |
---|---|
while (條件) { 運行代碼; } | [root@localhost ~]# cat data5 |
do { 運行代碼; }while(條件) | [root@localhost ~]# awk '{ |
for(變量;條件;計數器) { 運行代碼; } | [root@localhost ~]# awk '{ |
從表 3 中可以看出,awk 支持使用的循環結構的用法和 C 語言完全一樣,除此之外,awk 還支持使用 break(跳出循環)、continue(終止當前循環)關鍵字,其用法和 C 語言中也完全相同,這里不再過多贅述,讀者可以閱讀《C語言循環結構和選擇結構》一章系統學習。
awk使用函數
內建函數
和內建變量類似,awk 也提供了不少內建函數,可進行一些常見的數學、字符串以及時間函數運算,如表 4 所示。
函數分類 | 函數原型 | 函數功能 |
---|---|---|
數學函數 | 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%的其他從業者!