大家好 我是xindoo,距我上次發技術文章已經過去快一個半月了,原因是最近确實非常非常的忙,工作日除了吃飯睡覺之外,要麼是在工作,要麼就是在去工作的路上,而周末的時候我隻想

今天1024 程式猿節,百忙中抽空發篇一直想寫好久的文章來湊個熱鬧,簡單教大家如何使用awk這個指令行工具。認識我的人都知道我最早是運維出生,做運維沒學會啥太大的本事,有些指令行工具卻使得賊溜,awk就是其中之一。後來我轉開發後,憑借精通部分指令行工具的使用快速解決過很多小問題,指令行的便捷和高效也曾多次震驚到我們的同僚們。
各種指令行工具加管道的組合,可以極快的解決很多問題,這裡我就不再展開了,有興趣可以看下我之前寫的一篇部落格我常用的一些linux指令。而今天的主角是awk,一款極強的文本處理工具,我日常會用它來做資料清洗、篩選、檢視,甚至完成一些簡單的資料統計工作。毫不誇張的說,有些别人需要幾個小時、甚至完全搞不定的工作,我用awk分分鐘解決完,在别人看來完全就是黑魔法。
這麼說可能你沒有感覺,我舉個具體的例子。之前有個同僚,需要把一個上千萬行的文本檔案(大于500MB)均勻拆成倆檔案,其實就是想把千萬的使用者均勻随機拆成兩個集合做一些對比實驗,你會怎麼搞? 實際上我用awk一行指令搞定,敲指令20秒,執行半分鐘。
cat users.txt |awk 'NR%2==0 {print $1}' > 0.txt
cat users.txt |awk 'NR%2==1 {print $1}' > 1.txt
說這麼多隻是為了引出awk的強大,是以awk到底是什麼?很多初學者都認為awk是一個文本處理工具,它與grep、sed同稱為linux文本三劍客。 實際awk不僅僅是文本處理工具,它也是一門程式設計語言,awk隻是針對于文本處理提供了很多内置變量和函數(稍後會詳細介紹),使得他極易用于文本處理而已,接下來請跟随我由儉入深來學習下awk的使用。
基本使用
awk的基本用法就是,awk + 具體的執行 + 文本檔案,它也可以從linux管道裡讀取内容,兩種使用方法如下。
awk program textfile
cat textfile | awk
awk實際上是面向行的資料處理的,也就是說它的指令對于每一行資料都會執行一次,比如如下的例子
cat a.txt| awk '{print $1, $3}'
上面這條指令就是輸出檔案所有行的第1、3列,下标是從1開始的,而$0有特殊含義,指的是這一行的所有資料。 awk預設是使用空格或者tab來區分列的,有時候文本檔案不以空格或者tab分列,而是以特殊符号(比如 - )來分列,awk也提供了-F 參數來指定分隔符。
cat a.txt| awk -F'-' '{print $1, $3}'
内置變量
awk極擅長處理文本,其中一個原因就是它提供了大量的内置變量,可以很輕易就擷取到文本内容的一些資訊,比如目前在第幾行(NR)、這一行有多少列(NF),目前處理的檔案名(FILENAME)是啥…… 下面僅列舉一部分,
變量 | 作用 |
$0 | 目前行的所有内容 |
n | 目前行的第1-第n列 |
NF | 目前行有多少列 |
NR | 目前是第幾行,從1開始 |
RS | 輸入的記錄他隔符默 認為換行符 |
OFS | 輸出字段分隔符 預設也是空格 |
ORS | 輸出的記錄分隔符,預設為換行符 |
ARGC | 指令行參數個數 |
ARGV | 指令行參數數組 |
FILENAME | 目前輸入檔案的名字 |
IGNORECASE | 如果為真,則進行忽略大小寫的比對 |
ARGIND | 目前被處理檔案的ARGV标志符 |
比如我要輸出一個文本檔案a.txt,以 | 風格的話,第幾行分别有多少列,我就可以這麼寫:
cat a.txt | awk -F'|' '{print NR, NF}'
我在部落格《awk實作類sql的join操作》 中就用到了多個内置變量完成了對多個文本的複雜處理,有興趣可以看下,類似的對多個檔案求交集、差集都很容易實作。
内置函數
除了内置的變量外,awk也内置了很多常用的函數,這裡我也不在贅述,具體内容可以查閱https://www.runoob.com/w3cnote/awk-built-in-functions.html ,awk内置的函數主要分為以下幾種:
- 算數函數
- 字元串函數
- 時間函數
- 位操作函數
- 其它函數
這些内置函數可以完成打大多數常用的操作,如果這些内置函數還不夠用的話,剛才也說過了,awk是一門程式設計語言,需要啥函數你都可以自己實作。
文法
接下來我們來介紹下awk在指令行外作為一門程式設計語言的基本知識。
變量
首先從變量開始,除了上文說到的那些内置變量,你也可以自行使用其他的變量。awk和python語言,它是弱類型的,不用聲明,變量直接使用。 比如要求一個文本檔案第2列的綜合和平均值,就可以這麼寫。
cat a.txt |awk '{sum += $2; cnt += 1} END {print sum, sum/cnt}'
這裡sum和cnt就是我們自定義的變量,随用随寫,很是友善。除了簡單變量外,awk也支援一些複雜資料結構,比如map,這裡我還是舉一個例子,比如我們擁有一批人最近一個月明天的體重記錄,我們想知道每個人這一個月的平均體重是多少,資料如下,總共有三列,分别是姓名、日期、體重。
張三 2021-10-01 67.7
李四 2021-10-01 83.9
張三 2021-10-02 68.1
李四 2021-10-02 85.0
張三 2021-10-03 68.3
張三 2021-10-01 67.9
李四 2021-10-03 84.0
...
使用awk中的map,可以将每個人的體重總和sum和數量cnt分别存儲起來,等到所有資料處理完之後統一輸出即可,具體代碼如下:
cat a.txt|awk '{sum[$1] += $3;cnt[$1] += 1} END {for (key in sum) {print key, sum[key]/cnt[key]}}'
判斷
從上面幾個例子中,大家也注意到了,有時候不得不使用一些判斷條件。比如在最開始的文本拆分的例子中,我是按行号的奇偶将檔案拆分成兩個,這個時候需要按不同的含号執行不同的邏輯,在awk中判斷邏輯也很簡單。
awk 'expr { statement }' # 隻有expr為true的時候大括号中的statement代碼塊才會執行。
像上文中已經多次出現的END就表示隻有所有行都處理完後,其後面的代碼塊才會執行。和END對應的還有BEGIN,其所對應的代碼是在檔案處理開始前執行,是以一般都會做一些檔案初始化的工作。 其他你自定的判斷也都可以通過類似的方式寫,另外它也是支援if else的,其寫法如下:
cat a.txt |awk '{if (NR%2==1) print NR, $1 ; else print NR, $2}' # 如果是奇數行就輸出行号和第一列,否則輸出行号和第二列
循環
awk也支援for和while循環,和c語言for和while循環是一樣的,如下:
for (initialisation; condition; increment/decrement)
action
while (condition)
這裡我用awk實作輸出0-100之間所有的素數為例,串一下上面說的循環和判斷,除了變量定義外,和C語言基本一緻了。
BEGIN {
i = 2;
while (i < 100) {
isPrime = 1;
for (j = 2; j < i; j++) {
if (i % j == 0) {
isPrime = 0;
}
}
if (isPrime == 1) {
print i;
}
i += 1;
}
}
如果代碼太長,無法完整的拼接到指令行後,可以把代碼存到檔案中,然後用awk -f 調起,比如:
awk
函數
awk的函數定義也非常簡單,和js是一毛一樣了,具體可以參考https://www.runoob.com/w3cnote/awk-user-defined-functions.html
function isPrime(n) {
for (j = 2; j < n; j++) {
if (i % j == 0) {
return 0;
}
}
return 1;
}
BEGIN {
i = 2;
while (i < 100) {
if (isPrime(i)) {
print i;
}
i += 1;
}
}
像上面的文法學過程式設計語言的人都不會陌生把,非常的簡單。