vi/vim 的世界裡多了兩個兄弟:sed 和 gawk.

sed: stream editor. 在編輯器處理數據之前,根據事先提供的規則來編輯數據流。

sed 有點類似於 Kafka, 對數據進行一行一行的編輯,行雲流水,沒有半點拖沓。當然 kafka 更加強大,實時捕獲數據,還能指定更複雜的處理邏輯,並且能夠發送到任何地方保存起來。sed 能接受的源頭是文本文件,最終的結果還只能是普通的文本文件,實現的是文本到文本的轉換。

sed 的命令格式:

sed options script file

詳細的文檔見這裡:

gnu.org/software/sed/ma

常用的 3 個可選參數列在這裡:

sed -e s/cat/dog/ logfile.txt
sed -f Wordchange.sed logfile.txt
sed -e s/cat/dog -i logfile.txt

-e : 指定命令表達式,s/cat/dog 用 dog 取代文本中的 cat ;

-f : 如果有多行表達式,且頻繁修改,用文件存儲這些命令表達式則顯得尤為高效,而 -f 代表的就是命令文件;-i: in-place 就地修改並保存。如果不指定 sed 將修改後的結果輸出到標準輸出也就是屏幕上

主要是圍繞著 script 做文章,指定的命令可以完成目標文本的轉換, 而 options 則更多是一些可選的動作,比如直接修改文本而不是保存,指定多命令的文件等等。

革命要勝利,路線不能歪。所以首先要攻克的便是 script 命令。這裡有份指南,從簡到深,細細鋪開來講。

? sed script overview: sed script overview
? sed commands list: sed commands summary
? The "s" Command: sed』s Swiss Army Knife
? Common Commands: Often used commands
? Other Commands: Less frequently used commands
? Programming Commands: Commands for sed gurus
? Extended Commands: Commands specific of GNU sed
? Multiple commands syntax: Extension for easier scripting

sed 命令腳本綜述

[line address]X[options]

多命令也好,單行命令也好,多行命令也好,命令文件也罷,命令的格式逃不過上面這公式。

line address 是文本的行數範圍,比如指定文本的 30 到 50 行,30,50;

X 是單字命令,夠簡單,但是不好記。隨時備份一張 X 的列表在你的桌面上,或許能幫你隨用隨查;options 就是單字命令的可選參數

sed -e /^foo/d -e s/hello/world/ input.txt > output.txt

echo s/hello/world/ > script2.sed
sed -e /^foo/d -f script2.sed input.txt > output.txt

/^foo 比指定行數(每一行在文本文件中總有一個行號)要來的靈活,^foo代表的就是開頭以foo的那些行;

/d 標識命令是 delete, 即刪除行的操作;-e, -f, 都可以多次引用,其作用就是為了指定多個命令

sed 常用命令

可選的命令太多了,所以還是挑幾個常用的命令來講講

Swiss Army Knife 瑞士軍刀 - s

[root@centos00 _data]# echo this is a cat dog | sed -e s/cat/fat/
this is a fat dog
[root@centos00 _data]#

s 這單字命令,一定要嚴格按照格式:

s/original word/replaced word/

來編寫,否則出現會這種錯誤:

sed: -e expression #1, char 9: unterminated `s command

其他常用命令介紹

