Sed and awk 筆記之 sed 篇:基礎命令

在開始之前,首先回顧上一篇的重點內容:地址匹配。上一篇中介紹過,地址可以指定0個,1個或者2個。地址的形式可以爲斜槓分隔的正則表達式(例如/test/),行號(例如3,5)或者特殊符號(例如$)。如果沒有指定地址,說明sed應用的編輯命令是全局的;如果是1個地址,編輯命令只是應用到匹配的那一行;如果是一對地址,編輯命令則應用到該地址對匹配的行範圍。關於地址匹配的內容具體可以看Sed命令地址匹配問題總結

書中說,對於sed編輯命令的語法有兩種約定,分別是

[address]command               # 第一種
[line-address]command          # 第二種

第一種[address]是指可以爲任意地址包括地址對,第二種[line-address]是指只能爲單個地址。文中指出,例如i(後方插入命令)、a(前方追加命令)以及=(打印行號命令)等都屬於第二種命令,但是實際上我在ArchLinux上測試,指定地址對也沒有報錯。不過爲了兼容性,建議按作者所說的做。

以下是要介紹的全部基礎命令:

名稱 命令 語法 說明
替換 s [address]s/pattern/replacement/flags 替換匹配的內容
刪除 d [address]d 刪除匹配的行
插入 i [line-address]i\

text
在匹配行的前方插入文本
追加 a [line-address]a\

text
在匹配行的後方插入文本
行替換 c [address]c\

text
將匹配的行替換成文本text
打印行 p [address]p 打印在模式空間中的行
打印行號 = [address]= 打印當前行行號
打印行 l [address]l 打印在模式空間中的行,同時顯示控制字符
轉換字符 y [address]y/SET1/SET2/ 將SET1中出現的字符替換成SET2中對應位置的字符
讀取下一行 n [address]n 將下一行的內容讀取到模式空間
讀文件 r [line-address]r file 將指定的文件讀取到匹配行之後
寫文件 w [address]w file 將匹配地址的所有行輸出到指定的文件中
退出 q [line-address]q 讀取到匹配的行之後即退出

替換命令: s

替換命令的語法是:

[address]s/pattern/replacement/flags

其中[address]是指地址,pattern是替換命令的匹配表達式,replacement則是對應的替換內容,flags是指替換的標誌位,它可以包含以下一個或者多個值:

  • n: 一個數字(取值範圍1-512),表明僅替換前n個被pattern匹配的內容;
  • g: 表示全局替換,替換所有被pattern匹配的內容;
  • p: 僅當行被pattern匹配時,打印模式空間的內容;
  • w file:僅當行被pattern匹配時,將模式空間的內容輸出到文件file中;

如果flags爲空,則默認替換第一次匹配:

$ echo "column1 column2 column3 column4" | sed 's/ /;/'
column1;column2 column3 column4

如果flags中包含g,則表示全局匹配:

$ echo "column1 column2 column3 column4" | sed 's/ /;/g'
column1;column2;column3;column4

如果flags中明確指定替換第n次的匹配,例如n=2:

$ echo "column1 column2 column3 column4" | sed 's/ /;/2'
column1 column2;column3 column4

當替換命令的pattern與地址部分是一樣的時候,比如/regexp/s/regexp/replacement/可以省略替換命令中的pattern部分,這在單個編輯命令的情況下沒多大用處,但是在組合命令的場景下還是能省不少功夫的,例如下面是摘自文中的一個段落:

The substitute command is applied to the lines matching the address. If no
address is specified, it is applied to all lines that match the pattern, a
regular expression. If a regular expression is supplied as an address, and no
pattern is specified, the substitute command matches what is matched by the
address.  This can be useful when the substitute command is one of multiple
commands applied at the same address. For an example, see the section "Checking
Out Reference Pages" later in this chapter.

現在要在substitute command後面增加("s"),同時在被修改的行前面增加+號,以下是使用的sed命令:

$ sed '/substitute command/{s//&("s")/;s/^/+ /}' paragraph.txt 

這裏我們用到了組合命令,並且地址匹配的部分和第一個替換命令的匹配部分是一樣的,所以後者我們省略了,在replacement部分用到了&這個元字符,它代表之前匹配的內容,這點我們在後面介紹。執行後的結果爲:

+ The substitute command("s") is applied to the lines matching the address. If no
address is specified, it is applied to all lines that match the pattern, a
regular expression. If a regular expression is supplied as an address, and no
+ pattern is specified, the substitute command("s") matches what is matched by the
+ address.  This can be useful when the substitute command("s") is one of multiple
commands applied at the same address. For an example, see the section "Checking
Out Reference Pages" later in this chapter.

替換命令的一個技巧是中間的分隔符是可以更改的(這個技巧我們在簡潔的Bash編程技巧續篇 - 5. 你知道sed的這個特性嗎?中也曾經介紹過),這個技巧在有些地方非常有用,比如路徑替換,下面是採用默認的分隔符和使用感嘆號作爲分隔符的對比:

$ find /usr/local -maxdepth 2 -type d | sed 's/\/usr\/local\/man/\/usr\/share\/man/'
$ find /usr/local -maxdepth 2 -type d | sed 's!/usr/local/man!/usr/share/man!'

替換命令中還有一個很重要的部分——replacement(替換內容),即將匹配的部分替換成replacement。在replacemnt部分中也有幾個特殊的元字符,它們分別是:

  • &: 被pattern匹配的內容;
  • \num: 被pattern匹配的第num個分組(正則表達式中的概念,\(..\)括起來的部分稱爲分組;
  • \: 轉義符號,用來轉義&,\, 回車等符號

&元字符我們已經在上面的例子中介紹過,這裏舉個例子介紹下\num的用法。在Linux系統中,默認都開了幾個tty可以讓你切換(ctrl+alt+1, ctrl+alt+2 ...),例如CentOS 6.0+的系統默認是6個:

[root@localhost ~]# grep -B 1 ACTIVE_CONSOLES /etc/sysconfig/init 
# What ttys should gettys be started on?
ACTIVE_CONSOLES=/dev/tty[1-6]

可以通過修改上面的配置來減少開機啓動的時候創建的tty個數,比如我們只想要2個:

[root@localhost ~]# sed -r -i 's!(ACTIVE_CONSOLES=/dev/tty\[1-)6]!\12]!' /etc/sysconfig/init 
ACTIVE_CONSOLES=/dev/tty[1-2]

其中-i參數表示直接修改原文件,-r參數是指使用擴展的正則表達式(ERE),擴展的正則表達式中分組的括號不需要用反斜槓轉義"(ACTIVE_CONSOLES=/dev/tty\[1-)",這裏[是有特殊含義的(表示字符組),所以需要轉義。在替換的內容中使用\1來引用這個匹配的分組內容,1代表分組的編號,表示第一個分組。

刪除命令: d

刪除命令的語法是:

[address]d

刪除命令可以用於刪除多行內容,例如1,3d會刪除1到3行。刪除命令會將模式空間中的內容全部刪除,並且導致後續命令不會執行並且讀入新行,因爲當前模式空間的內容已經爲空。我們可以試試:

$ sed '2,${d;=}' list
John Daggett, 341 King Road, Plymouth MA

以上命令嘗試在刪除第2行到最後一行之後,打印當前行號(=),但是事實上並沒有執行該命令。

插入行/追加行/替換行命令: i/a/c

這三個命令的語法如下所示:

# Append 追加
[line-address]a\
text
# Insert 插入
line-address]i\
text
# Change 行替換 
[address]c\
text

以上三個命令,行替換命令(c)允許地址爲多個地址,其餘兩個都只允許單個地址(注:在ArchLinux上測試表明,追加和插入命令都允許多個地址,sed版本爲GNU sed version 4.2.1

追加命令是指在匹配的行後面插入文本text;相反地,插入命令是指匹配的行前面插入文本text;最後,行替換命令會將匹配的行替換成文本text。文本text並沒有被添加到模式空間,而是直接輸出到屏幕,因此後續的命令也不會應用到添加的文本上。注意,即使使用-n參數也無法抑制添加的文本的輸出。

我們用實際的例子來簡單介紹下這三個命令的用法,依然用最初的文本list:

$ cat list
John Daggett, 341 King Road, Plymouth MA
Alice Ford, 22 East Broadway, Richmond VA
Orville Thomas, 11345 Oak Bridge Road, Tulsa OK
Terry Kalkas, 402 Lans Road, Beaver Falls PA
Eric Adams, 20 Post Road, Sudbury MA
Hubert Sims, 328A Brook Road, Roanoke VA
Amy Wilde, 334 Bayshore Pkwy, Mountain View CA
Sal Carpenter, 73 6th Street, Boston MA

現在,我們要在第2行後面添加'------':

$ sed '2a\
--------------------------------------
' list
John Daggett, 341 King Road, Plymouth MA
Alice Ford, 22 East Broadway, Richmond VA
--------------------------------------
Orville Thomas, 11345 Oak Bridge Road, Tulsa OK
Terry Kalkas, 402 Lans Road, Beaver Falls PA
Eric Adams, 20 Post Road, Sudbury MA
Hubert Sims, 328A Brook Road, Roanoke VA
Amy Wilde, 334 Bayshore Pkwy, Mountain View CA
Sal Carpenter, 73 6th Street, Boston MA

或者可以在第3行之前插入:

$ sed '3i\
--------------------------------------
' list
John Daggett, 341 King Road, Plymouth MA
Alice Ford, 22 East Broadway, Richmond VA
--------------------------------------
Orville Thomas, 11345 Oak Bridge Road, Tulsa OK
Terry Kalkas, 402 Lans Road, Beaver Falls PA
Eric Adams, 20 Post Road, Sudbury MA
Hubert Sims, 328A Brook Road, Roanoke VA
Amy Wilde, 334 Bayshore Pkwy, Mountain View CA
Sal Carpenter, 73 6th Street, Boston MA

我們來測試下文本是否確實沒有添加到模式空間,因爲模式空間中的內容默認是會打印到屏幕的:

$ sed -n '2a\
--------------------------------------
' list
--------------------------------------

通過-n參數來抑制輸出後發現插入的內容依然被輸出,所以可以判定插入的內容沒有被添加到模式空間。

使用行替換命令將第2行到最後一行的內容全部替換成'----':

$ sed '2,$c\
--------------------------------------
' list
John Daggett, 341 King Road, Plymouth MA
--------------------------------------

打印命令: p/l/=

這裏純粹的打印命令應該是指p,但是因爲後兩者(l和=)和p差不多,並且相對都比較簡單,所以這裏放到一起介紹。

這三個命令的語法是:

[address]p
[address]=
[address]l

p命令用於打印模式空間的內容,例如打印list文件的第一行:

$ sed -n '1p' list
John Daggett, 341 King Road, Plymouth MA

l命令類似p命令,不過會顯示控制字符,這個命令和vim的list命令相似,例如:

$ echo "column1 column2 column3^M" | sed -n 'l'
column1\tcolumn2\tcolumn3\r$

=命令顯示當前行行號,例如:

$ sed '=' list
1
John Daggett, 341 King Road, Plymouth MA
2
Alice Ford, 22 East Broadway, Richmond VA
3
Orville Thomas, 11345 Oak Bridge Road, Tulsa OK
4
Terry Kalkas, 402 Lans Road, Beaver Falls PA
5
Eric Adams, 20 Post Road, Sudbury MA
6
Hubert Sims, 328A Brook Road, Roanoke VA
7
Amy Wilde, 334 Bayshore Pkwy, Mountain View CA
8
Sal Carpenter, 73 6th Street, Boston MA

轉換命令: y

轉換命令的語法是:

[address]y/SET1/SET2/

它的作用是在匹配的行上,將SET1中出現的字符替換成SET2中對應位置的字符,例如1,3y/abc/xyz/會將1到3行中出現的a替換成x,b替換成y,c替換成z。是不是覺得這個功能很熟悉,其實這一點和tr命令是一樣的。可以通過y命令將小寫字符替換成大寫字符,不過命令比較長:

$ echo "hello, world" | sed 'y/abcdefghijklmnopqrstuvwxyz/ABCDEFGHIJKLMNOPQRSTUVWXYZ/'
HELLO, WORLD

使用tr命令來轉換成大寫:

$ echo "hello, world" | tr a-z A-Z                                                    
HELLO, WORLD

取下一行命令: n

取下一行命令的語法爲:

[address]n

n命令爲將下一行的內容提前讀入,並且將之前讀入的行(在模式空間中的行)輸出到屏幕,然後後續的命令會應用到新讀入的行上。因此n命令也會同d命令一樣改變sed的控制流程。

書中給出了一個例子來介紹n的用法,假設有這麼一個文本:

$ cat text
.H1 "On Egypt"

Napoleon, pointing to the Pyramids, said to his troops:
"Soldiers, forty centuries have their eyes upon you."

現在要將.H1後面的空行刪除:

$ sed '/.H1/{n;/^$/d}' text
.H1 "On Egypt"
Napoleon, pointing to the Pyramids, said to his troops:
"Soldiers, forty centuries have their eyes upon you."

讀寫文件命令: r/w

讀寫文件命令的語法是:

[line-address]r file
[address]w file

讀命令將指定的文件讀取到匹配行之後,並且輸出到屏幕,這點類似追加命令(a)。我們以書中的例子來講解讀文件命令。假設有一個文件text:

$ cat text
For service, contact any of the following companies:
[Company-list]
Thank you.

同時我們有一個包含公司名稱列表的文件company.list:

$ cat company.list 
Allied
Mayflower
United

現在我們要將company.list的內容讀取到[Company-list]之後:

$ sed '/^\[Company-list]/r company.list' text
For service, contact any of the following companies:
[Company-list]
Allied
Mayflower
United
Thank you.

更好的處理應當是用公司名稱命令替換[Company-list],因此我們還需要刪除[Company-list]這一行:

$ sed '/^\[Company-list]/r company.list;/^\[Company-list]/d;' text
For service, contact any of the following companies:
[Company-list]
Thank you.

但是結果我們非但沒有刪除[Company-list],而且company.list的內容也不見了?這是怎麼回事呢?

下面是我猜測的過程,讀文件的命令會將r空格後面的所有內容都當成文件名,即company.list;/^\[Company-list]/d;,然後讀取命令的時候發現該文件不存在,但是sed命令讀取不存在的文件是不會報錯的。所以什麼事都沒幹成。

我們用-e選項將兩個命令分開就正常了:

$ sed -e '/^\[Company-list]/r company.list' -e '/^\[Company-list]/d;' text
For service, contact any of the following companies:
Allied
Mayflower
United
Thank you.

寫命令將匹配地址的所有行輸出到指定的文件中。假設有一個文件內容如下,前半部分是人名,後半部分是區域名稱:

$ cat text 
Adams, Henrietta Northeast
Banks, Freda South
Dennis, Jim Midwest
Garvey, Bill Northeast
Jeffries, Jane West
Madison, Sylvia Midwest
Sommes, Tom South

現在我們要將不同區域的人名字寫到不同的文件中:

$ sed '/Northeast$/w region.northeast
> /South$/w region.south
> /Midwest$/w region.midwest
> /West$/w region.west' text
Adams, Henrietta Northeast
Banks, Freda South
Dennis, Jim Midwest
Garvey, Bill Northeast
Jeffries, Jane West
Madison, Sylvia Midwest
Sommes, Tom South

查看輸出的文件:

$ cat region.
region.midwest    region.northeast  region.south      region.west 
$ cat region.midwest     
Dennis, Jim Midwest
Madison, Sylvia Midwest

我們也可以試試將所有的w命令放到一起用分號分隔,發現會出下面的錯誤,它把w空格後面的部分當作文件名,這樣就驗證了上面的猜測:

$ sed '/Northeast$/w region.northeast;/south$/w region.south;' text
sed: couldn't open file region.northeast;/south$/w region.south;: No such file or directory

退出命令: q

退出命令的語法是

[line-address]q

當sed讀取到匹配的行之後即退出,不會再讀入新的行,並且將當前模式空間的內容輸出到屏幕。例如打印前3行內容:

$ sed '3q' list
John Daggett, 341 King Road, Plymouth MA
Alice Ford, 22 East Broadway, Richmond VA
Orville Thomas, 11345 Oak Bridge Road, Tulsa OK

打印前3行也可以用p命令:

$ sed -n '3p' list
John Daggett, 341 King Road, Plymouth MA
Alice Ford, 22 East Broadway, Richmond VA
Orville Thomas, 11345 Oak Bridge Road, Tulsa OK

但是對於大文件來說,前者比後者效率更高,因爲前者讀取到第N行之後就退出了。後者雖然打印了前N行,但是後續的行還是要繼續讀入,只不會不作處理。

到此爲止,sed基礎命令的部分就介紹完了。下面一篇的內容會在最近幾天更新上來,主要會介紹sed高級命令。不過一般情況下,會使用基礎命令已經差不多了,高級命令不一定需要去了解。

下面是我整理的一份思維導圖(附.xmind文件下載地址):
sed基礎命令

轉載請註明轉自: 糰子的小窩 , 本文固定鏈接: Sed and awk 筆記之 sed 篇:基礎命令

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章