大家好 我是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 program
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 | 當前行的所有內容 |
$1~$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)
action
這裡我用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 -f getPrime.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;
}
}
像上面的語法學過程式語言的人都不會陌生把,非常的簡單。
結語
awk作為一面語言似乎非常的小眾,和其他成熟程式語言比起來似乎毫無優勢,但它只專注於文字處理,在文字處理這一領域卻是佼佼者。不過確實也有個現象,現在隨著各類分散式文字檢索工具的出現(比如elastic search),會使用awk的人越來越少了,也許像這類優秀的命令列工具未來會逐漸被新生代程式猿遺忘在歷史的長河中……,所以希望我這篇文章能讓awk被更多的人瞭解到。
另外本文只是粗淺的介紹了下awk的基礎功能,但如果你想要精通awk還需要自行查閱一些其他的資料,並且伴以大量的聯絡。今天我也聽了會csdn 1024線上活動直播,恰好聽到一些top級的程式猿為普通程式猿提的建議,其實都是些老生常談的內容,道理大家都懂,但大多數人都是流於平庸,核心還是少了實踐和積累。不積跬步無以至千里,不積小流無以成江河。