運維之路-文字處理-三劍客(grep、sed、awk)
轉載:https://blog.csdn.net/zisefeizhu/article/details/82526749
目錄
grep
支援的正則 描述
輸出控制 描述
內容行控制 描述
示例:
sed
Usage:
命令 描述
地址 描述
匹配刪除(d)
替換(s///)
多重編輯(-e)
列印和刪除模式空間第一行(P 和 D)
標籤(:、b 和 t)
獲取總行數(#)
選項
選項 描述
常用模式有:
示例:
內建變數
示例:
運算子 描述
示例:
流程控制
語句 描述
示例:
printf 語句
自定義函式
需求案例
1)分析 Nginx 日誌
2)兩個檔案對比
3)合併兩個檔案
4)將第一列合併到一行
5)字串拆分,統計出現的次數
6)統計平均成績
7)費用統計
8)數字欄位最大值
9)去除第一行和最後一行
本篇博文整理於wiki
grep
過濾來自一個檔案或標準輸入匹配模式內容。
除了 grep 外,還有 egrep、fgrep。egrep 是 grep 的擴充套件,相當於 g
f,用的少。
Usage: grep [OPTION]... PATTERN [FILE]...
支援的正則 描述
-E,--extended-regexp 模式是擴充套件正則表示式(ERE)
-P,--perl-regexp 模式是 Perl 正則表示式
-e,--regexp=PATTERN 使用模式匹配,可指定多個模式匹
-f,--file=FILE 從檔案每一行獲取匹配模式
-i,--ignore-case 忽略大小寫
-w,--word-regexp 模式匹配整個單詞
-x,--line-regexp 模式匹配整行
-v,--invert-match 列印不匹配的行
輸出控制 描述
-m,--max-count=NUM 輸出匹配的結果 num 數
-n,--line-number 列印行號
-H,--with-filename 列印每個匹配的檔名
-h,--no-filename 不輸出檔名
-o,--only-matching 只打印匹配的內容
-q,--quiet 不輸出正常資訊
-s, --no-messages 不輸出錯誤資訊
-r,--recursive 遞迴目錄
-c,--count 只打印每個檔案匹配的行數
--include=FILE_PATTERN 只檢索匹配的檔案
--exclude=FILE_PATTERN 跳過匹配的檔案
--exclude-from=FILE 跳過匹配的檔案,來自檔案模式
--exclude-dir=PATTERN 跳過匹配的目錄
內容行控制 描述
-B,--before-context=NUM 列印匹配的前幾行
-A,--after-context=NUM 列印匹配的後幾行
-C,--context=NUM 列印匹配的前後幾行
--color[=WHEN], 匹配的字型顏色
示例:
1)輸出 b 檔案中在 a 檔案相同的行
# grep -f a b
2)輸出 b 檔案中在 a 檔案不同的行
# grep -v -f a b
3) 匹配多個模式
# echo "a bc de" |xargs -n1 |grep -e 'a' -e 'bc
a
bc
4)去除空格 http.conf 檔案空行或開頭#號的行
# grep -E -v "^$|^#" /etc/httpd/conf/httpd.conf
5) 匹配開頭不分大小寫的單詞
# echo "A a b c" |xargs -n1 |grep -i a
或
# echo "A a b c" |xargs -n1 |grep '[Aa]'
A
a
6)只顯示匹配的字串
# echo "this is a test" |grep -o 'is'
is
is
7)輸出匹配的前五個結果
# seq 1 20 |grep -m 5 -E '[0-9]{2}'
10
11
12
13
14
8)統計匹配多少行
# seq 1 20 |grep -c -E '[0-9]{2}'
11
9) 匹配 b 字元開頭的行
# echo "a bc de" |xargs -n1 |grep '^b'
bc
10) 匹配 de 字元結尾的行並輸出匹配的行
# echo "a ab abc abcd abcde" |xargs -n1 |grep -n 'de$'
5:abcde
11) 遞迴搜尋/etc 目錄下包含 ip 的 conf 字尾檔案
# grep -r '192.167.1.1' /etc --include *.conf
12) 排除搜尋 bak 字尾的檔案
# grep -r '192.167.1.1' /opt --exclude *.bak
13) 排除來自 file 中的檔案
# grep -r '192.167.1.1' /opt --exclude-from file
14) 匹配 41 或 42 的數字
# seq 41 45 |grep -E '4[12]'
41
42
15) 匹配至少 2 個字元
# seq 13 |grep -E '[0-9]{2}'
10
11
12
13
16) 匹配至少 2 個字元的單詞,最多 3 個字元的單詞
# echo "a ab abc abcd abcde" |xargs -n1 |grep -E -w -o '[a-z]{2,3}'
ab
abc
17) 匹配所有 IP
# ifconfig |grep -E -o "[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}"
18) 列印匹配結果及後 3 行
# seq 1 10 |grep 5 -A 3
5
6
7
8
19) 列印匹配結果及前 3 行
# seq 1 10 |grep 5 -B 3
2
3
4
5
20) 列印匹配結果及前後 3 行
# seq 1 10 |grep 5 -C 3
2
3
4
5
6
7
8
21) 不顯示輸出
不顯示錯誤輸出:
# grep 'a' abc
grep: abc: No such file or directory
# grep -s 'a' abc
# echo $?
2
不顯示正常輸出:
# grep -q 'a' a.txt
grep 支援基礎和擴充套件正則表示式字元
sed
流編輯器,過濾和替換文字。
工作原理:sed 命令將當前處理的行讀入模式空間進行處理,處理完把結果輸出,並清空模式空間。然後再將下一行讀入模式空間進行處理輸出,以此類推,直到最後一行。還有一個空間叫保持空間,又稱暫存空間,可以暫時存放一些處理的資料,但不能直接輸出,只能放到模式空間輸出。
這兩個空間其實就是在記憶體中初始化的一個記憶體區域,存放正在處理的資料和臨時存放的資料。
Usage:
sed [OPTION]... {script-only-if-no-other-script} [input-file]...
sed [選項] '地址 命令' file
選項 描述
-n 不列印模式空間
-e 執行指令碼、表示式來處理
-f 執行動作從檔案讀取執行
-i 修改原檔案
-r 使用擴充套件正則表示式
命令 描述
s/regexp/replacement/ 替換字串
p 列印當前模式空間
P 列印模式空間的第一行
d 刪除模式空間,開始下一個迴圈
D 刪除模式空間的第一行,開始下一個迴圈
= 列印當前行號
a \text 當前行追加文字
i \text 當前行上面插入文字
c \text 所選行替換新文字
q 立即退出 sed 指令碼
r 追加文字來自檔案
: label label 為 b 和 t 命令
b label 分支到指令碼中帶有標籤的位置,如果分支不存在則分支到指令碼
的末尾
t label 如果 s///是一個成功的替換,才跳轉到標籤
h H 複製/追加模式空間到保持空間
g G 複製/追加保持空間到模式空間
x 交換模式空間和保持空間內容
l 列印模式空間的行,並顯示控制字元$
n N 讀取/追加下一行輸入到模式空間
w filename 寫入當前模式空間到檔案
! 取反、否定
& 引用已匹配字串
地址 描述
first~step 步長,每 step 行,從第 first 開始
$ 匹配最後一行
/regexp/ 正則表示式匹配行
number 只匹配指定行
addr1,addr2 開始匹配 addr1 行開始,直接 addr2 行結束
addr1,+N 從 addr1 行開始,向後的 N 行
addr1,~N 從 addr1 行開始,到 N 行結束
藉助以下文字內容作為示例講解:
# tail /etc/services
nimgtw 48003/udp # Nimbus Gateway
3gpp-cbsp 48049/tcp # 3GPP Cell Broadcast Service Protocol
isnetserv 48128/tcp # Image Systems Network Services
isnetserv 48128/udp # Image Systems Network Services
blp5 48129/tcp # Bloomberg locator
blp5 48129/udp # Bloomberg locator
com-bardac-dw 48556/tcp # com-bardac-dw
com-bardac-dw 48556/udp # com-bardac-dw
iqobject 48619/tcp # iqobject
iqobject 48619/udp # iqobject
匹配列印(p)
1)列印匹配 blp5 開頭的行
# tail /etc/services |sed -n '/^blp5/p'
blp5 48129/tcp # Bloomberg locator
blp5 48129/udp # Bloomberg locator
2)列印第行
# tail /etc/services |sed -n '1p'
nimgtw 48003/udp # Nimbus Gateway
3)列印第一行至第三行
# tail /etc/services |sed -n '1,3p'
nimgtw 48003/udp # Nimbus Gateway
3gpp-cbsp 48049/tcp # 3GPP Cell Broadcast Service Protoc
isnetserv 48128/tcp # Image Systems Network Services
4)列印奇數行
# seq 10 |sed -n '1~2p'
1
3
5
7
9
5)列印匹配行及後一行
# tail /etc/services |sed -n '/blp5/,+1p'
blp5 48129/tcp # Bloomberg locator
blp5 48129/udp # Bloomberg locator
6)列印最後一行
# tail /etc/services |sed -n '$p'
iqobject 48619/udp # iqobject
7)不列印最後一行
# tail /etc/services |sed -n '$!p'
3gpp-cbsp 48049/tcp # 3GPP Cell Broadcast Service
Protocol
isnetserv 48128/tcp # Image Systems Network Services
isnetserv 48128/udp # Image Systems Network Services
blp5 48129/tcp # Bloomberg locator
blp5 48129/udp # Bloomberg locator
com-bardac-dw 48556/tcp # com-bardac-dw
com-bardac-dw 48556/udp # com-bardac-dw
iqobject 48619/tcp # iqobject
iqobject 48619/udp # iqobject
感嘆號也就是對後面的命令取反。
8)匹配範圍
# tail /etc/services |sed -n '/^blp5/,/^com/p'
blp5 48129/tcp # Bloomberg locator
blp5 48129/udp # Bloomberg locator
com-bardac-dw 48556/tcp # com-bardac-dw
匹配開頭行到最後一行:
# tail /etc/services |sed -n '/blp5/,$p'
blp5 48129/tcp # Bloomberg locator
blp5 48129/udp # Bloomberg locator
com-bardac-dw 48556/tcp # com-bardac-dw
com-bardac-dw 48556/udp # com-bardac-dw
iqobject 48619/tcp # iqobject
iqobject 48619/udp # iqobject
以逗號分開兩個樣式選擇某個範圍。
9)引用系統變數,用引號
# a=1
# tail /etc/services |sed -n ''$a',3p'
或
# tail /etc/services |sed -n "$a,3p"
sed 命令用單引號時,裡面變數用單引號引起來,或者 sed 命令用雙引號,因為雙引號解釋特殊符號原有意義。
匹配刪除(d)
刪除與列印使用方法類似,簡單舉幾個例子。
# tail /etc/services |sed '/blp5/d'
nimgtw 48003/udp # Nimbus Gateway
3gpp-cbsp 48049/tcp # 3GPP Cell Broadcast Service
isnetserv 48128/tcp # Image Systems Network Services
isnetserv 48128/udp # Image Systems Network Services
com-bardac-dw 48556/tcp # com-bardac-dw
com-bardac-dw 48556/udp # com-bardac-dw
iqobject 48619/tcp # iqobject
iqobject 48619/udp # iqobject
# tail /etc/services |sed '1d'
3gpp-cbsp 48049/tcp # 3GPP Cell Broadcast Service
Protocol
isnetserv 48128/tcp # Image Systems Network Services
isnetserv 48128/udp # Image Systems Network Services
blp5 48129/tcp # Bloomberg locator
blp5 48129/udp # Bloomberg locator
com-bardac-dw 48556/tcp # com-bardac-dw
com-bardac-dw 48556/udp # com-bardac-dw
iqobject 48619/tcp # iqobject
iqobject 48619/udp # iqobject
# tail /etc/services |sed '1~2d'
3gpp-cbsp 48049/tcp # 3GPP Cell Broadcast Service
isnetserv 48128/udp # Image Systems Network Services
blp5 48129/udp # Bloomberg locator
com-bardac-dw 48556/udp # com-bardac-dw
iqobject 48619/udp # iqobject
# tail /etc/services |sed '1,3d'
isnetserv 48128/udp # Image Systems Network Services
blp5 48129/tcp # Bloomberg locator
blp5 48129/udp # Bloomberg locator
com-bardac-dw 48556/tcp # com-bardac-dw
com-bardac-dw 48556/udp # com-bardac-dw
iqobject 48619/tcp # iqobject
iqobject 48619/udp # iqobject
去除空格 http.conf 檔案空行或開頭#號的行:
# sed '/^#/d;/^$/d' /etc/httpd/conf/httpd.conf
列印是把匹配的打印出來,刪除是把匹配的刪除,刪除只是不用-n 選項。
替換(s///)
1)替換 blp5 字串為 test
# tail /etc/services |sed 's/blp5/test/'
3gpp-cbsp 48049/tcp # 3GPP Cell Broadcast Service
isnetserv 48128/tcp # Image Systems Network Services
isnetserv 48128/udp # Image Systems Network Services
test 48129/tcp # Bloomberg locator
test 48129/udp # Bloomberg locator
com-bardac-dw 48556/tcp # com-bardac-dw
com-bardac-dw 48556/udp # com-bardac-dw
iqobject 48619/tcp # iqobject
iqobject 48619/udp # iqobject
matahari 49000/tcp # Matahari Broker
全域性替換加 g:
# tail /etc/services |sed 's/blp5/test/g'
2)替換開頭是 blp5 的字串並列印
# tail /etc/services |sed -n 's/^blp5/test/p'
test 48129/tcp # Bloomberg locator
test 48129/udp # Bloomberg locator
3)使用&命令引用匹配內容並替換
# tail /etc/services |sed 's/48049/&.0/'
3gpp-cbsp 48049.0/tcp # 3GPP Cell Broadcast Service
isnetserv 48128/tcp # Image Systems Network Services
isnetserv 48128/udp # Image Systems Network Services
blp5 48129/tcp # Bloomberg locator
blp5 48129/udp # Bloomberg locator
com-bardac-dw 48556/tcp # com-bardac-dw
com-bardac-dw 48556/udp # com-bardac-dw
iqobject 48619/tcp # iqobject
iqobject 48619/udp # iqobject
matahari 49000/tcp # Matahari Broker
IP 加單引號:
# echo '10.10.10.1 10.10.10.2 10.10.10.3' |sed -r 's/[^ ]+/"&"/g'
"10.10.10.1" "10.10.10.2" "10.10.10.3"
4)對 1-4 行的 blp5 進行替換
# tail /etc/services | sed '1,4s/blp5/test/'
3gpp-cbsp 48049/tcp # 3GPP Cell Broadcast Service
isnetserv 48128/tcp # Image Systems Network Services
isnetserv 48128/udp # Image Systems Network Services
test 48129/tcp # Bloomberg locator
blp5 48129/udp # Bloomberg locator
com-bardac-dw 48556/tcp # com-bardac-dw
com-bardac-dw 48556/udp # com-bardac-dw
iqobject 48619/tcp # iqobject
iqobject 48619/udp # iqobject
matahari 49000/tcp # Matahari Broker
5)對匹配行進行替換
# tail /etc/services | sed '/48129\/tcp/s/blp5/test/'
3gpp-cbsp 48049/tcp # 3GPP Cell Broadcast Service
isnetserv 48128/tcp # Image Systems Network Services
isnetserv 48128/udp # Image Systems Network Services
test 48129/tcp # Bloomberg locator
blp5 48129/udp # Bloomberg locator
com-bardac-dw 48556/tcp # com-bardac-dw
com-bardac-dw 48556/udp # com-bardac-dw
iqobject 48619/tcp # iqobject
iqobject 48619/udp # iqobject
matahari 49000/tcp # Matahari Broker
6)二次匹配替換
# tail /etc/services |sed 's/blp5/test/;s/3g/4g/'
4gpp-cbsp 48049/tcp # 3GPP Cell Broadcast Service
isnetserv 48128/tcp # Image Systems Network Services
isnetserv 48128/udp # Image Systems Network Services
test 48129/tcp # Bloomberg locator
test 48129/udp # Bloomberg locator
com-bardac-dw 48556/tcp # com-bardac-dw
com-bardac-dw 48556/udp # com-bardac-dw
iqobject 48619/tcp # iqobject
iqobject 48619/udp # iqobject
matahari 49000/tcp # Matahari Broker
7)分組使用,在每個字串後面新增 123
# tail /etc/services |sed -r 's/(.*) (.*)(#.*)/\1\2test \3/'
3gpp-cbsp 48049/tcp test # 3GPP Cell Broadcast Service
isnetserv 48128/tcp test # Image Systems Network Services
isnetserv 48128/udp test # Image Systems Network Services
blp5 48129/tcp test # Bloomberg locator
blp5 48129/udp test # Bloomberg locator
blp5 48129/udp test # Bloomberg locator
com-bardac-dw 48556/tcp test # com-bardac-dw
com-bardac-dw 48556/udp test # com-bardac-dw
iqobject 48619/tcp test # iqobject
iqobject 48619/udp test # iqobject
matahari 49000/tcp test # Matahari Broker
第一列是第一個小括號匹配,第二列第二個小括號匹配,第三列一樣。將不變的字串匹配分組,
再通過\數字按分組順序反向引用。
8)將協議與埠號位置調換
# tail /etc/services |sed -r 's/(.*)(\<[0-9]+\>)\/(tcp|udp)(.*)/\1\3\/\2\4/'
3gpp-cbsp tcp/48049 # 3GPP Cell Broadcast Service
isnetserv tcp/48128 # Image Systems Network Services
isnetserv udp/48128 # Image Systems Network Services
blp5 tcp/48129 # Bloomberg locator
blp5 udp/48129 # Bloomberg locator
com-bardac-dw tcp/48556 # com-bardac-dw
com-bardac-dw udp/48556 # com-bardac-dw
iqobject tcp/48619 # iqobject
iqobject udp/48619 # iqobject
matahari tcp/49000 # Matahari Broker
9)位置調換
替換 x 字元為大寫:
# echo "abc cde xyz" |sed -r 's/(.*)x/\1X/'
abc cde Xyz
456 與 cde 調換:
# echo "abc:cde;123:456" |sed -r 's/([^:]+)(;.*:)([^:]+$)/\3\2\1/'
abc:456;123:cde
10)註釋匹配行後的多少行
# seq 10 |sed '/5/,+3s/^/#/'
1
2
3
4
#5
#6
#7
#8
9
10
11)註釋指定多行
# seq 5 |sed -r 's/^3|^4/&#/'
1
2
3#
4#
5
# seq 5 |sed -r '/^3|^4/s/^/#/'
1
2
#3
#4
5
# seq 5 |sed -r 's/^3|^4/#\0/'
1
2
#3
#4
5
12)去除開頭和結尾空格或製表符
# echo " 1 2 3 " |sed 's/^[ \t]*//;s/[ \t]*$//'
1 2 3
多重編輯(-e)
# tail /etc/services |sed -e '1,2d' -e 's/blp5/test/'
isnetserv 48128/udp # Image Systems Network Services
test 48129/tcp # Bloomberg locator
test 48129/udp # Bloomberg locator
com-bardac-dw 48556/tcp # com-bardac-dw
com-bardac-dw 48556/udp # com-bardac-dw
iqobject 48619/tcp # iqobject
iqobject 48619/udp # iqobject
matahari 49000/tcp # Matahari Broker
也可以使用分號分隔:
# tail /etc/services |sed '1,2d;s/blp5/test/'
7.2.5 新增新內容(a、i 和 c)
1)在 blp5 上一行新增 test
# tail /etc/services |sed '/blp5/i \test'
3gpp-cbsp 48049/tcp # 3GPP Cell Broadcast Service
isnetserv 48128/tcp # Image Systems Network Services
isnetserv 48128/udp # Image Systems Network Services
test
blp5 48129/tcp # Bloomberg locator
test
blp5 48129/udp # Bloomberg locator
com-bardac-dw 48556/tcp # com-bardac-dw
com-bardac-dw 48556/udp # com-bardac-dw
iqobject 48619/tcp # iqobject
iqobject 48619/udp # iqobject
matahari 49000/tcp # Matahari Broker
2)在 blp5 下一行新增 test
# tail /etc/services |sed '/blp5/a \test'
3gpp-cbsp 48049/tcp # 3GPP Cell Broadcast Service
isnetserv 48128/tcp # Image Systems Network Services
isnetserv 48128/udp # Image Systems Network Services
blp5 48129/tcp # Bloomberg locator
test
blp5 48129/udp # Bloomberg locator
test
com-bardac-dw 48556/tcp # com-bardac-dw
com-bardac-dw 48556/udp # com-bardac-dw
iqobject 48619/tcp # iqobject
iqobject 48619/udp # iqobject
matahari 49000/tcp # Matahari Broker
3)將 blp5 替換新行
# tail /etc/services |sed '/blp5/c \test'
3gpp-cbsp 48049/tcp # 3GPP Cell Broadcast Service
isnetserv 48128/tcp # Image Systems Network Services
isnetserv 48128/udp # Image Systems Network Services
test
test
com-bardac-dw 48556/tcp # com-bardac-dw
com-bardac-dw 48556/udp # com-bardac-dw
iqobject 48619/tcp # iqobject
iqobject 48619/udp # iqobject
matahari 49000/tcp # Matahari Broker
4)在指定行下一行新增一行
# tail /etc/services |sed '2a \test'
3gpp-cbsp 48049/tcp # 3GPP Cell Broadcast Service
isnetserv 48128/tcp # Image Systems Network Services
test
isnetserv 48128/udp # Image Systems Network Services
blp5 48129/tcp # Bloomberg locator
blp5 48129/udp # Bloomberg locator
com-bardac-dw 48556/tcp # com-bardac-dw
com-bardac-dw 48556/udp # com-bardac-dw
iqobject 48619/tcp # iqobject
iqobject 48619/udp # iqobject
matahari 49000/tcp # Matahari Broker
5)在指定行前面和後面新增一行
# seq 5 |sed '3s/.*/txt\n&/'
1
2
txt
3
4
5
# seq 5 |sed '3s/.*/&\ntxt/'
1
2
3
txt
4
5
讀取檔案並追加到匹配行後(r)
# cat a.txt
123
456
# tail /etc/services |sed '/blp5/r a.txt'
3gpp-cbsp 48049/tcp # 3GPP Cell Broadcast Service
isnetserv 48128/tcp # Image Systems Network Services
isnetserv 48128/udp # Image Systems Network Services
blp5 48129/tcp # Bloomberg locator
123
456
blp5 48129/udp # Bloomberg locator
123
456
com-bardac-dw 48556/tcp # com-bardac-dw
com-bardac-dw 48556/udp # com-bardac-dw
iqobject 48619/tcp # iqobject
iqobject 48619/udp # iqobject
matahari 49000/tcp # Matahari Broker
7.2.7 將匹配行寫到檔案(w)
# tail /etc/services |sed '/blp5/w b.txt'
3gpp-cbsp 48049/tcp # 3GPP Cell Broadcast Service
isnetserv 48128/tcp # Image Systems Network Services
isnetserv 48128/udp # Image Systems Network Services
blp5 48129/tcp # Bloomberg locator
blp5 48129/udp # Bloomberg locator
bdd48556/t# bdd
com-bardac-dw 48556/tcp # com-bardac-dw
com-bardac-dw 48556/udp # com-bardac-dw
iqobject 48619/tcp # iqobject
iqobject 48619/udp # iqobject
matahari 49000/tcp # Matahari Broker
# cat b.txt
blp5 48129/tcp # Bloomberg locator
blp5 48129/udp # Bloomberg locator
讀取下一行(n 和 N)
n 讀取下一行到模式空間。
N 追加下一行內容到模式空間,並以換行符\n 分隔。
1)列印匹配的下一行
# seq 5 |sed -n '/3/{n;p}'
4
2)列印偶數
# seq 6 |sed -n 'n;p'
2
4
6
sed 先讀取第一行 1,執行 n 命令,獲取下一行 2,此時模式空間是 2,執行 p 命令,列印模式空
間。 現在模式空間是 2,sed 再讀取 3,執行 n 命令,獲取下一行 4,此時模式空間為 4,執行 p 命
令,以此類推。
3)列印奇數
# seq 6 |sed 'n;d'
1
3
5
sed 先讀取第一行 1,此時模式空間是 1,並列印模式空間 1,執行 n 命令,獲取下一行 2,執行 d
命令,刪除模式空間的 2,sed 再讀取 3,此時模式空間是 3,並列印模式空間,再執行 n 命令,獲
取下一行 4,執行 d 命令,刪除模式空間的 3,以此類推。
# seq 6 |sed -n 'p;n'
1
3
5
4)每三行執行一次 p 命令
# seq 6 |sed 'n;n;p'
1
2
3
3
4
5
6
6
sed 先讀取第一行 1,並列印模式空間 1,執行 n 命令,獲取下一行 2,並列印模式空間 2,再執行 n
命令,獲取下一行 3,執行 p 命令,列印模式空間 3。sed 讀取下一行 3,並列印模式空間 3,以此類
推。
5)每三行替換一次
方法 1:
# seq 6 |sed 'n;n;s/^/=/;s/$/=/'
1
2
=3=
4
5
=6=
我們只是把 p 命令改成了替換命令。
方法 2:
這次用到了地址匹配,來實現上面的效果:
# seq 6 |sed '3~3{s/^/=/;s/$/=/}'
1
2
=3=
4
5
=6=
當執行多個 sed 命令時,有時相互會產生影響,我們可以用大括號{}把他們括起來。
6)再看下 N 命令的功能
# seq 6 |sed 'N;q'
1
2
將兩行合併一行:
# seq 6 |sed 'N;s/\n//'
12
34
56
第一個命令:sed 讀取第一行 1,N 命令讀取下一行 2,並以\n2 追加,此時模式空間是 1\n2,再執
行 q 退出。
為了進一步說明 N 的功能,看第二個命令:執行 N 命令後,此時模式空間是 1\n2,再執行把\n 替換
為空,此時模式空間是 12,並列印。
# seq 5 |sed -n N;p
1
2
3
4
# seq 6 |sed -n 'N;p'
1
2
3
4
5
6
為什麼第一個不列印 5 呢?
因為 N 命令是讀取下一行追加到 sed 讀取的當前行,當 N 讀取下一行沒有內容時,則退出,也不會
執行 p 命令列印當前行。
當行數為偶數時,N 始終就能讀到下一行,所以也會執行 p 命令。
7)列印奇數行數時的最後一行
# seq 5 |sed -n '$!N;p'
1
2
3
4
5
加一個滿足條件,當 sed 執行到最後一行時,用感嘆號不去執行 N 命令,隨後執行 p 命令。
列印和刪除模式空間第一行(P 和 D)
P 列印模式空間的第一行。
D 刪除模式空間的第一行。
1)列印奇數
# seq 6 |sed -n 'N;P'
1
3
5
2)保留最後一行
# seq 6 |sed 'N;D'
6
讀取第一行 1,執行 N 命令讀取下一行並追加到模式空間,此時模式空間是 1\n2,執行 D 命令刪除
模式空間第一行 1,剩餘 2。
讀取第二行,執行 N 命令,此時模式空間是 3\n4,執行 D 命令刪除模式空間第一行 3,剩餘 4。
以此類推,讀取最後一行列印時,而 N 獲取不到下一行則退出,不再執行 D,因此模式空間只剩餘 6
就列印。
保持空間操作(h 與 H、g 與 G 和 x)
h 複製模式空間內容到保持空間(覆蓋)。
H 複製模式空間內容追加到保持空間。
g 複製保持空間內容到模式空間(覆蓋)。
G 複製保持空間內容追加到模式空間。
模式空間與保持空間內容換
1)將匹配的內容覆蓋到另一個匹配
# seq 6 |sed -e '/3/{h;d}' -e '/5/g'
1
2
4
3
6
h 命令把匹配的 3 複製到保持空間,d 命令刪除模式空間的 3。後面命令再對模式空間匹配 5,並用
g 命令把保持空間 3 覆蓋模式空間 5。
2)將匹配的內容放到最後
# seq 6 |sed -e '/3/{h;d}' -e '$G'
1
2
4
5
6
3
3)交換模式空間和保持空間
# seq 6 |sed -e '/3/{h;d}' -e '/5/x' -e '$G'
1
2
4
3
6
5
看後面命令,在模式空間匹配 5 並將保持空間的 3 與 5 交換,5 就變成了 3,。最後把保持空間的 5
追加到模式空間的。
4)倒敘輸出
# seq 5 |sed '1!G;h;$!d'
5
4
3
2
1
分析下:
1!G 第一行不執行把保持空間內容追加到模式空間,因為現在保持空間還沒有資料。
h 將模式空間放到保持空間暫存。
$!d 最後一行不執行刪除模式空間的內容。
讀取第一行 1 時,跳過 G 命令,執行 h 命令將模式空間 1 複製到保持空間,執行 d 命令刪除模式空
間的 1。
讀取第二行 2 時,模式空間是 2,執行 G 命令,將保持空間 1 追加到模式空間,此時模式空間是
2\n1,執行 h 命令將 2\n1 覆蓋到保持空間,d 刪除模式空間。
讀取第三行 3 時,模式空間是 3,執行 G 命令,將保持空間 2\n1 追加到模式空間,此時模式空間是
3\n2\n1,執行 h 命令將模式空間內容複製到保持空間,d 刪除模式空間。
以此類推讀到第 行時模式空間是 執行 命令將保持空間的 追加模式空間
以此類推,讀到第 5 行時,模式空間是 5,執行 G 命令,將保持空間的 4\n3\n2\n1 追加模式空間,
然後複製到模式空間,5\n4\n3\n2\n1,不執行 d,模式空間保留,輸出。
由此可見,每次讀取的行先放到模式空間,再複製到保持空間,d 命令刪除模式空間內容,防止輸
出,再追加到模式空間,因為追加到模式空間,會追加到新讀取的一行的後面,迴圈這樣操作, 就
把所有行一行行追加到新讀取行的後面,就形成了倒敘。
5)每行後面新增新空行
# seq 10 |sed G
1
2
3
4
5
6)列印匹配行的上一行內容
# seq 5 |sed -n '/3/{x;p};h'
2
讀取第一行 1,沒有匹配到 3,不執行{x;p},執行 h 命令將模式空間內容 1 覆蓋到保持空間。
讀取第二行 2,沒有匹配到 3,不執行{x;p},執行 h 命令將模式空間內容 2 覆蓋到保持空間。
讀取第三行 3,匹配到 3,執行 x 命令把模式空間 3 與保持空間 2 交換,再執行 p 列印模式空間 2.
以此類推。
7)列印匹配行到最後一行或下一行到最後一行
# seq 5 |sed -n '/3/,$p'
3
4
5
# seq 5 |sed -n '/3/,${h;x;p}'
3
4
5
# seq 5 |sed -n '/3/{:a;N;$!ba;p}'
3
4
5
# seq 5 |sed -n '/3/{n;:a;N;$!ba;p}'
4
5
匹配到 3 時,n 讀取下一行 4,此時模式空間是 4,執行 N 命令讀取下一行並追加到模式空間,此時
模式空間是 4\n5,標籤迴圈完成後列印模式空間 4\n5。
標籤(:、b 和 t)
標籤可以控制流,實現分支判斷。
: lable name 定義標籤
b lable 跳轉到指定標籤,如果沒有標籤則到指令碼末尾
t lable 跳轉到指定標籤,前提是 s///命令執行成功
1)將換行符替換成逗號
方法 1:
# seq 6 |sed 'N;s/\n/,/'
1,2
3,4
5,6
這種方式並不能滿足我們的需求,每次 sed 讀取到模式空間再列印是新行,替換\n 也只能對 N 命令
追加後的 1\n2 這樣替換。
這時就可以用到標籤了:
# seq 6 |sed ':a;N;s/\n/,/;b a'
1,2,3,4,5,6
看看這裡的標籤使用,:a 是定義的標籤名,b a 是跳轉到 a 位置。
sed 讀取第
行 1,N 命令讀取下行 2,此時模式空間是 1\n2$,執行替換,此時模式空間是
1,2$,執行 b 命令再跳轉到標籤 a 位置繼續執行 N 命令,讀取下一行 3 追加到模式空間,此時模式
空間是 1,2\n3$,再替換,以此類推,不斷追加替換,直到最後一行 N 讀不到下一行內容退出。
方法 2:
# seq 6 |sed ':a;N;$!b a;s/\n/,/g'
1,2,3,4,5,6
先將每行讀入到模式空間,最後再執行全域性替換。$!是如果是最後一行,則不執行 b a 跳轉,最後
執行全域性替換。
# seq 6 |sed ':a;N;b a;s/\n/,/g'
1
2
3
4
5
6
可以看到,不加$!是沒有替換,因為迴圈到 N 命令沒有讀到行就退出了,後面的替換也就沒執行。
2)每三個數字加個一個逗號
# echo "123456789" |sed -r 's/([0-9]+)([0-9]+{3})/\1,\2/'
123456,789
# echo "123456789" |sed -r ':a;s/([0-9]+)([0-9]+{3})/\1,\2/;t a'
123,456,789
# echo "123456789" |sed -r ':a;s/([0-9]+)([0-9]+{2})/\1,\2/;t a'
1,23,45,67,89
執行第一次時,替換最後一個,跳轉後,再對 123456 匹配替換,直到匹配替換不成功,不執行 t 命
令。
7.2.12 忽略大小寫匹配(I)
# echo -e "a\nA\nb\nc" |sed 's/a/1/Ig'
b
c
獲取總行數(#)
# seq 10 |sed -n '$='
awk
awk 是一個處理文字的程式語言工具,能用簡短的程式處理標準輸入或檔案、資料排序、計算以及
生成報表等等。
在 Linux 系統下預設 awk 是 gawk,它是 awk 的 GNU 版本。可以通過命令檢視應用的版本:ls -l
/bin/awk
基本的命令語法:awk option 'pattern {action}' file
其中 pattern 表示 AWK 在資料中查詢的內容,而 action 是在找到匹配內容時所執行的一系列命令。
花括號用於根據特定的模式對一系列指令進行分組。
awk 處理的工作方式與資料庫類似,支援對記錄和欄位處理,這也是 grep 和 sed 不能實現的。
在 awk 中,預設的情況下將文字檔案中的一行視為一個記錄,逐行放到記憶體中處理,而將一行中的
某一部分作為記錄中的一個欄位。用 1,2,3...數字的方式順序的表示行(記錄)中的不同欄位。用
$後跟數字,引用對應的欄位,以逗號分隔,0 表示整個行。
選項
選項 描述
-f program-file 從檔案中讀取 awk 程式原始檔
-F fs 指定 fs 為輸入欄位分隔符
-v var=value 變數賦值
--posix 相容 POSIX 正則表示式
--dump-variables=[file] 把 awk 命令時的全域性變數寫入檔案,
預設檔案是 awkvars.out
--profile=[file] 格式化 awk 語句到檔案,預設是 awkprof.out
8.3.2 模式
常用模式有:
Pattern Description
BEGIN{ } 給程式賦予初始狀態,先執行的工作
END{ } 程式結束之後執行的一些掃尾工作
/regular expression/ 為每個輸入記錄匹配正則表示式
pattern && pattern 邏輯 and,滿足兩個模式
pattern || pattern 邏輯 or,滿足其中一個模式
! pattern 邏輯 not,不滿足模式
pattern1, pattern2 範圍模式,匹配所有模式 1 的記錄,直到匹配到模式 2
而動作呢,就是下面所講的 print、流程控制、I/O 語句等。
示例:
1)從檔案讀取 awk 程式處理檔案
# vi test.awk
{print $2}
# tail -n3 /etc/services |awk -f test.awk
48049/tcp
48128/tcp
49000/tcp
2)指定分隔符,列印指定欄位
列印第二欄位,預設以空格分隔:
# tail -n3 /etc/services |awk '{print $2}'
48049/tcp
48128/tcp
48128/udp
指定冒號為分隔符列印第一欄位:
# awk -F ':' '{print $1}' /etc/passwd
root
bin
daemon
adm
lp
sync
......
還可以指定多個分隔符,作為同一個分隔符處理:
# tail -n3 /etc/services |awk -F'[/#]' '{print $3}'
iqobject
iqobject
Matahari Broker
# tail -n3 /etc/services |awk -F'[/#]' '{print $1}'
iqobject 48619
iqobject 48619
matahari 49000
# tail -n3 /etc/services |awk -F'[/#]' '{print $2}'
tcp
udp
tcp
# tail -n3 /etc/services |awk -F'[/#]' '{print $3}'
iqobject
iqobject
Matahari Broker
# tail -n3 /etc/services |awk -F'[ /]+' '{print $2}'
48619
48619
49000
[]元字元的意思是符號其中任意個字元,也就是說每遇到個/或#時就分隔個欄位,當用多個
分隔符時,就能更方面處理欄位了。
3)變數賦值
# awk -v a=123 'BEGIN{print a}'
123
系統變數作為 awk 變數的值:
# a=123
# awk -v a=$a 'BEGIN{print a}'
123
或使用單引號
# awk 'BEGIN{print '$a'}'
123
4)輸出 awk 全域性變數到檔案
# seq 5 |awk --dump-variables '{print $0}'
1
2
3
4
5
# cat awkvars.out
ARGC: number (1)
ARGIND: number (0)
ARGV: array, 1 elements
BINMODE: number (0)
CONVFMT: string ("%.6g")
ERRNO: number (0)
FIELDWIDTHS: string ("")
FILENAME: string ("-")
FNR: number (5)
FS: string (" ")
IGNORECASE: number (0)
LINT: number (0)
NF: number (1)
NR: number (5)
OFMT: string ("%.6g")
OFS: string (" ")
ORS: string ("\n")
RLENGTH: number (0)
RS: string ("\n")
RSTART: number (0)
RT: string ("\n")
SUBSEP: string ("\034")
TEXTDOMAIN: string ("messages")
5)BEGIN 和 END
BEGIN 模式是在處理檔案之前執行該操作,常用於修改內建變數、變數賦值和列印輸出的頁首或標
題。
例如:列印頁首
# tail /etc/services |awk 'BEGIN{print "Service\t\tPort\t\t\tDescription\n==="}{print
$0}'
Service Port Description
===
3gpp-cbsp 48049/tcp # 3GPP Cell Broadcast Service
isnetserv 48128/tcp # Image Systems Network Services
isnetserv 48128/udp # Image Systems Network Services
blp5 48129/tcp # Bloomberg locator
blp5 48129/udp # Bloomberg locator
com-bardac-dw 48556/tcp # com-bardac-dw
com-bardac-dw 48556/udp # com-bardac-dw
iqobject 48619/tcp # iqobject
iqobject 48619/udp # iqobject
matahari 49000/tcp # Matahari Broker
END 模式是在程式處理完才會執行。
例如:列印頁尾
# tail /etc/services |awk '{print $0}END{print "===\nEND......"}'
3gpp-cbsp 48049/tcp # 3GPP Cell Broadcast Service
isnetserv 48128/tcp # Image Systems Network Service
isnetserv 48128/udp # Image Systems Network Service
blp5 48129/tcp # Bloomberg locator
blp5 48129/udp # Bloomberg locator
com-bardac-dw 48556/tcp # com-bardac-dw
com-bardac-dw 48556/udp # com-bardac-dw
iqobject 48619/tcp # iqobject
iqobject 48619/udp # iqobject
matahari 49000/tcp # Matahari Broker
===
END......
6)格式化輸出 awk 命令到檔案
# tail /etc/services |awk --profile 'BEGIN{print
"Service\t\tPort\t\t\tDescription\n==="}{print $0}END{print "===\nEND......"}'
Service Port Description
===
nimgtw 48003/udp # Nimbus Gateway
3gpp-cbsp 48049/tcp # 3GPP Cell Broadcast Service Protocol
isnetserv 48128/tcp # Image Systems Network Services
isnetserv 48128/udp # Image Systems Network Services
blp5 48129/tcp # Bloomberg locator
blp5 48129/udp # Bloomberg locator
com-bardac-dw 48556/tcp # com-bardac-dw
com-bardac-dw 48556/udp # com-bardac-dw
iqobject 48619/tcp # iqobject
iqobject 48619/udp # iqobject
===
END......
# cat awkprof.out
# gawk profile, created Sat Jan 7 19:45:22 2017
# BEGIN block(s)
BEGIN {
print Service\t\tPort\t\t\tDescription\n===
}
# Rule(s)
{
print $0
}
# END block(s)
END {
print "===\nEND......"
}
7)/re/正則匹配
匹配包含 tcp 的行:
# tail /etc/services |awk '/tcp/{print $0}'
3gpp-cbsp 48049/tcp # 3GPP Cell Broadcast Service
isnetserv 48128/tcp # Image Systems Network Services
blp5 48129/tcp # Bloomberg locator
com-bardac-dw 48556/tcp # com-bardac-dw
iqobject 48619/tcp # iqobject
matahari 49000/tcp # Matahari Broker
匹配開頭是 blp5 的行:
# tail /etc/services |awk '/^blp5/{print $0}'
blp5 48129/tcp # Bloomberg locator
blp5 48129/udp # Bloomberg locator
匹配第一個欄位是 8 個字元的行:
# tail /etc/services |awk '/^[a-z0-9]{8} /{print $0}'
iqobject 48619/tcp # iqobject
iqobject 48619/udp # iqobject
matahari 49000/tcp # Matahari Broker
如果沒有匹配到,請檢視你的 awk 版本(awk --version)是不是 3,因為 4 才支援{}
8)邏輯 and、or 和 not
匹配記錄中包含 blp5 和 tcp 的行:
# tail /etc/services |awk '/blp5/ && /tcp/{print $0}'
blp5 48129/tcp # Bloomberg locator
匹配記錄中包含 blp5 或 tcp 的行:
# tail /etc/services |awk '/blp5/ || /tcp/{print $0}'
3gpp-cbsp 48049/tcp # 3GPP Cell Broadcast Service
isnetserv 48128/tcp # Image Systems Network Services
blp5 48129/tcp # Bloomberg locator
blp5 48129/udp # Bloomberg locator
com-bardac-dw 48556/tcp # com-bardac-dw
iqobject 48619/tcp # iqobject
matahari 49000/tcp # Matahari Broker
不匹配開頭是#和空行:
# awk '! /^#/ && ! /^$/{print $0}' /etc/httpd/conf/httpd.conf
或
# awk '! /^#|^$/' /etc/httpd/conf/httpd.conf
或
# awk '/^[^#]|"^$"/' /etc/httpd/conf/httpd.conf
9)匹配範圍
# tail /etc/services |awk '/^blp5/,/^com/'
blp5 48129/tcp # Bloomberg locator
blp5 48129/udp # Bloomberg locator
com-bardac-dw 48556/tcp # com-bardac-dw
對匹配範圍後記錄再次處理,例如匹配關鍵字下一行到最後一行:
# seq 5 |awk '/3/,/^$/{printf /3/?"":$0"\n"}'
4
5
另一種判斷真假的方式實現:
# seq 5 |awk '/3/{t=1;next}t'
4
5
1 和 2 都不匹配 3,不執行後面{},執行 t,t 變數還沒賦值,為空,空在 awk 中就為假,就不列印
當前行。匹配到 3,執行 t=1,next 跳出,不執行 t。4 也不匹配 3,執行 t,t 的值上次賦值的 1,
為真,列印當前行,以此類推。(非 0 的數字都為真,所以 t 可以寫任意非 0 數字)
如果想列印匹配行都最後一行,就可以這樣了:
# seq 5 |awk '/3/{t=1}t'
3
4
5
內建變數
變數名 描述
FS 輸入欄位分隔符,預設是空格或製表符
OFS 輸出欄位分隔符,預設是空格
RS 輸入記錄分隔符,預設是換行符\n
ORS 輸出記錄分隔符,預設是換行符\n
NF 統計當前記錄中欄位個數
NR 統計記錄編號,每處理一行記錄,編號就會+1
FNR 統計記錄編號,每處理一行記錄,編號也會+1,與 NR 不同的是,處理
檔案時,編號會重新計數。
ARGC 命令列引數數量
ARGV 命令列引數陣列序列陣列,下標從 0 開始,ARGV[0]是 awk
ARGIND 當前正在處理的檔案索引值。第一個檔案是 1,第二個檔案是 2,以此
ENVIRON 當前系統的環境變數
FILENAME 輸出當前處理的檔名
IGNORECASE 忽略大小寫
SUBSEP 陣列中下標的分隔符,預設為"\034"
示例:
1)FS 和 OFS
在程式開始前重新賦值 FS 變數,改變預設分隔符為冒號,與-F 一樣。
#awk 'BEGIN{FS=":"}{print $1,$2}' /etc/passwd |head -n5
root x
bin x
daemon x
adm x
lp x
也可以使用-v 來重新賦值這個變數:
# awk -vFS=':' '{print $1,$2}' /etc/passwd |head -n5 # 中間逗號被換成了 OFS 的默
認值
root x
bin x
daemon x
adm x
lp x
由於 OFS 預設以空格分隔,反向引用多個欄位分隔的也是空格,如果想指定輸出分隔符這樣:
# awk 'BEGIN{FS=":";OFS=":"}{print $1,$2}' /etc/passwd |head -n5
root:x
bin:x
daemon:x
adm:x
lp:x
也可以通過字串拼接實現分隔:
# awk 'BEGIN{FS=":"}{print $1"#"$2}' /etc/passwd |head -n5
root#x
bin#x
daemon#x
adm#x
lp#x
2)RS 和 ORS
RS 預設是\n 分隔每行,如果想指定以某個字元作為分隔符來處理記錄:
# echo "www.baidu.com/user/test.html" |awk 'BEGIN{RS="/"}{print $0}'
www.baidu.com
user
test.html
RS 也支援正則,簡單演示下:
# seq -f "str%02g" 10 |sed 'n;n;a\-----' |awk 'BEGIN{RS="-+"}{print $1}'
str01
str04
str07
str10
將輸出的換行符替換為+號:
# seq 10 |awk 'BEGIN{ORS="+"}{print $0}'
1+2+3+4+5+6+7+8+9+10+
替換某個字元:
# tail -n2 /etc/services |awk 'BEGIN{RS="/";ORS="#"}{print $0}'
iqobject 48619#udp # iqobject
matahari 49000#tcp # Matahari Broker
3)NF
NF 是欄位個數。
# echo "a b c d e f" |awk '{print NF}'
6
列印最後一個欄位:
# echo "a b c d e f" |awk '{print $NF}'
f
列印倒數第二個欄位:
# echo "a b c d e f" |awk '{print $(NF-1)}'
e
排除最後兩個欄位:
# echo "a b c d e f" |awk '{$NF="";$(NF-1)="";print $0}'
a b c d
排除第一個欄位:
# echo "a b c d e f" |awk '{$1="";print $0}'
b c d e f
4)NR 和 FNR
NR 統計記錄編號,每處理一行記錄,編號就會+1,FNR 不同的是在統計第二個檔案時會重新計數。
列印行數:
# tail -n5 /etc/services |awk '{print NR,$0}'
1 com-bardac-dw 48556/tcp # com-bardac-dw
2 com-bardac-dw 48556/udp # com-bardac-dw
3 iqobject 48619/tcp # iqobject
4 iqobject 48619/udp # iqobject
5 matahari 49000/tcp # Matahari Broker
列印總行數:
# tail -n5 /etc/services |awk 'END{print NR}'
5
列印第三行:
# tail -n5 /etc/services |awk 'NR==3'
iqobject 48619/tcp # iqobject
列印第三行第二個欄位:
# tail -n5 /etc/services |awk 'NR==3{print $2}'
48619/tcp
列印前三行:
# tail -n5 /etc/services |awk 'NR<=3{print NR,$0}'
1 com-bardac-dw 48556/tcp # com-bardac-dw
2 com-bardac-dw 48556/udp # com-bardac-dw
3 iqobject 48619/tcp # iqobject
看下 NR 和 FNR 的區別:
# cat a
a
b
c
# cat b
c
d
e
# awk '{print NR,FNR,$0}' a b
1 1 a
2 2 b
3 3 c
4 1 c
5 2 d
6 3 e
可以看出 NR 每處理一行就會+1,而 FNR 在處理第二個檔案時,編號重新計數。同時也知道 awk 處理
兩個檔案時,是合併到一起處理。
# awk 'FNR==NR{print $0"1"}FNR!=NR{print $0"2"}' a b
a1
b1
c1
c2
d2
e2
當 FNR==NR 時,說明在處理第一個檔案內容,不等於時說明在處理第二個檔案內容。
一般 FNR 在處理多個檔案時會用到,下面會講解。
5)ARGC 和 ARGV
ARGC 是命令列引數數量
ARGV 是將命令列引數存到陣列,元素由 ARGC 指定,陣列下標從 0 開始
# awk 'BEGIN{print ARGC}' 1 2 3
4
# awk 'BEGIN{print ARGV[0]}'
awk
# awk 'BEGIN{print ARGV[1]}' 1 2
1
# awk 'BEGIN{print ARGV[2]}' 1 2
2
6)ARGIND
ARGIND 是當前正在處理的檔案索引值,第一個檔案是 1,第二個檔案是 2,以此類推,從而可以通
過這種方式判斷正在處理哪個檔案。
# awk '{print ARGIND,$0}' a b
1 a
1 b
1 c
2 c
2 d
2 e
# awk 'ARGIND==1{print "a->"$0}ARGIND==2{print "b->"$0}' a b
a->a
a->b
a->c
b->c
b->d
b->e
7)ENVIRON
ENVIRON 呼叫系統變數。
# awk 'BEGIN{print ENVIRON["HOME"]}'
/root
如果是設定的環境變數,還需要用 export 匯入到系統變數才可以呼叫:
# awk 'BEGIN{print ENVIRON["a"]}'
# export a
# awk 'BEGIN{print ENVIRON["a"]}'
123
8)FILENAME
FILENAME 是當前處理檔案的檔名。
# awk 'FNR==NR{print FILENAME"->"$0}FNR!=NR{print FILENAME"->"$0}' a b
a->a
a->b
a->c
b->c
b->d
b->e
9)忽略大小寫
# echo "A a b c" |xargs -n1 |awk 'BEGIN{IGNORECASE=1}/a/'
A
a
等於 1 代表忽略大小寫。
操作符
運算子 描述
(....) 分組
$ 欄位引用
++ -- 遞增和遞減
+ - ! 加號,減號,和邏輯否定
* / % 乘,除和取餘
+ - 加法,減法
| |& 管道,用於 getline,print 和 printf
< > <= >= != == 關係運算符
~ !~ 正則表示式匹配,否定正則表示式匹配
in 陣列成員
&& || 邏輯 and,邏輯 or
?: 簡寫條件表示式:
expr1 ? expr2 : expr3
第一個表示式為真,執行 expr2,否則執行 expr3
= += -= *= /= %= ^= 變數賦值運算子
須知:
在 awk 中,有 3 種情況表示式為假:數字是 0,空字串和未定義的值。
數值運算,未定義變數初始值為 0。字元運算,未定義變數初始值為空。
舉例測試:
# awk 'BEGIN{n=0;if(n)print "true";else print "false"}'
false
# awk 'BEGIN{s="";if(s)print "true";else print "false"}'
false
# awk 'BEGIN{if(s)print "true";else print "false"}'
false
示例:
1)擷取整數
# echo "123abc abc123 123abc123" |xargs -n1 | awk '{print +$0}'
123
0
123
# echo "123abc abc123 123abc123" |xargs -n1 | awk '{print -$0}'
-123
0
-123
2)感嘆號
列印奇數行:
# seq 6 |awk 'i=!i'
1
3
5
列印偶數行:
# seq 6 |awk '!(i=!i)'
2
4
6
讀取第行:i 是未定義變數,也就是 i!0,!取反意思。感嘆號右邊是個布林值,0 或空字串為假,非 0 或非空字串為真,!0 就是真,因此 i=1,條件為真列印當前記錄。
沒有 print 為什麼會列印呢?因為模式後面沒有動作,預設會列印整條記錄。
讀取第二行:因為上次 i 的值由 0 變成了 1,此時就是 i=!1,條件為假不列印。
讀取第三行:上次條件又為假,i 恢復初始值 0,取反,繼續列印。以此類推...
可以看出,運算時並沒有判斷行內容,而是利用布林值真假判斷輸出當前行。
2)不匹配某行
# tail /etc/services |awk '!/blp5/{print $0}'
3gpp-cbsp 48049/tcp # 3GPP Cell Broadcast Service
isnetserv 48128/tcp # Image Systems Network Services
isnetserv 48128/udp # Image Systems Network Services
com-bardac-dw 48556/tcp # com-bardac-dw
com-bardac-dw 48556/udp # com-bardac-dw
iqobject 48619/tcp # iqobject
iqobject 48619/udp # iqobject
matahari 49000/tcp # Matahari Broker
3)乘法和除法
# seq 5 |awk '{print $0*2}'
2
4
6
8
10
# seq 5 |awk '{print $0%2}'
1
0
1
0
1
列印偶數行:
# seq 5 |awk '$0%2==0{print $0}'
2
4
列印奇數行:
# seq 5 |awk '$0%2!=0{print $0}'
1
3
5
4)管道符使用
# seq 5 |shuf |awk '{print $0|"sort"}
1
2
3
4
5
5)正則表示式匹配
# seq 5 |awk '$0~3{print $0}'
3
# seq 5 |awk '$0!~3{print $0}'
1
2
4
5
# seq 5 |awk '$0~/[34]/{print $0}'
3
4
# seq 5 |awk '$0!~/[34]/{print $0}'
1
2
5
# seq 5 |awk '$0~/[^34]/{print $0}'
1
2
5
6)判斷陣列成員
# awk 'BEGIN{a["a"]=123}END{if("a" in a)print "yes"}' </dev/null
yes
7)三目運算子
# awk 'BEGIN{print 1==1?"yes":"no"}' # 三目運算作為一個表示式,裡面不允許寫 print
yes
# seq 3 |awk '{print $0==2?"yes":"no"}'
no
yes
no
替換換行符為逗號:
# seq 5 |awk '{print n=(n?n","$0:$0)}'
1
1,2
1,2,3
1,2,3,4
1,2,3,4,5
# seq 5 |awk '{n=(n?n","$0:$0)}END{print n}'
1,2,3,4,5
說明:讀取第一行時,n 沒有變數,為假輸出$0 也就是 1,並賦值變數 n,讀取第二行時,n 是 1 為
真,輸出 1,2 以此類推,後面會一直為真。
每三行後面新增新一行:
# seq 10 |awk '{print NR%3?$0:$0 "\ntxt"}'
1
2
3
txt
4
5
6
txt
7
8
9
txt
10
在
兩行合併一行:
# seq 6 |awk '{printf NR%2!=0?$0" ":$0" \n"}'
1 2
3 4
5 6
# seq 6 |awk 'ORS=NR%2?" ":"\n"'
1 2
3 4
5 6
# seq 6 |awk '{if(NR%2)ORS=" ";else ORS="\n";print}'
8)變數賦值
欄位求和:
# seq 5 |awk '{sum+=1}END{print sum}'
5
# seq 5 |awk '{sum+=$0}END{print sum}'
15
流程控制
1)if 語句
格式:if (condition) statement [ else statement ]
單分支:
# seq 5 |awk '{if($0==3)print $0}'
3
也支援正則匹配判斷,一般在寫複雜語句時使用:
# echo "123abc#456cde 789aaa#aaabbb " |xargs -n1 |awk -F# '{if($2~/[0-9]/)print $2}'
456cde
# echo "123abc#456cde 789aaa#aaabbb " |xargs -n1 |awk -F# '{if($2!~/[0-9]/)print $2}'
aaabbb
或
# echo "123abc#456cde 789aaa#aaabbb" |xargs -n1 |awk -F# '$2!~/[0-9]/{print $2}'
aaabbb
雙分支:
# seq 5 |awk '{if($0==3)print $0;else print "no"}'
no
no
3
no
no
多分支:
# cat file
1 2 3
4 5 6
7 8 9
# awk '{if($1==4){print "1"} else if($2==5){print "2"} else if($3==6){print "3"} else
{print "no"}}' file
no
1
no
2)while 語句
格式:while (condition) statement
遍歷列印所有欄位:
# awk '{i=1;while(i<=NF){print $i;i++}}' file
1
2
3
4
5
6
7
8
9
awk 是按行處理的,每次讀取一行,並遍歷列印每個欄位。
3)for 語句 C 語言風格
格式:for (expr1; expr2; expr3) statement
遍歷列印所有欄位:
# cat file
1 2 3
4 5 6
7 8 9
# awk '{for(i=1;i<=NF;i++)print $i}' file
1
2
3
4
5
6
7
8
9
倒敘列印文字:
# awk '{for(i=NF;i>=1;i--)print $i}' file
3
2
1
6
5
4
9
8
7
都換行了,這並不是我們要的結果。怎麼改進呢?
# awk '{for(i=NF;i>=1;i--){printf $i" "};print ""}' file # print 本身就會新列印一行
3 2 1
6 5 4
9 8 7
或
# awk '{for(i=NF;i>=1;i--)if(i==1)printf $i"\n";else printf $i" "}' file
3 2 1
6 5 4
6 5 4
9 8 7
在這種情況下,是不是就排除第一行和倒數第一行呢?我們正序列印看下
排除第一行:
# awk {for(i=2;i<=NF;i++){printf $i};print }file
2 3
5 6
8 9
排除第二行:
# awk '{for(i=1;i<=NF-1;i++){printf $i" "};print ""}' file
1 2
4 5
7 8
IP 加單引號:
# echo '10.10.10.1 10.10.10.2 10.10.10.3' |awk '{for(i=1;i<=NF;i++)printf
"\047"$i"\047"}
'10.10.10.1' '10.10.10.2' '10.10.10.3'
\047 是 ASCII 碼,可以通過 showkey -a 命令檢視。
4)for 語句遍歷陣列
格式:for (var in array) statement
# seq -f "str%.g" 5 |awk '{a[NR]=$0}END{for(v in a)print v,a[v]}'
4 str4
5 str5
1 str1
2 str2
3 str3
5)break 和 continue 語句
break 跳過所有迴圈,continue 跳過當前迴圈。
# awk 'BEGIN{for(i=1;i<=5;i++){if(i==3){break};print i}}'
1
2
# awk 'BEGIN{for(i=1;i<=5;i++){if(i==3){continue};print i}}'
1
2
4
5
6)刪除陣列和元素
格式:
delete array[index] 刪除陣列元素
delete array 刪除陣列
# seq -f "str%.g" 5 |awk '{a[NR]=$0}END{delete a;for(v in a)print v,a[v]}'
空的…
# seq -f "str%.g" 5 |awk '{a[NR]=$0}END{delete a[3];for(v in a)print v,a[v]}'
4 str4
5 str5
1 str1
2 str2
7)exit 語句
格式:exit [ expression ]
exit 退出程式,與 shell 的 exit 一樣。[ expr ]是 0-255 之間的數字。
# seq 5 |awk '{if($0~/3/)exit (123)}'
# echo $?
123
陣列
陣列:儲存一系列相同型別的元素,鍵/值方式儲存,通過下標(鍵)來訪問值。
awk 中陣列稱為關聯陣列,不僅可以使用數字作為下標,還可以使用字串作為下標。
陣列元素的鍵和值儲存在 awk 程式內部的一個表中,該表採用雜湊演算法,因此陣列元素是隨機排
序。
陣列格式:array[index]=value
1)自定義陣列
# awk 'BEGIN{a[0]="test";print a[0]}'
test
2)通過 NR 設定記錄下標,下標從 1 開始
# tail -n3 /etc/passwd |awk -F: '{a[NR]=$1}END{print a[1]}'
systemd-network
# tail -n3 /etc/passwd |awk -F: '{a[NR]=$1}END{print a[2]}'
zabbix
# tail -n3 /etc/passwd |awk -F: '{a[NR]=$1}END{print a[3]}'
user
3)通過 for 迴圈遍歷陣列
# tail -n5 /etc/passwd |awk -F: '{a[NR]=$1}END{for(v in a)print a[v],v}'
zabbix 4
user 5
admin 1
systemd-bus-proxy 2
systemd-network 3
# tail -n5 /etc/passwd |awk -F: '{a[NR]=$1}END{for(i=1;i<=NR;i++)print a[i],i}'
admin 1
systemd-bus-proxy 2
systemd-network 3
zabbix 4
user 5
上面列印的 i 是陣列的下標。
第一種 for 迴圈的結果是亂序的,剛說過,陣列是無序儲存。
第二種 for 迴圈通過下標獲取的情況是排序正常。
所以當下標是數字序列時,還是用 for(expr1;expr2;expr3)迴圈表示式比較好,保持順序不變。
4)通過++方式作為下標
# tail -n5 /etc/passwd |awk -F: '{a[x++]=$1}END{for(i=0;i<=x-1;i++)print a[i],i}'
admin 0
systemd-bus-proxy 1
systemd-network 2
zabbix 3
user 4
x 被 awk 初始化值是 0,沒迴圈次1
5)使用欄位作為下標
# tail -n5 /etc/passwd |awk -F: '{a[$1]=$7}END{for(v in a)print a[v],v}'
/sbin/nologin admin
/bin/bash user
/sbin/nologin systemd-network
/sbin/nologin systemd-bus-proxy
/sbin/nologin zabbix
6)統計相同欄位出現次數
# tail /etc/services |awk '{a[$1]++}END{for(v in a)print a[v],v}'
2 com-bardac-dw
1 3gpp-cbsp
2 iqobject
1 matahari
2 isnetserv
2 blp5
# tail /etc/services |awk '{a[$1]+=1}END{for(v in a)print a[v],v}'
2 com-bardac-dw
1 3gpp-cbsp
2 iqobject
1 matahari
2 isnetserv
2 blp5
# tail /etc/services |awk '/blp5/{a[$1]++}END{for(v in a)print a[v],v}'
2 blp5
第一個欄位作為下標,值被++初始化是 0,每次遇到下標(第一個欄位)一樣時,對應的值就會被+1,因此實現了統計出現次數。
想要實現去重的的話就簡單了,只要列印下標即可。
7)統計 TCP 連線狀態
# netstat -antp |awk '/^tcp/{a[$6]++}END{for(v in a)print a[v],v}'
9 LISTEN
6 ESTABLISHED
6 TIME_WAIT
8)只打印出現次數大於等於 2 的
# tail /etc/services |awk '{a[$1]++}END{for(v in a) if(a[v]>=2){print a[v],v}}'
2 com-bardac-dw
2 iqobject
2 isnetserv
2 blp5
9)去重
只打印重複的行:
# tail /etc/services |awk 'a[$1]++'
isnetserv 48128/udp # Image Systems Network Services
blp5 48129/udp # Bloomberg locator
com-bardac-dw 48556/udp # com-bardac-dw
iqobject 48619/udp # iqobject
不列印重複的行:
# tail /etc/services |awk '!a[$1]++'
3gpp-cbsp 48049/tcp # 3GPP Cell Broadcast Service
isnetserv 48128/tcp # Image Systems Network Services
blp5 48129/tcp # Bloomberg locator
com-bardac-dw 48556/tcp # com-bardac-dw
iqobject 48619/tcp # iqobject
matahari 49000/tcp # Matahari Broker
先明白一個情況,當值是 0 是為假,非 0 整數為真,知道這點就不難理解了。
只打印重複的行說明:當處理第一條記錄時,執行了++,初始值是 0 為假,就不列印,如果再遇到
相同的記錄,值就會+1,不為 0,則列印。
不列印重複的行說明:當處理第一條記錄時,執行了++,初始值是 0 為假,感嘆號取反為真,打
印,如果再遇到相同的記錄,值就會+1,不為 0 為真,取反為假就不列印。
# tail /etc/services |awk '{if(a[$1]++)print $1}'
isnetserv
blp5
com-bardac-dw
iqobject
使用三目運算:
# tail /etc/services |awk
{print a[$1]++?$1:no}
no
no
isnetserv
no
blp5
no
com-bardac-dw
no
iqobject
no
# tail /etc/services |awk '{if(!a[$1]++)print $1}'
3gpp-cbsp
isnetserv
blp5
com-bardac-dw
iqobject
matahari
10)統計每個相同欄位的某欄位總數:
# tail /etc/services |awk -F'[ /]+' '{a[$1]+=$2}END{for(v in a)print v, a[v]}'
com-bardac-dw 97112
3gpp-cbsp 48049
iqobject 97238
matahari 49000
isnetserv 96256
blp5 96258
11)多維陣列
awk 的多維陣列,實際上 awk 並不支援多維陣列,而是邏輯上模擬二維陣列的訪問方式,比如
a[a,b]=1,使用 SUBSEP(預設\034)作為分隔下標欄位,儲存後是這樣 a\034b。
示例:
# awk 'BEGIN{a["x","y"]=123;for(v in a) print v,a[v]}'
xy 123
我們可以重新複製 SUBSEP 變數,改變下標預設分隔符:
# awk 'BEGIN{SUBSEP=":";a["x","y"]=123;for(v in a) print v,a[v]}'
x:y 123
根據指定的欄位統計出現次數:
# cat file
A 192.168.1.1 HTTP
B 192.168.1.2 HTTP
B 192.168.1.2 MYSQL
C 192.168.1.1 MYSQL
C 192.168.1.1 MQ
D 192.168.1