sed 命令

sed 簡述

sed 命令列格式如下:
sed [選項] [指令碼] [輸入檔案]
sed 選項

-e script, --expression=script

在命令列上執行 script (指令),只有一組指令時可不需要本選項。

-E, -r, --regexp-extended

支援延伸正規表示法(預設是基本正規表示法)。

-f script-file, --file=script-file

執行 script-file (指令檔), sed 的指令是在指令檔內。

-i

直接修改檔案內容。

-z

separate lines by NUL characters,將換行以 NUL 字元取代,取代換行時需本選項。

-n, --quiet, --silent

不自動列印 (模式空間)。

替代命令
sed 's/pattern/replacement/flags' inputFileName > outputFileName

替代命令 (在指令碼中第一個字母 s) 將 inputFileName 的內容與提供的樣板 (pattern) 匹配;當匹配成功則將樣板替換為「替換文字 (replacement)」。 / (斜線) 是傳統的定界字元 (delimiter),但;其實在 pattern 和 replacement 中都未出現的其他字元都可以當作定界字元。pattern 和 replacement 常稱為 正規表示法

sed 命令中可以用單引號 (') 和雙引號 ("),一般習慣採用單引號,但是如果要使用 shell 變數就需要使用雙引號

flags 指定為 g 時,表示 全域性更改。

將下列的 is 改成 is a:
echo 'this is book, that is bicycle' | sed -e 's/ is / is a /' (1)

this is a book, that is bicycle

以全域性 (g) 更改:
echo 'this is book, that is bicycle' | sed 's/ is / is a /g'

this is a book, that is a bicycle
1 只有一組指令,可不需要 -e 選項。

正規表示法

正規表示式 (英語: Regular Expression,簡寫為 regex、regexp 或 RE),又稱正規表示法。正規表示法分為基本正規表示法(Basic Regular Expression,BRE)和延伸正規表示法(Extended Regular Expression,ERE)。
sed 採用 ERE 的選項為 -E-r

正規表示法的特殊字元
. * ^ $ / \ [ ]

上述特殊字元作為文字字元時,需要在特殊字元前加入「跳脫字元 (Escape Character) \ (反斜線)」 來轉譯它。:

sed 的定界字元如果是 / (斜線),那麼文字字元的「斜線」要轉譯成 \/,如果不是則不可轉譯。
改變定界字元的目的就是為了不要轉譯「斜線」,如果又轉譯那麼在 BRE 不會出錯,ERE 會出錯 (參閱: 測試轉譯斜線)。

? + | ( ) { }

上述特殊字元作為文字字元時,在 BRE 不需轉譯,但在 ERE 需要轉譯。參閱:測試「特殊字元」轉譯

  • 作為 BRE 文字字元時,不需要 (也不可) 轉譯,但作為匹配字元時需轉譯。

  • 作為 ERE 文字字元時,需轉譯,但作為匹配字元時,不需要 (也不可) 轉譯。

sed 的指令碼如果是使用單引號 ('),而文字中有單引號時,那麼 bash 要轉譯文字字元為 \',反之如果是雙引號 (") 則要轉譯 \"

bash 在雙引號時,除了轉譯 " 也要注意字元 \ $ (作為文字字元,並非引用變數) 也需要跳脫字元
echo "\" \\ \$"
執行結果:
" \ $

當文字內容含「特殊字元」時,需注意 bash 是否正確,如將 $'pattern' 替換為 $'replacement'

echo "\$'pattern'" | sed "s/\$'pattern'/\$'replacement'/"
'replacement'

結果無誤,但實際上是錯的

顯示整段命令:
echo "\$'pattern'" \| sed "s/\$'pattern'/\$'replacement'/"
'pattern' | sed s/$'pattern'/$'replacement'/ (1)
1 沒有轉譯 $,但剛好 BRE 可在不需要轉譯的情況下執行,但如果改為 ERE 則會出錯。
specChar='$'

3.14159 (BRE 轉譯)
3.14159 (BRE 不轉譯)
3.14159 (ERE 轉譯)
3$14    (ERE 不轉譯)

在寫 bash 時,echo 會記得轉譯 $,但;當重點放在 sed 轉譯特殊字元時,往往會忽略 (忘記) 了 bash 在雙引號 (") 時也需要轉譯 \ $

先顯示整段命令,確認 bash 無誤:
echo "\$'pattern'" \| sed "s/\\\$'pattern'/\\\$'replacement'/"
執行結果:
'pattern' | sed s/\$'pattern'/\$'replacement'/
以 BRE ERE 執行命令:
echo "\$'pattern'" | sed    "s/\\\$'pattern'/\\\$'replacement'/" &&\
echo "\$'pattern'" | sed -E "s/\\\$'pattern'/\\\$'replacement'/"
匹配字元
字元 .

匹配一個任意字元。

字元 ^

匹配開始位置。

字元 $

匹配結束位置。

字元 *

表示前置字元有任意個 (包含 0 個)。
a*b 表示 b 的前面有 0 個或多個 a,匹配 baaab。而 .* 則表示全部文字。

字元 ?

表示前置字元有 0 個或 1 個。BRE 需要使用 \?
a\?b 表示 b 的前面有 0 個或 1 個 a,匹配 bab,不匹配 aab
? 作為 BRE 匹配字元時,需要前置跳脫字元 (\),但作為文字字元時不可加入跳脫字元。
? 作為 ERE 匹配字元時,不需要 (也不可有) 跳脫字元,但作為文字字元時需要前置跳脫字元。

字元 +

表示前置字元有 1 個或多個,BRE 需要使用 \+
a\+b 表示 b 的前面有 1 個或多個 a,匹配 abaaab,不匹配 b

字元 |

表示指明兩項之間的一個選擇,cat|dog 表示可以匹配 cat 或者 dog,BRE 需使用 \|

monkey moose 隱藏:
echo 'meerkat monkey moose mule' | sed    's/monkey\|moose/hide/g' &&\
echo 'meerkat monkey moose mule' | sed -E 's/monkey|moose/hide/g'
meerkat hide hide mule
meerkat hide hide mule
字元 &

合乎匹配的文字。

前置任意字元的 ats,加上中括號
echo 'Bats and cats.' | sed 's/.ats/\[&\]/g'
[Bats] and [cats].
字元 []
中括號中可以包含表示字元集 (character sets) 的表示式,使用方法大概有如下幾種:

[0-9]

表示 0-9 字元中的一個。

[0-9.]

表示數字和小數點。

[a-z]

表示 a-z 字元中的一個。

[A-Z]

表示大寫字母。

[a-zA-Z0-9]

大小寫字母和數字。

[abc]

表示字元 a 或者字元 b 或者字元 c。

[^0-9]

表示非數字的字元,^ 表示取反意思,只能放在中括號的開始處才有意義。

[-az]

表示字元 - 或者字元 a 或者字元 z,注意與 [a-z] 的區別,因為 - 字元沒有放在 a 和 z 之間。

將動物前置 mo mu 隱藏 (找出前置 m 後接 [ou] 再接任意個 [a-z]*):
echo 'meerkat monkey moose mule' | sed    's/m[ou][a-z]*/hide /g' &&\
echo 'meerkat monkey moose mule' | sed -E 's/m[ou][a-z]*/hide /g'
meerkat hide  hide  hide
meerkat hide  hide  hide
將參數改名,匹配開始字元,任意個不等於「結束字元」的字元,再匹配結束字元
echo 'show(abc)' | sed    's/([^)]*)/(def)/' &&\
echo 'show(abc)' | sed -E 's/\([^\)]*\)/\(def\)/'
show(def)
show(def)
取代任意文字,要指定可取代那些字元;最好的方式是字元條件不等於 pattern 的結束字元 ([^pattern])。
字元 [[: :]]

sed 除了正規的字元集外還支援命名字元類 (named character classes),命名類必須在兩個中括號內使用。

命名字元類的等效字元集如下:

[[:alnum:]]

[A-Za-z0-9]

大小寫字母和數字。

[[:alpha:]]

[A-Za-z]

英文字母。

[[:blank:]]

[ \x09]

空格或製表字元 (Tab 鍵)。

[[:cntrl:]]

[\x00-\x19\x7F]

控制字元。

[[:digit:]]

[0-9]

數字。

[[:graph:]]

[!-~]

空白字元之外的 (可見) 字元。

[[:lower:]]

[a-z]

小寫字母。

[[:print:]]

[ -~]

可列印的字元(類似 [[:graph:]],但包括空白字元)。

[[:punct:]]

[!-/:-@[-`{-~]

標點符號。

[[:space:]]

[ \f\n\r\t\v]

所有空白字元。

[[:upper:]]

[A-Z]

大寫字母。

[[:xdigit:]]

[0-9a-fA-F]

16 進制字元。

[^[:digit:]]

[^0-9]

表示非數字的字元,^ 表示取反意思,只能放在第二個中括號的開始處才有意義。

字元 \

\a

Alert (警報) beep,嗶聲,等價於 \x07\cG

\b

匹配「單詞」邊界。註:單詞為字母、數字或底線 (_)。

\B

匹配非單詞邊界。(\B 的匹配條件跟 \b 相反。)

\d

匹配數字 (含中文全型數字),等價於 [0-9]

\D

匹配非數字,等價於 [^0-9]

\f

匹配換頁字元,等價於 \x0c\cL

\n

匹配換行字元,等價於 \x0a\cJ

\r

匹配確認鍵,等價於 \x0d\cM

\s

匹配空白字元;包括空格 (含中文全型空格)、換頁字元、製表字元等等,等價於 [ \f\n\r\t\v]

\S

匹配非空白字元,等價於 [^ \f\n\r\t\v]

\t

匹配一個製表字元,等價於 \x09\cI

\v

匹配一個垂直製表字元,等價於 \x0b\cK

\w

匹配「單詞」字元,等價於 [A-Za-z0-9_]

\W

匹配非單詞的字元,等價於 [^A-Za-z0-9_]

\cx

匹配控制字元 x,例如,\cM 為一個 Ctrl+M 或確認鍵。

\xnn

16 進制數值 nn,如 \x0a 為換行。

\dnum

10 進制數值 num,如 \d10 為換行。

\u

在 sed 中是將字元轉成大寫,並非作為 Unicode 編碼,參閱:特殊序列


匹配「單詞」邊界 (\b)。

將邊界以中括號標示
echo 'at attach cattle cat' | sed -e 's/\b/\[&\]/g' &&\
echo 'at attach cattle cat' | sed -E 's/\b/\[&\]/g'
[]at[] []attach[] []cattle[] []cat[]
[]at[] []attach[] []cattle[] []cat[]

邊界是一個抽象的匹配,在邊界時條件為吻合。

先匹配文字 at 再匹配邊界 \b
echo 'at attach cattle cat' | sed    's/at\b/&\[\]/g' &&\
echo 'at attach cattle cat' | sed -E 's/at\b/&\[\]/g'
at[] attach cattle cat[]
at[] attach cattle cat[]

匹配文字 at 條件之後要為邊界才為吻合。

先匹配邊界 \b 再匹配文字 at
echo 'at attach cattle cat' | sed    's/\bat/\[\]&/g' &&\
echo 'at attach cattle cat' | sed -E 's/\bat/\[\]&/g'
[]at []attach cattle cat
[]at []attach cattle cat

邊界條件匹配後,再匹配文字 at 才為吻合。


無邊界文字,匹配「邊界」。
echo '12345678' | sed 's/\b/\[&\]/g'

[]12345678[]

在文字的前後邊界時條件為吻合。

千位分隔符號,在一段連續的數字中,由右方開始算起,每隔三位數加進一個逗號。
在數字右方 (邊界),開始算起接連三個數字,加入一個逗號。
用 sed 的說明:在匹配三個數字 ([0-9]{3}) 後要接「邊界」(\b)。

匹配三個數字 ([0-9]{3}) 後要接「邊界」(\b)
echo '12345678' | sed 's/[0-9]\{3\}\b/\[&\]/'

12345[678]
上述雖然正確,但如果數字為 123 也會合乎條件,匹配式有錯。
echo '123' | sed 's/[0-9]\{3\}\b/\[&\]/'

[123]

條件需再加上,開始的「非邊界」。
在「非邊界」之後,接三個數字,在三個數字之後要接「邊界」。

條件是

  1. 非邊界 /B

  2. 三個連續數字 [0-9]{3}

  3. 邊界 /b

echo '12345678' | sed    's/\B[0-9]\{3\}\b/\[&\]/' &&\
echo '12345678' | sed -E 's/\B[0-9]{3}\b/\[&\]/' &&\
echo '123'      | sed    's/\B[0-9]\{3\}\b/\[&\]/' &&\
echo '123'      | sed -E 's/\B[0-9]{3}\b/\[&\]/'
12345[678]
12345[678]
123
123

註:接下來要「分支」的動作,才能完成 12,345,678

\B \b 之間的匹配,可當做是由後面 (右邊) 開始,向前面 (左邊) 匹配。

「單詞」有那些?

測試「單詞」邊界:
echo "abc +-*/=. 123 _" | sed 's/\b/X/g'
單詞邊界結果:
XabcX +-*/=. X123X X_X

單詞為字母、數字或底線 (_)。


匹配換行 \n,sed 需要使用 -z 選預。

將換行替換為 ,
seq 3 | sed -z 's/\n/,/g'

如何處理 Unicode 編碼?

Σ 的 Unicode 編碼為 \u03a3
echo -n Σ | iconv -t JAVA
\u03a3
  • 直接採用 Unicode 字元。

  • echo -n Σ | od -tx1echo -n Σ | hexdump -C 轉換成 16 進位字元 (cea3)。

  • 以 bash echo 來轉換如 '$(echo -ne '\u03a3')'

  • 以 bash 轉換如 '$'\u03a3''

echo Σ | sed 's/Σ/X/' &&\
echo Σ | sed 's/\xce\xa3/X/' &&\
echo Σ | sed 's/'$(echo -ne '\u03a3')'/X/' &&\
echo Σ | sed 's/'$'\u03a3''/X/'
X
X
X
X
字元 (pattern)

表示分組「Group」,在 ( ) 之間為匹配樣板 (pattern),BRE 需使用 \(\)。可以通過回撥引數 \1\2\3 至最大值為 \9 來表示分組匹配內容。

只列印匹配分組樣板 monkey
echo 'mule monkey moose' | sed    's/.*\(monkey\).*/\1/' &&\
echo 'mule monkey moose' | sed -E 's/.*(monkey).*/\1/'
monkey
monkey
將參數 b a 互換 a1 b2
echo 'add(b, a)' | sed     's/add(\([^,]\+\), *\([^)]\+\)/add(\21, \12/' &&\
echo 'add(b, a)' | sed  -E 's/add\(([^,]+), *([^\)]+)/add(\21, \12/'
add(a1, b2)
add(a1, b2)

回撥引數最大為 \9\21 是指回撥第 2 組再加上數字 1

字元 {n,m}

{n}

匹配 n 次。如 o{2} 不能匹配 fox 中的一個 o,但是能匹配 google 中的兩個 o

{n,}

至少匹配 n 次。如 o{2,} 不能匹配 fox 中的一個 o,但能匹配 goooogle 中的所有 o
o{1,} 等價於 o+o{0,} 則等價於 o*

{n,m}

至少匹配 n 次且最多匹配 m 次。如 o{1,3},將匹配 goooogle 前三個 o
o{0,1} 等價於 o?

BRE 需使用 \{n,m\}

匹配一個或多個小寫 o,替換為一個大寫 O:
echo 'meerkat monkey moose mule' | sed    's/o\{1,\}/O/g' &&\
echo 'meerkat monkey moose mule' | sed    's/o\+/O/g' &&\
echo 'meerkat monkey moose mule' | sed -E 's/o{1,}/O/g' &&\
echo 'meerkat monkey moose mule' | sed -E 's/o+/O/g'
meerkat mOnkey mOse mule
meerkat mOnkey mOse mule
meerkat mOnkey mOse mule
meerkat mOnkey mOse mule
字元 () 在 BRE (基本正規表示法) 及 ERE (延伸正規表示法) 的差異
文字字元 () 在 BRE 不是特殊字元不需要轉譯。如替換 (Hello) 取代成 hi:
echo '(Hello)' | sed 's/(Hello)/hi/'

hi
如果轉譯成 \(\),則變成 BRE 分組「Group」。
echo '(Hello)' | sed 's/\(Hello\)/hi/'

(hi)

但為什麼會有這樣的結果?
「分組」取得分組樣板 Hello,將其分組樣板替換為 hi
原始文字 (Hello)(「分組樣板」),將其「分組樣板」替換 hi 其結果為 (hi)

文字字元 () 在 ERE 需要轉譯:
echo '(Hello)' | sed -E 's/\(Hello\)/hi/'

hi
不轉譯 (),則為 ERE 分組「Group」。
echo '(Hello)' | sed -E 's/(Hello)/hi/'

(hi)

sed 指令 (script)

sed 程序由一個或多個 sed 指令 (script) 組成,由多個 -e 選項傳入或者執行 -f 指定的指令檔。

sed 指令 (script) 格式如下:
[選擇行號] 命令 [選項]
多指令語法

指令的範例是將 2 替換為 2B3 替換為 3C

多指令使用 -e 選項
seq 5 | sed -e 's/2/2B/' -e 's/3/3C/' (1)
1 -e 之前要空格,-e 之後可不需要空格。
使用分號 (;)
seq 5 | sed 's/2/2C/; s/3/3C/' (1)
1 分號之後可不需要空格。
使用換行
seq 5 | sed 's/2/2B/
s/3/3C/
'
使用檔案 (-f 選項):
cat > script.sed << EOF
s/2/2B/
s/3/3C/
EOF

seq 5 | sed -f script.sed
sed 管道
當讀取跟輸出的檔案相同時,如果以下列方式執行:
sed command $File | sed command > $File (1)
cat $File | sed command | sed command > $File (1)
1 將得到一個空的 $File,原因是在建立管道時,輸出管道 > $File 會先執行將建立一個空的 $File。
可使用臨時檔案來解決此問題:
sed command $File | sed command > tmpFile
mv tmpFile $File

cat $File | sed command | sed command > tmpFile
mv tmpFile $File

選擇行號 (Addressing)

number

指定「行號 (number 數字)」僅匹配該「行號」。

first~step

first 行號 (數字) 開始,間隔 (~) step 行號 (數字)。1~2 表示選擇奇數行。

$

匹配最後一行

/regexp/

匹配正規表示式 regexp 的「行」。

\%regexp%

同樣是匹配正規表示式 regexp,但允許使用與 / (斜線) 不同的定界字元,表示式中不需要再轉譯「斜線」。
定界字元可為 % 亦可替換為其他單個字元。

/regexp/I
\%regexp%I

regexp 不區分大小寫

/regexp/M
\%regexp%M

多行模式空間採用修飾字元 M 才能匹配每行的 ^$

n,m

上述可以採用逗號 (,) 區分兩個位置;位置範圍由第一個位置 (n) 至 (,) 第二個位置 (m)。第二個位置如果是「行號」,該數字小於第一個行號,則只有一行匹配。
第二個位置如果是 regexp,則匹配開始位置是第一個位置之後的開始行,該範圍始終跨越至少兩行。
註:如果 regexp 沒有匹配,則範圍為第一個位置至最後一行。

0,/regexp/

行號 0 可以用在地址規範中,例如 0,/regexp/,第二個位置是 regexp,則匹配開始位置是第一個位置 (0) 之後 (即為檔案開頭),sed 可以由第一行開始匹配。
註:如果 regexp 沒有匹配,則範圍為第一行至最後一行。

addr1,+N

addr1 行開始,再接著往下數 N 行,也就是 addr1addr1+N 行。
addr1 可為行號 (number) 或 正規表示式 (regexp)。

addr1,~N

addr1 行開始至 (,) 下一個倍數 (~) N 的行號。
addr1 可為行號 (number) 或 正規表示式 (regexp)。

number
第 2 行執行列印
seq 3 | sed -n '2 p' (1)

2
1 sed 使用 -n 選項 (不自動輸出),由 p (列印) 命令來輸出,另外 2 p 中間可不需要空格字元。
first~step
從第 2 行開始,每隔 (~) 3 行,執行列印
seq 9 | sed -n '2~3 p'

2
5
8
regexp
找出 B 行,執行列印
printf '%s\n' 1A 2B 3C | sed -n '/B/ p' &&\
printf '%s\n' 1A 2B 3C | sed -n '/b/I p'

2B
2B
找出不存在的 F,執行列印
printf '%s\n' 1A 2B 3C | sed -n '/F/ p'

沒有輸出。

\%regexp%
採用定界字元 %#,不需轉譯 /。找出含字元 / 的行,執行列印。
printf '%s\n' 1A 2/ 3C | sed -n '\%/% p' &&\
printf '%s\n' 1A 2/ 3C | sed -n '\#/# p' &&\
printf '%s\n' 1A 2/ 3C | sed -n '/\// p'

2/
2/
2/

$ 需要轉譯成 \$,表示式 \%regexp% 是允許使用與 / 不同的定界字元,並不是不需要「轉譯」。

找出 $ 行,執行列印
printf '%s\n' 1A 2$ 3C | sed -n '\%\$% p' &&\
printf '%s\n' 1A 2$ 3C | sed -n '/\$/ p'

2$
2$
/regexp/M

模式空間只有一行時,能匹配 ^$

列印開頭 (^) 之後為 2 或者 2 在結束 ($) 之前的行。
seq 3 | sed -n '/^2/ l' &&\
seq 3 | sed -n '/2$/ l'
2$
2$

但在多行時,無法匹配 2,因為 2 不在整個資料的開頭之後或結束之前。

下列將無輸出
seq 3 | sed -n 'N;N; /^2/ p' &&\
seq 3 | sed -n 'N;N; /2$/ p'

採用修飾字元 M,則能匹配每行的 ^$

匹配第 2 行的 ^$
seq 3 | sed -n 'N;N; /^2/M l' &&\
seq 3 | sed -n 'N;N; /2$/M l'
1\n2\n3$
1\n2\n3$
n,m
第 2 行 至(,) 第 4 行執行列印
seq 100 | sed -n '2,4 p'

2
3
4
第 4 行 至(,) 第 2 行,只有第 4 行執行列印 (行號 4 至行號 2,只有行號 4 合乎)
printf '%s\n' 1A 2B 3C 4D 5E | sed -n '4,2 p'

4D
第 D 行 至(,) 第 2 行執行列印,只有 D 行執行列印 (D 行號為 4 至行號 2,只有行號 4 合乎)
printf '%s\n' 1A 2B 3C 4D 5E | sed -n '/D/,2 p'

4D
第 2 行 至(,) D 行執行列印
printf '%s\n' 1A 2B 3C 4D 5E | sed -n '2,/D/ p'

2B
3C
4D
第 4 行 至(,) B 行執行列印,regexp 沒有匹配 B,列印 第 4 行至最後一行 (regexp 沒有匹配,則範圍由第一個位置至最後一行)。
printf '%s\n' 1A 2B 3C 4D 5E | sed -n '4,/B/ p'

4D
5E
第 1 行 至(,) A 行執行列印,regexp 沒有匹配 A (匹配開始位置是第一個位置之後),列印 第 1 行至最後一行。
printf '%s\n' 1A 2B 3C 4D 5E | sed -n '1,/A/ p'

1A
2B
3C
4D
5E
0,regexp
第 0 行 至(,) A 行執行列印
printf '%s\n' 1A 2B 3C | sed -n '0,/A/ p'

1A
第 0 行 至(,) 不存在 F 行執行列印,regexp 沒有匹配,列印全部。
printf '%s\n' 1A 2B 3C | sed -n '0,/F/ p'

1A
2B
3C
addr1,+N
從第 2 行開始,再接著往下數 (+) 3 行,也就是 2 至 5 行執行列印
seq 100 | sed -n '2,+3 p'

2
3
4
5
addr1,~N
由第 4 行開始 至(,) 下一個倍數 (~) 3 的行號,也是 4 至 6 執行列印。
seq 100 | sed -n '4,~3 p'

4
5
6

選擇行號「分組」表示式

選擇行號的 BRE 分組功能 \(regexp\),ERE 需使用 (regexp),所選取的內容為兩組相同;分組匹配的內容跟回撥內容 (\1) 相同,如匹配內容為 9 回撥內容亦為 9 則匹配 99 的行。

列印接連二個相同字元的行
seq 87 110 | sed -En '/(.)\1/p'
88
99
100
110
回撥引數可加上額外條件如 \19,回撥引數最大為 \9\19 是指回撥第 1 組再加上字元 9
seq 100 400 | sed -En '/(.)\19/p'
119
229
339
列印前置 9;中間接連兩個相同字元;及尾端為 9 的行。
seq 9100 9400 | sed -En '/^9(.)\19$/p'
9119
9229
9339
匹配開始位置 (^) 及 匹配結束位置 ($) 說明:
  • /(.)\1/ 匹配二個相同字元。

  • /^(.)\1/ 匹配開始位置 ^,開始的字元再接續一個相同的字元。

  • /(.)\1$/ 匹配結束位置 $,回撥匹配,最後的字元要跟前一個字元相同。

  • /^(.)\1$/ 同時匹配開始及結束,在匹配一個字元的情況,只有兩個相同的字元合乎匹配。

seq 87 110 | sed -En '/^(.)\1/p'
88
99
110

seq 87 110 | sed -En '/(.)\1$/p'
88
99
100

seq 87 111 | sed -En '/^(.)\1$/p'
88
99

若表示式中可匹配多個數量如 *?,以 /(.*),\1/ 為例,將兩組文字以字元 , 區分。
若分組表示式無其他條件,則分組匹配內容往往是 0 個字元,則匹配有字元 , 的行。

/(.*),\1/ 匹配有字元 , 的行
printf '%s\n' 1,1 12,123 321,21 12345 | sed -En '/(.*),\1/p'

1,1
12,123
321,21
/^(.*),\1/ 匹配第一組開始的文字跟第二組開始的文字。
printf '%s\n' 1,1 12,123 321,21 | sed -En '/^(.*),\1/p'

1,1
12,123
/(.*),\1$/ 回撥匹配第二組文字跟第一組文字 (字元 , 之前)。
printf '%s\n' 1,1 12,123 321,21 | sed -En '/(.*),\1$/p'

1,1
321,21
/^(.*),\1$/ 同時匹配開始及結束,匹配兩組相同的文字。
printf '%s\n' 1,1 12,123 321,21 | sed -En '/^(.*),\1$/p'

1,1

sed 基本命令

sed 基本命令

a

新增文字,在行後新增文字 (文字在下一行)。

c

取代文字,用文字取代行。

d

刪除。

i

插入文字,在行之前插入文字 (文字在上一行)。

l

以明確形式列印 (列印控制字元)。

n

讀取下一行。

p

列印。

q

離開。

r

讀取檔案。

s

替換 (搜尋並替換)。

w

寫到檔案。

y

轉換字元。

=

列印行號。

a (新增)、i (插入)

a text

在行後新增一行文字,輸入的文字直到換行。自動忽略 a 命令後跟文字間的空格。

在第二行後新增 hello:
seq 3 | sed '2a hello'
1
2
hello
3

如果換行要接續則輸入「行接續字元 \ (反斜線)」。

在第二行後新增一行 hello 再接續 world:
seq 3 | sed '2a hello\
world
' (1)
1 亦可採用「\n 換行」如 seq 3 | sed '2a hello\nworld'
1
2
hello
world
3
在第二行後新增 hello,在第三行新增 world:
seq 3 | sed '2a hello
3a world
'
1
2
hello
3
world
i text

在行前插入一行文字,輸入的文字直到換行。自動忽略 i 命令後跟文字間的空格。

在第二行前插入 hello:
seq 3 | sed '2i hello'
1
hello
2
3

如果換行要接續則輸入「行接續字元 \ (反斜線)」。

在第二行前插入一行 hello 再接續 world:
seq 3 | sed '2i hello\
world
' (1)
1 亦可採用「\n 換行」如 seq 3 | sed '2i hello\nworld'
1
hello
world
2
3
在第二行前插入 hello,在第三行前插入 world:
seq 3 | sed '2i hello
3i world
'
1
hello
2
world
3
a\
text

在行後新增文字,在新行新增文字,直到換行。忽略 a\ 之間的空格。

在第二行後新增 hello (前置一個空格):
seq 3 | sed '2a\
 hello'
1
2
 hello
3
如果換行要接續則輸入「行接續字元 \ (反斜線)」:
seq 3 | sed '2a\
 hello\
world
s/./x/' (1)
1 本例再增加替換命令,替換 (一行只有) 一個任意字元為 x
x
x
 hello
world
x
a\ 命令和文字可以分為兩個 -e 引數,使編寫指令碼更加容易:
seq 3 | sed -e '2a\' -e ' hello\nworld' -e 's/./x/'
i\
text

在行前插入文字,在新行輸入文字,直到換行。忽略 i\ 之間的空格。

在第二行前插入 hello (前置一個空格):
seq 3 | sed '2i\
 hello'
1
 hello
2
3
如果換行要接續則輸入「行接續字元 \ (反斜線)」:
seq 3 | sed '2i\
 hello\
world
s/./x/'
x
 hello
world
x
x
i\ 命令和文字可以分為兩個 -e 引數,使編寫指令碼更加容易:
seq 3 | sed -e '2i\' -e ' hello\nworld' -e 's/./x/'

c (取代)

用文字取代行,輸入的文字直到換行。自動忽略 c 命令後跟文字間的空格。

第二行取代成 hello:
seq 3 | sed '2c hello'
1
hello
3

如果換行要接續則輸入「行接續字元 \ (反斜線)」。

第二行取代成 hello 文字再接續 world:
seq 3 | sed '2c hello\
world (1)
'
1 亦可採用「\n 換行」如 seq 3 | sed '2c hello\nworld'
1
hello
world
3
第二行取代成 hello,第三行取代成 world:
seq 3 | sed '2c hello
3c world
'
1
hello
world
c\
text

用文字取代行,輸入的文字直到換行。忽略 c\ 之間的空格。

在第二行取代成 hello (前置一個空格):
seq 3 | sed '2c\
 hello'
1
 hello
3

如果換行要接續則輸入「行接續字元 \ (反斜線)」。

seq 3 | sed '2c\
 hello\
world
s/./x/'
x
 hello
world
x
c\ 命令和文字可以分為兩個 -e 引數,使編寫指令碼更加容易:
seq 3 | sed -e '2c\' -e ' hello\nworld' -e 's/./x/'
x
 hello
world
x

d (刪除)

刪除第 2 行
seq 5 | sed 2d
1
3
4
5

n (讀取下一行)

讀取下二行
seq 10 | sed -n 'n;n;p'

sed 先讀取第 1 行,再讀取 2 行,第 3 行列印。下一個循環 (cycle),由第 4 行開始,讀取 2 行,第 6 行 列印,再次執行下一個循環。

3
6
9

p (列印)

第 2 行執行列印
seq 3 | sed -n '2 p' (1)
1 sed 使用 -n 選項 (不自動輸出),由 p (列印) 命令來輸出,另外 2 p 中間可不需要空格字元。

qQ (離開)

離開 sed 不再處理任何命令或輸入。

命令格式

q[exit-code]

Q[exit-code]

Q 命令和 q 一樣,但 Q 離開時不會列印。

讀取下一行後執行離開,只會輸出前 2 行:
seq 3 | sed 'n;q'
1
2
離開時,一併回傳代號 255
seq 3 | sed 'n;q255' ; echo $? (1)
1 echo $? 列印回傳代號。
1
2
255
列出 /etc/passwd 的前 10 行
sed '10q' /etc/passwd

s (替換)

替換命令格式 s/pattern/replacement/flags

flags

g

全域性更改

i, I

不區分大小寫。

m, M

多行模式 (multi-line mode)。

p

列印

w

寫入檔案 (多個 flag 時,需放在最後)

number

number 次匹配時,執行替換。

flags 可用 (不定數量的) 空格字元區分。

flags 示例
number
取代 a 為 X:
echo BAnana | sed 's/a/X/'

BAnXna
取代第 2 個 a 為 X (flags number 為 2):
echo BAnana | sed 's/a/X/2'

BAnanX
g
以 flags g (全域性更改) 全部取代 a 為 X:
echo BAnana | sed 's/a/X/g'

BAnXnX
i, I
以 flags gi (不區分大小寫) 取代 a 為 X:
echo BAnana |sed 's/a/X/gi'

BXnXnX
p
sed 採用 -n 命令列選項不自動輸出:
echo BAnana |sed -n 's/a/X/'

無輸出

s 命令採用 flags p (列印) 輸出:
echo BAnana |sed -n 's/a/X/p'

BAnXna
w
s 命令採用 flags w (寫入) 到標準輸出:
echo BAnana |sed -n 's/a/X/w /dev/stdout'

BAnXna
m, M

在說明多行模式 (multi-line mode) 之前,先準備好多行的文字內容。

sed 先讀取第 1 行, 執行 2 次命令 N (讀取下一行),讀取後的「文字內容」會有 3 行。
註:「文字內容」稱為「模式空間 (pattern space)」。

seq 3 | sed 'N;N'
1
2
3
s 替換命令預設行為

sed 預設行為將模式空間中所有的資料一起處理,整個資料只有一個開始 (^) 和結束($),而匹配一個任意字元 (.) 也是整個資料一起匹配。

替換「開始 (^)」為 ^,替換「結束 ($) 」為 $
seq 3 | sed 'N;N; s/^/\^/g; s/$/\$/g'
^1
2
3$
任意數量的字元 (.*) 加上中括號
seq 3 | sed 'N;N; s/.*/\[&\]/g'
[1
2
3]
s 替換命令多行模式

在多行模式 (multi-line mode) 下,^$ 表示每一行的開始和結束,而 . 只會匹配同一行。

替換「開始 (^)」為 ^,替換「結束 ($) 」為 $
seq 3 | sed 'N;N; s/^/\^/mg; s/$/\$/mg'
^1$
^2$
^3$
任意數量的字元 (.*) 加上中括號
seq 3 | sed 'N;N; s/.*/\[&\]/mg'
[1]
[2]
[3]

可由一個由反斜線和字母組成的特殊序列,其意義如下:

\L

將「替換文字 (replacement)」轉成小寫,直到 \U\E

\l

將第一個字元轉為小寫。

\U

將替換轉成大寫,直到 \L\E

\u

將第一個字元轉成大寫。

\E

停止 (由 \L\U 開始的) 大小寫轉換。

\U 示例
匹配前置任意字元的 at,將合乎匹配的文字 (&) 轉成大寫 (\U)。
echo 'bat monkey and cat' | sed 's/.at/\U&/g'
BAT monkey and CAT
\u 示例
匹配前置任意字元的 at,將合乎匹配的文字 (&) 第一個字改成大寫(\u)。
echo 'bat monkey and cat' | sed 's/.at/\u&/g'
Bat monkey and Cat

y (轉換字元)

y/原始字元集/目的字元集/
替原始字元集的個數和目的字元集個數必須相等,字元集內每個字元一一對應。

轉換 a-j0-9:
echo hi mary | sed 'y/abcdefghij/0123456789/'

78 m0ry

l (明確形式列印)

明確形式列印

無法列印的字元 (如換行 \n) 和字元 \ 以 C 風格的形式轉譯;超出長度的尾端會以「接續字元 \」拆分,末尾會標示 $
註:字元 $ 是由命令 l 明確表達,該字元實際上並不存在。

可由命令選項 l N 或命令列選項 sed -l Nsed --line-length=N 指定輸出最大長度 N

明確形式列印 (控制字元)
echo -e 'Hello\tworld' | sed -n 'l'
Hello\tworld$
以命令限制長度 8
echo -e 'Hello\tworld' | sed -n 'l8'
以命令列選項 -l 限制長度 8
echo -e 'Hello\tworld' | sed -l8 -n -e 'l'
Hello\t\ (1)
world$
1 當長度不足時,尾端會列印「接續字元 \」。

下列可將 將換行替換為 ,

seq 3 | sed -z 's/\n/,/g'

但為什麼? 因為命令列選項 -z 會將所有的換行,變成一行文字。

觀察命令列選項 z 的行為
seq 3 | sed -zn 'l' (1)
1 由於執行了明確列印 (l),以命令列選項 (-n) 禁止「預設列印」,避免混淆。
1\n2\n3\n$

= (列印行號)

printf '%s\n' a b c | sed =
1
a
2
b
3
c

sed 先列印行號,再列印原始內容,原始的一行變成二行。

列出 /etc/passwd 共有幾行,選擇最後行 ($),再列印行號 (=),並使用命令列選項 -n 不列印原始內容。
sed -n '$=' /etc/passwd

註:「列印行號」不會影響模式空間。

sed 進階命令

在說明進階命令之前,先說明「模式空間 (pattern space)」。

sed 的預設動作為一次讀取一行到「模式空間」,然後執行指令,接著處理下一行;繼續「循環 (cycle)」直到檔案結束 (EOF)。

d 刪除命令是指刪除「模式空間」,p 列印命令是指列印「模式空間」,sed 的「預設列印」(命令列選項無 -n) 也是列印 (在處理所有指令後剩下的) 「模式空間」。

sed 進階命令

d

刪除模式空間,然後重啟循環 (cycle)。

D

刪除模式空間內的第一行,並以模式空間 (刪除後) 的結果「重啟指令 (script)」。
註:如果 (刪除前的) 模式空間不含換行,則為一般循環跟 d 命令相同 (會讀取新行)。

n

(讀取) 下一行複製至模式空間 next (copy),「預設列印」模式空間。

N

(讀取) 下一行新增至模式空間 Next (append)。

p

p (小寫) 列印模式空間。

P

P (大寫) 列印模式空間內的第一行。

q

離開,「預設列印」(目前的) 模式空間。

Q

離開 (不列印)。

z

清除模式空間。

{}

命令組,一次執行多個命令,命令組格式為 { 命令 [;命令] }

#

註解

!

否定 (反動作)

註:命令列有 -n 選項時,則不會「預設列印」。

進階命令補充
D 命令

刪除模式空間內的第一行,剩餘第二行(含以後),若模式空間只有一行則全部刪除,另一種說法是「刪除模式空間的內容直到第一個換行字元,若無換行時則刪除全部」。
本節中將 D 的後續動作稱為「重啟指令」(重新執行指令),不採用通用的說法「重啟循環」,避免混淆。重啟指令時,sed 並不會讀取新的輸入行,而是按照指令執行動作。

n 命令

讀取下一行複製至模式空間,先前的「模式空間」將會消失,該命令可「預設列印」先前 (未讀取前) 的「模式空間」。

N 命令

讀取下一行新增至模式空間,另一種說法是「先新增換行至模式空間,再將讀取行新增至模式空間」。
模式空間的結果為:「目前 (上次) 的模式空間」 + \n + 讀取行 (本次讀取的模式空間)」。

P (大寫) 命令

列印模式空間內的第一行,若模式空間只有一行也會列印,另一種說法是「列印模式空間內的第一個字直到換行字元,若無換行則列印全部」。

命令範例:
seq 5 | sed 'n;l;d'
範例結果:
1
2$
3
4$
5
流程說明
  1. 在每個循環的開始,sed 讀取一行至模式空間 (在第一循環為 1)。

  2. 命令 n「預設列印」上次模式空間(在第一循環為 1),命令 n 讀取下一行至模式空間 (在第一循環為 2)。

  3. 命令 l 明確列印模式空間時加入尾端標示 (在第一循環為 2$)。

  4. 命令 d 重啟循環。

  5. 在下一個循環中,sed 讀取一行至模式空間 (例如 3),命令 n 讀取下一行至模式空間 (例如 4)。

將範例中如下修改
seq 5 | sed -n 'n;p;d'
結果為列印雙數行
2
4
實際上並不需要以命令 d 來重啟循環,sed 的預設動作已經會循環。
seq 5 | sed -n 'n;p'
在上述的「流程說明」步驟說明如下
  1. 非檔案結束 (EOF) 時繼續循環。

n;p 為下一行列印,那麼列印 3 倍數行則為「下一行再下一行然後列印」,指令為 n;n;p (亦可採用 選擇行號0~3 p)。
列印單數行為「先列印再讀取下一行」,指令為 p;n (或採用選擇行號的 1~2 p)。

在 sed 的 sed 4.7 (含以上) 版本,可在命令列中加入 --debug 選項,可得知程序情況
seq 5 | sed -n 'n;p' --debug
SED PROGRAM:
  n
  p
INPUT:   'STDIN' line 1
PATTERN: 1
COMMAND: n
PATTERN: 2
COMMAND: p
2
END-OF-CYCLE:
INPUT:   'STDIN' line 3
PATTERN: 3
COMMAND: n
PATTERN: 4
COMMAND: p
4
END-OF-CYCLE:
INPUT:   'STDIN' line 5
PATTERN: 5
COMMAND: n (1)
END-OF-CYCLE:
1 列印雙數行在列印第 4 行後的循環;在第 5 行執行 n;p 時,在執行 n 命令之後已經 EOF,sed 會中斷循環並結束。

多行處理

N 命令示例 (加入行號)
printf '%s\n' a b c | sed = | sed -e 'N; s/\n/ /'
1 a
2 b
3 c
  1. 第一個 sed,先將每一行,列印行號 (=) 成為二行,行號在第一行,原始內容的第一行變成第二行。

  2. 第二個 sed,以命令 N 將兩行合併成一行,兩行中間為換行 (\n),再以命令 s 將換行替換為空格。

N; s/\n/ / 流程說明
  1. 在每個循環的開始,sed 讀取一行至模式空間 (在第一循環為 1)。

  2. 命令 N 讀取下一行 (第一循環為 a),新增至模式空間 (第一循環為 1\na)。

  3. 命令 s 將換行替換為空格 (第一循環為 1 a),執行後「預設列印」1 a

  4. 非檔案結束 (EOF) 時繼續循環。

  5. 在下一個循環中,sed 讀取一行至模式空間 (例如 2),命令 N 讀取下一行 (例如 b),新增至模式空間 (例如 2\nb)。

N、D 命令示例
seq 5 | sed 'N;l;D'
1\n2$
2\n3$
3\n4$
4\n5$
5
  1. sed 將第一行讀入模式空間(即為 1)。

  2. 在每個程序的開始,命令 N 新增至模式空間(第一個流程即為 1\n2)。

  3. 命令 l 列印模式空間的內容 (第一個流程即為 1\n2$)。

  4. 命令 D 刪除模式空間內的第一行(在第一個流程結束時模式空間留下 2)。
    然後「重啟程序」 (並不會讀取新行,由第 2 步驟開始執行)。

  5. 在下一個流程中,命令 N 新增至模式空間(例如 2\n3)。

  6. 整個程序只有一個循環,循環結束時模式空間留下 5

N、P、D 命令示例 (刪除重覆的行)
printf '%s\n' 1A 2B 2B 3C | sed -n  '$!N; /^\(.*\)\n\1$/!P; D' &&\
printf '%s\n' 1A 2B 2B 3C | sed -En '$!N; /^(.*)\n\1$/!P; D'
1A
2B
3C
1A
2B
3C
N、P、D 命令說明
printf '%s\n' 1A 2B 2B 3C | sed -n '$!N; /^\(.*\)\n\1$/!l; D'
1A\n2B$
2B\n3C$
3C$
  1. sed 將第一行讀入模式空間(即為 1A)。

  2. 在每個程序的開始,命令 $!N 在非最後行時新增至模式空間(第一個流程即為 1A\n2B)。

  3. /^\(.*\)\n\1$/ 將兩組文字以 \n 區分,匹配兩組相同文字的「行號」,!l 行號不匹配時執行明確列印(第一個流程列印 1A\n2B$,若命令為 P 則列印 1A)。

  4. 命令 D 刪除模式空間內的第一行(在第一個流程結束時模式空間留下 2B),然後「重啟程序」。

  5. 在第二個流程中命令 $!N 新增模式空間(例如 2B\n2B),行號匹配不執行 !l,命令 D(模式空間留下 2B)。

  6. 在第三個流程中命令 $!N 新增模式空間(例如 2B\n3C),行號不匹配執行 !l(第三個流程列印 2B\n3C$),命令 D(模式空間留下 3C)。

  7. 在第四個流程中,已為最後一行不執行 $!N(模式空間為 3C),行號不匹配執行 !l(第四個流程列印 3C$)。

  8. 執行命令 D 時,模式空間不含換行字元為一般循環並且清除模式空間,sed 結束程序。

一個包含人名、年齡的記錄,列印年齡為 38 的人名
(
cat << EOF
Name:John
Year-old:38

Name:Marry
Year-old:43

Name:Helen
Year-old:38
EOF
) |
sed -n '/Name/ {
     N
     /Year-old:38/ !d
     P
}'
Name:John
Name:Helen
  1. /Name/ 匹配行號 Name

  2. N 新增模式空間(第一個流程即為 Name:John\nYear-old:38)。

  3. /Year-old:38/ 匹配行號年齡為 38,若不是則執行 d 重啟循環。

  4. P 列印模式空間的第一行(第一個流程列印 Name:John)。

  5. 在下一個循環中,年齡為 43,行號不匹配,執行 d 重啟循環。


空間操作

sed 空間操作命令

h

將模式空間複製到保持空間 hold (copy)。

H

將模式空間新增至保持空間 Hold (append)。

g

將保持空間複製到模式空間 get pattern space (copy)。

G

將保持空間新增至模式空間 Get pattern space (append)。

x

交換空間 (交換保持空間和模式空間互換) exchange。

空間操作命令補充
H 命令

將模式空間新增至保持空間,另一種說法是「新增換行至保持空間,再將模式空間新增至保持空間」。
保持空間的結果為:「目前 (上次) 的保持空間」 + \n + 模式空間」。

G 命令

將保持空間新增至模式空間,另一種說法是「新增換行至模式空間,再將保持空間新增至模式空間」。
模式空間的結果為:「目前 (上次) 的模式空間」 + \n + 保持空間」。

空間操作示例
echo -e '1\n\n3\n4\n5' | sed -n '/./{H;$!d}; x; l'
\n1$
\n3\n4\n5$

第一個表示式 /./{H;$!d} 對所有具值的 (非空) 行執行「命令組」,將 (當前的) 模式空間新增到保持空間,非最後一行時刪除模式空間並重新循環。
「選擇行號」後面只能接一個命令,採用「{} 命令組」,將多個命令「組合」成一個。

  1. 第 1 行具值,模式空間為 1 新增到保持空間 (H) 為 \n1 (保持空間初始為空,新增至下一行)。非最後行 ($!),由 d 刪除模式空間並重啟循環。

  2. 第 2 行是空行,第一個表示式不會執行動作。
    再來 sed 執行交換空間 (x),將保持空間 (\n1) 跟模式空間 (NUL) 互換,互換後;保持空間為 NUL,模式空間為 \n1
    再明確列印模式空間 (l) 為 \n1$

  3. 第 3 行具值,模式空間為 3,命令 H 新增保留空間後為 \n3 (在前一步驟保持空間為 NUL),非最後行重啟循環。

  4. 第 4 行具值,模式空間為 4,命令 H 新增保留空間後為 \n3\n4,非最後行重啟循環。

  5. 第 5 行具值,模式空間為 5,命令 H 新增保留空間後為 \n3\n4\n5,最後行不重啟循環。

  6. 再來 sed 執行 x; l 輸出 \n3\n4\n5$ 後結束程序。

將上述的示例修改如下
echo -e '1\n\n3\n4\n5' | sed '/./{H;$!d}; x; s/^/\nSTART-->/; s/$/\n<--END/'
結果為按空白行分段
START-->
1
<--END

START-->
3
4
5
<--END

如何倒序?
採用 G 將保持空間新增至模式空間,當保持空間是前一行時,新增後則為反排。(先新增再保留)。

先確認想法,是否可行
seq 3 |  sed -n 'G;h;l' (1)
1 在測試 sed 的命令時,跟前例一樣;以 l 明確列印比較清楚。
1\n$
2\n1\n$
3\n2\n1\n$

但每次列印,並不正確。

改成最後一行才印
seq 3 |  sed -n 'G;h;$l'
3\n2\n1\n$

大致正確,但上述的第一行也加入換行 (因為是反排,所以是最後字元)。

改成,只在第二筆 (含) 以後才新增至模式空間,也就是第一筆不新增。
seq 3 |  sed -n '1! G; h; $l'
3\n2\n1$
或者正向思考,第一行只做保留,第二行以後;新增後再保留,結果也一樣。
seq 3 |  sed -n '1h; 2,${G;h}; $l'

分支

分支 (branch),意思是跳躍 (jump) 或者是 go to。

sed 分支命令

:label

定義 label 為一個或多個字母作為標籤

b

(無條件) 分支。

t

s 命令合乎匹配時分支。

T

s 命令不匹配時分支。

btT 命令後面可接標籤,命令跟標籤之間可為 0 個或多個空白。分支至標籤時依據指令執行動作,但如果省略標籤,則分支命令會重啟循環 (sed 的預設動作會讀取新的輸入行)。

千位分隔符號範例
確定替換命令 (s) 正確處理千位符號
echo '12345678' | sed    's/\B[0-9]\{3\}\b/,&/' &&\
echo '12345678' | sed -E 's/\B[0-9]{3}\b/,&/' &&\
echo '123'      | sed    's/\B[0-9]\{3\}\b/,&/' &&\
echo '123'      | sed -E 's/\B[0-9]{3}\b/,&/'
12345,678
12345,678
123
123
以分支完成所有的千位符號
echo '12345678' | sed    ':X s/\B[0-9]\{3\}\b/,&/; t X' &&\
echo '12345678' | sed -E ':X s/\B[0-9]{3}\b/,&/; t X'
12,345,678
12,345,678
流程如下:
  1. 第一次匹配 678,模式空間為 12345,678,合乎匹配分支到 (t) 標籤 X,繼續匹配。

  2. 第二次匹配 345,模式空間為 12,345,678,合乎匹配分支到標籤 X 並繼續。

  3. 第三次不合乎匹配,不分支並結束程序。

以分支執行匹配條件錯誤範例
echo '12345678' | sed ':X s/[0-9]{3}\b/,&/; t X' --debug
流程如下:
  1. 第 1 次匹配 678,模式空間為 12345,678,合乎匹配分支到 (t) 標籤 X,繼續匹配。

  2. 第 2 次匹配 345,模式空間為 12,345,678,合乎匹配分支到標籤 X 並繼續。

  3. 第 3 次匹配 345,模式空間為 12,,345,678,合乎匹配分支到標籤 X 並繼續。

  4. 第 4 次匹配 345,模式空間為 12,,,345,678,合乎匹配分支到標籤 X 並繼續。

  5. sed 一直在執行沒有結束,這表示了發生了「無窮迴圈 (infinite loop)」,可採用命令列選項 --debug 來偵錯。

由上述的第 3 次流程中已知,匹配條件有錯誤。當發生無窮迴圈時,應先確定替換命令 (s) 有正確處理。

替換命令匹配條件錯誤 (參閱:正規表示法-邊界):
echo ',345,' | sed 's/[0-9]{3}\b/,&/'
,,345,

將所有行合併至模式空間。

seq 3 | sed -n ':X; N; $! bX; l'
1\n2\n3$
  1. :X 建立標籤 X

  2. N 下一行新增至模式空間

  3. $! 非最後一行,bX (無條件) 分支到標籤 X,繼續執行指令。

上述的範例,即為命令列選項 -z
seq 3 | sed -nz 'l'
1\n2\n3\n$
將換行替換成 ,
seq 3 | sed -z           's/\n/,/g' &&\
seq 3 | sed ':X; N; $!bX; s/\n/,/g'
1,2,3,1,2,3

讀寫命令 (rRwW)

sed 讀寫命令

r

讀取整個檔案,插入到輸出流中。

R

依次讀取檔案一行,插入到輸出流中。

w

將模式空間寫入檔案。

W

將模式空間內的第一行寫入檔案。

rR

在循環結束時或在讀取下一個輸入時,將檔案內容插入到輸出流中。

  • 不會影響模式空間

  • 一律列印,忽略命令列選項 -n

  • 若無法讀取檔案,則將其視為一個空檔,並不會出現錯誤。

讀取整個檔案 (命令 r),如輸出 /etc/passwd 3 次
seq 3 | sed -n 'r /etc/passwd'
依次讀取檔案一行 (命令 R),如輸出 /etc/passwd 前 3 行
seq 3 | sed -n 'R /etc/passwd'
wW

開啟的檔案會不斷新增,直到 sed 命令結束。

將模式空間寫入 (命令 w) 到標準輸出
seq 3 | sed -n 'N; N; w /dev/stdout'
1
2
3
將模式空間內的第一行寫入 (命令 W) 到標準輸出,如模式空間每次讀取 3 行:
seq 9 | sed -n 'N; N; W /dev/stdout'
1
4
7
採用命令 w 輸出 1 4 7。
seq 9 | sed -n -e 'w /dev/stdout' -e 'n;n'

在檔案名稱之後的命令,必須使用選項 -e 分開表示式。

測試「特殊字元」轉譯

確定哪些「特殊字元」在 BRE、ERE 需要以跳脫字元 (\) 轉譯。

選一個確定不需要轉譯的字元
specChar='a'
確定 bash 變數轉換正確:
echo "3${specChar}14" \| sed -e "s/3\\${specChar}14/3\.14159/" &&\
echo "3${specChar}14" \| sed -e "s/3${specChar}14/3.14159/" &&\
echo "3${specChar}14" \| sed -E "s/3\\${specChar}14/3\.14159/" &&\
echo "3${specChar}14" \| sed -E "s/3${specChar}14/3.14159/"
3a14 | sed -e s/3\a14/3\.14159/
3a14 | sed -e s/3a14/3.14159/
3a14 | sed -E s/3\a14/3\.14159/
3a14 | sed -E s/3a14/3.14159/
測試命令:
echo "3${specChar}14" | sed -e "s/3\\${specChar}14/3\.14159/" &&\
echo "3${specChar}14" | sed -e "s/3${specChar}14/3.14159/" &&\
echo "3${specChar}14" | sed -E "s/3\\${specChar}14/3\.14159/" &&\
echo "3${specChar}14" | sed -E "s/3${specChar}14/3.14159/"
測試結果:
3a14    (BRE 轉譯)
3.14159 (BRE 不轉譯)
3a14    (ERE 轉譯)
3.14159 (ERE 不轉譯)
BRE ERE 不轉譯跟轉譯後相同的字元「! @ # % & , - " =」:
specChar='!'
specChar='@'
specChar='#'
specChar='%'
specChar='&'
specChar='_'
specChar=','
specChar='-'
specChar='"'
specChar='='

3.14159 (BRE 轉譯)
3.14159 (BRE 不轉譯)
3.14159 (ERE 轉譯)
3.14159 (ERE 不轉譯)
BRE ERE 一律要轉譯的字元「. $ * ^ / \ [ ]」:
specChar='.'

3.14159 (BRE 轉譯)
3.14159 (BRE 不轉譯)
3.14159 (ERE 轉譯)
3.14159 (ERE 不轉譯)

不轉譯也正確,但因為「.」為匹配字元,應納入「BRE ERE 一律要轉譯的字元」。

specChar='$'

3.14159 (BRE 轉譯)
3.14159 (BRE 不轉譯)
3.14159 (ERE 轉譯)
3$14    (ERE 不轉譯)

specChar='*'

3.14159
3*3.14159
3.14159
3*3.14159

specChar='^'

3.14159
3.14159
3.14159
3^14

specChar='/' (定界字元)

3.14159 (BRE 轉譯)
Error   (BRE 不轉譯)
3.14159 (ERE 轉譯)
Error   (ERE 不轉譯)

specChar='\'

3.14159
Error
3.14159
Error

specChar='['

3.14159
Error
3.14159
Error

specChar=']'

3.14159
3.14159
3.14159
3.14159

BRE ERE 不轉譯跟轉譯後結果相同,但為了配合「[」,將「]」納入「BRE ERE 一律要轉譯的字元」。
測試轉譯斜線 (/),將 sed 的定界字元改為 _
specChar='/'

echo "3${specChar}14" | sed -e "s_3\\${specChar}14_3\.14159_" &&\
echo "3${specChar}14" | sed -e "s_3${specChar}14_3.14159_" &&\
echo "3${specChar}14" | sed -E "s_3\\${specChar}14/3_.14159_" &&\
echo "3${specChar}14" | sed -E "s_3${specChar}14_3.14159_"
3.14159 (BRE 轉譯)
3.14159 (BRE 不轉譯)
3/14    (ERE 轉譯)
3.14159 (ERE 不轉譯)
BRE ERE 一律不轉譯的字元「` ' < >」:
specChar='`'

3`14    (BRE 轉譯)
3.14159 (BRE 不轉譯)
3`14    (ERE 轉譯)
3.14159 (ERE 不轉譯) 

specChar="'"

3'14
3.14159
3'14
3.14159

specChar='<'

3<14
3.14159
3<14
3.14159

specChar='>'

3>14
3.14159
3>14
3.14159
BRE 不轉譯 ERE 要轉譯的字元 「+ ? | ( ) { }」:
specChar='+'

3+14    (BRE 轉譯)
3.14159 (BRE 不轉譯)
3.14159 (ERE 轉譯)
3+14    (ERE 不轉譯)

specChar='?'

3?3.14159
3.14159
3.14159
3?3.14159

specChar='|'

3.14159|14
3.14159
3.14159
3.14159|14

specChar='('

Error
3.14159
3.14159
Error

specChar=')'

Error
3.14159
3.14159
Error

specChar='{'

Error
3.14159
3.14159
Error

specChar='}'

3.14159
3.14159
3.14159
3.14159

BRE ERE 不轉譯跟轉譯後結果相同,但為了配合「{」,將「}」納入「BRE 不轉譯 ERE 要轉譯的字元」。