{#;d;q;p;n}

q - quit 在當前行退出(當前處理的文件),不再處理更多往下的行

[root@centos00 _data]# seq 5
1
2
3
4
5
[root@centos00 _data]# seq 5 | sed 3q
1
2
3
[root@centos00 _data]#

seq 是 sequence 命令,產生一組序列值;

3q 是 sed 單字命令應用,3 代表第三行,而 q 就是退出

d - delete 是刪除滿足條件的行,可以指定行號也可以使用條件表達式

[root@centos00 _data]# seq 5 | sed 3d
1
2
4
5
[root@centos00 _data]#

p - print 列印當前行,必須與 sed -n 可選參數同時使用,才奏效

[root@centos00 _data]# seq 5 | sed 3p
1
2
3
3
4
5
[root@centos00 _data]# seq 5 | sed 3p -n
3
[root@centos00 _data]#

-n 作為 sed 的可選參數,沒有在文檔中找到其原意,我暫 YY 它是 no print 的意思。

n - Next line , 隔行處理。指定多少個 n, 就隔多少行處理一次編輯

[root@centos00 _data]# seq 5 | sed n;s/./new line/
1
new line
3
new line
5
[root@centos00 _data]#

[root@centos00 _data]# seq 5 | sed n;n;s/./new line/
1
2
new line
4
5
[root@centos00 _data]#

{#;d;q;p;n} - 命令組合符號{;}

剛才那一案例已經說明白了 n;n;s/./new line/ , 使用「;」即可將多個命令同時作用於一行上,而如果要作用於滿足條件的行,則必須加上「{}」:

[root@centos00 _data]# seq 5 | sed -n 2{s/./new line/;p}
new line
[root@centos00 _data]#

看完這些例子,不禁令我想到一個問題,在單字命令表達式

[line address]X[options]中,line address 可以是數字型的行號,也可以是滿足條件的行號。而什麼樣的條件可以被放在[line address]表達式中呢?

好比,我需要列印偶數行,表達式該怎麼寫?

固然,用 n 命令可以解決這個問題,但我們考察的是[line address]的用法

# 使用單字命令:

[root@centos00 _data]# seq 8 | sed -n {n;p}
2
4
6
8
[root@centos00 _data]#

使用~可以實現列印隔行的功能:

[root@centos00 _data]# seq 8 | sed -n 0~2p
2
4
6
8

而[line address]還可以使用正則表達式:

[root@centos00 _data]# seq 20 | sed -n /[2]/p
2
12
20
[root@centos00 _data]#

/regular express/ 是正確引用正則表達式的方法,這裡僅僅是列印包含2字元的那些行。

在 IT 領域,僅看理論而不動手,「學而不練」則惘。就像筆者一樣,在玩 Oracle 那段時間天天用著,還蠻熟練的,中途轉 SQL Server 做了幾年,回頭再用 sed 卻手生得緊。拳不離手,曲不離口,文不離碼,一點沒錯。

有 20 道題,是從文檔上看到的,做下筆記,方便日後查閱

  • 1 Joining lines
  • 2 Centering Lines
  • 3 Increment a Number
  • 4 Rename Files to Lower Case
  • 5 Print bash Environment
  • 6 Reverse Characters of Lines
  • 7 Text search across multiple lines
  • 8 Line length adjustment
  • 9 Reverse Lines of Files
  • 10 Numbering Lines
  • 11 Numbering Non-blank Lines
  • 12 Counting Characters
  • 13 Counting Words
  • 14 Counting Lines
  • 15 Printing the First Lines
  • 16 Printing the Last Lines
  • 17 Make Duplicate Lines Unique
  • 18 Print Duplicated Lines of Input
  • 19 Remove All Duplicated Lines
  • 20 Squeezing Blank Lines

awk 簡史:

The name awk comes from the initials of its designers: Alfred V. Aho, Peter J. Weinberger, and Brian W. Kernighan. The original version of awk was written in 1977 at AT&T Bell Laboratories.

gawk 簡史:

Paul Rubin wrote gawk in 1986. Jay Fenlason completed it, with advice from Richard Stallman. John Woods contributed parts of the code as well. In 1988 and 1989, David Trueman, thoroughly reworked gawk for compatibility with the newer awk.

gawk 是 awk 的 GNU 版本。是一個功能更加強大的具有編程擴展性的工具。

gawk 的命令格式

gawk options program file

簡單的對 options 做個介紹:

-F fs : 指定航中分割數據欄位的欄位分隔符

-f file: 指定讀取程序的文件名。多行命令可以放在一個文件以便復用

-v var=value: 指定 gawk 程序中一個變數以及其默認值
-mf N: 指定要處理的數據文件中的最大欄位數
-mr N: 指定數據文件中的最大數據行數
-W keyword: 指定 gawk 的兼容模式或警告等級

gawk 的命令編程

gawk 的中心是其命令,可以有兩種方式來調用命令

命令行的調用方式;

將多行命令編寫在文件的調用方式

命令行的調用方式:

[root@centos00 _data]# gawk {print "hello, world!"}
_

要注意的是兩點:

{} 成為 gawk 的固定格式,{} 是放置 gawk 命令的地方,而 是將命令當做字元串與其他選項或參數字元串隔離的分隔符。

[root@centos00 _data]# gawk print "hello, world!"
gawk: cmd. line:1: print "hello, world!"
gawk: cmd. line:1: ^ syntax error

[root@centos00 _data]# gawk {print "hello, world!"}
bash: !"}: event not found
[root@centos00 _data]#

gawk 的默認從標準輸入流來讀文本,如果沒有指定文本文件,那就等待標準輸入流的輸入:

[root@centos00 _data]# gawk {print "hello, world!"}
this a hello world programm
hello, world!

多條命令也可以寫在一行中處理,使用「;」分隔符即可:

[root@centos00 _data]# gawk -F: { $6 = $1 ":" $6 ; print $1 "s home director is " $6 } /etc/passwd
roots home director is root:/root
bins home director is bin:/bin
daemons home director is daemon:/sbin
adms home director is adm:/var/adm
lps home director is lp:/var/spool/lpd
syncs home director is sync:/sbin

對單引號""做轉義的時候,使用兩次單引號即可,而不是使用"".

gawk 的功能也是對每行輸入做處理。

將多行命令編寫在文件的調用方式

[root@centos00 _data]# gawk -F: -f getUserDirectory.awk /etc/passwd
[root@centos00 _data]# cat getUserDirectory.awk
{print $1 "s home directory is " $6 }
[root@centos00 _data]# gawk -F: -f getUserDirectory.awk /etc/passwd
roots home directory is /root
bins home directory is /bin
daemons home directory is /sbin
adms home directory is /var/adm
lps home directory is /var/spool/lpd

多行命令寫在文件中:

[root@centos00 _data]# cat getUserDirectory.awk
{$6 = $1 ":" $6 }
{print $1 "s home directory is " $6 }

[root@centos00 _data]# gawk -F: -f getUserDirectory.awk /etc/passwd
roots home directory is root:/root
bins home directory is bin:/bin
daemons home directory is daemon:/sbin

依然是用{} 來引用命令,但不需要來分割命令字元串了

$1,$2,$3,$n 是標識被-F 指定的欄位分隔符分割的欄位值。

$1 就是輸入文件中的第一欄

gawk 的重頭戲 - 「命令」武器庫

編程思路大家都沒大問題,針對 gawk 首先要了解的是他的武器庫,到底藏了哪些寶貝

AK-47,可靠精良,擅長短距離橫掃;

M16,輕便成熟,火力猛;

沙漠之鷹,更符合隨身細帶的要求。

因此依據作戰規模,我們需要選好手上的武器:

循環

條件變數

操作函數

變數

變數又可分為「內建變數」和「自定義變數」

內建變數

舉個例子來說明:

[root@centos00 _data]# gawk -F: { $6 = $1 ":" $6 ; print $1 "s home director is " $6 } /etc/passwd
roots home director is root:/root
bins home director is bin:/bin
daemons home director is daemon:/sbin

這裡的 $6 就是內建變數,看得出來,欄位索引內建變數需要用 $ 來標記。而 $6 的 6 就是指文本文件中的第 6 列,以此類推,$7 就是第 7 列,且取出來之後,可以對 $6 , $7 做變更。

那麼問題就來了:

是否能將內建變數取出來的值,做修改,再傳回源文件做保存呢?

除了 $n ( n 指代 1,2,3,4,5,6...等自然數)之外,還有一些內建變數:

FS: 輸入欄位分隔符

RS: 輸入數據行分隔符OFS: 輸出欄位分隔符ORS: 輸出數據行分隔符

[root@centos00 _data]# echo 32:31:33|gawk BEGIN {FS=":";OFS="|"} {print $1,$2,$3}
32|31|33

ENVIRON 也是比較有意思的內建變數用法:

[root@centos00 _data]# gawk BEGIN{ print ENVIRON["PATH"] }
/root/perl5/bin:/usr/lib64/qt-3.3/bin:/home/huangyun/perl5/bin:/usr/local/bin:
更多 gawk 內建的變數,參考文檔:

ftp://ftp.gnu.org/old-gnu/Manuals/gawk-3.0.3/html_chapter/gawk_11.html

https://www.gnu.org/software/gawk/manual/gawk.html#SEC_Contents

自定義變數

無法想像只有內建變數的編程世界會是怎麼樣,大概會像少了插件那樣去用 visual studio code 吧,很無助。所以自定義變數是肯定會支持的

[root@centos00 _data]# gawk {Greetings="hello world";print Greetings}
d
hello world
^C
[root@centos00 _data]# gawk BEGIN{Greetings="hello world";print Greetings}
hello world
[root@centos00 _data]#

Greetings 是自定義的變數,第一個例子很有趣,沒有BEGIN 指令,gawk 始終是在等待輸入。

自定義變數之數組

數組在 gawk 中的使用,更像是 K-V 對:

[root@centos00 _data]# gawk BEGIN{
Prize["One"]="house"
Prize["Two"]="iphoneX"
for( prize in Prize)
{
print Prize[prize]
}
}
iphoneX
house

變數在程序結構中是區分大小寫的!是區分大小寫的!是區分大小寫的!

結構化命令 (條件,循環)

# if 條件:

if(condition)
statement
# while 循環

while(condition)
{
statements
}
# do while 循環

do
{
statements
}
while(condition)
# for 循環

# c語言風格
for(i=1;i<4;i++)
{
total += $i
}

# python 式

for( prize in Prize)
{
print Prize[prize]
}

已知每行欄位總數可以用內建變數 NF 標識,據此可以列印出每個欄位的值:

[root@centos00 _data]# cat testData.txt
123 345 567 789
111 222 333 444
333 555 666 777
[root@centos00 _data]# gawk {for(i=1;i<=NF;i++){ print $i } } testData.txt
123
345
567
789
111
222
333
444
333
555
666
777
[root@centos00 _data]#

操作函數

跟變數一樣,可分「內建函數」和「自定義函數」

內建函數參考文檔即可,不需要太多的解釋。

我們該關心的還是自定義函數。

自定義函數的格式

[root@centos00 _data]# gawk
> function getRand(myRand)
> {
> print myRand*rand()
> }
> BEGIN{
> print getRand(4)
> }
0.95115

[root@centos00 _data]#

所有的自定義函數一定是放在 BEGIN 之前,或者說程序的開頭。

但馬上就會有問題的是,函數必須重用。重複發明不必要的輪子,是低效的。因此 gawk 給出了函數庫這個概念。

將所有的自定義函數歸檔到函數庫中,在使用個別函數時候,只要引用這個庫就可以了。相信 Java 朋友不陌生,Python 朋友簡直是秒懂,就是庫引用嘛!

[root@centos00 _data]# cat funclib.awk
function myprint()
{
printf "%-16s - %s
", $1, $4
}

function getRand(myRand)
{
print myRand*rand()
}

[root@centos00 _data]# cat rand.awk
BEGIN{FS=":"}
{
getRand(NF)
}

[root@centos00 _data]# gawk -f funclib.awk -f rand.awk /etc/passwd
1.66451
2.03746
5.9207
1.06546

推薦閱讀:

相关文章