學(xué)習(xí)《玩透sed》基礎(chǔ)篇

通過學(xué)習(xí)《玩透sed》整理

作者: 駿馬金龍
學(xué)習(xí)鏈接: https://www.junmajinlong.com/how_to_nav_posts/
學(xué)習(xí)來源: 駿馬金龍

整體的思維導(dǎo)圖查看

sed入門

sed的基本概率

sed是一個流式的編輯程序,它讀取輸入流(可以是文件、標(biāo)準(zhǔn)輸入)的每一行放進(jìn)模式空間 (pattern space),
同時將此行行號通過 sed 行號計數(shù)器記錄在內(nèi)存中,然后對模式空間中 的行進(jìn)行模式匹配,如果能匹配上則使用 sed 程序內(nèi)部的命令進(jìn)行處理,
處理結(jié)束后,從模式空間中輸出(默認(rèn))出去,并清空模式空間,
隨后再從輸入流中讀取下一行到模式空間 中進(jìn)行相同的操作,
直到輸入流中的所有行都處理完成。由此可見,sed 是一個循環(huán)一個 循環(huán)處理內(nèi)容的。

一次sed處理的過程:
1. 讀取輸入流的一行到模式空間。
2. 對模式空間中的內(nèi)容進(jìn)行匹配和處理。 
3. 自動輸出模式空間內(nèi)容。
4. 清空模式空間內(nèi)容。
5. 讀取輸入流的下一行到模式空間。
PS:如果是文件會一次性加載一定量的到os buffer之中然后再一行行的讀取,如果使用的是管道或者其他的輸入流,就是直接從對應(yīng)的緩存之中再一行行的讀取。

我們在這個過程中可以通過我們命令所改變的是第二步,其他步驟都是固定的模式。
但是sed的3,4步可以通過sed的幾個命令以及選項進(jìn)行改變,使其輸出空或者無法清空模式空間。

語法格式:
sed OPTIONS SCRIPT INPUT_STREAM

SCPIPT部分就是sed腳本,它是 sed 內(nèi)部命令的集合,sed中的命令有些奇 特,它包含行匹配以及要執(zhí)行的命令。
格式為 ADDR1[,ADDR2]cmd_list。例如,要對第2行執(zhí)行刪除命令,其命令為 sed 2d filename,只輸出第 4 行到 6 行,其命令為 sed -n 4,6p。

因為SCRIPT是命令的集合所以循環(huán)可以修改為:
1. 讀取輸入流的一行到模式空間。
2. 對模式空間中內(nèi)容執(zhí)行SCRIPT。(包括上面示例中的"2d"和"4,6p") 
3. 讀取輸入流的下一行到模式空間。
4. 對模式空間中內(nèi)容執(zhí)行SCRIPT。

SCRIPT部分除了包含了 sed 命令行中的內(nèi)部命令,還包括兩個特殊動作:自動輸出和清 空模式空間內(nèi)容。這兩個動作是一定會執(zhí)行的,但是有些命令也可以改變讓這兩個命令不進(jìn)行執(zhí)行。

用shell程序?qū)崿F(xiàn)過程就是這樣:
for ((line=1;line<=last_line_num;++line))
do    
    read $line to pattern_space; while pattern_space is not null
    do
        execute cmd1 in SCRIPT; 
        execute cmd2 in SCRIPT; 
        execute cmd3 in SCRIPT; 
        ......
        auto_print; 
        remove_pattern_space;
    done
done

在for循環(huán)中讀取下一行和執(zhí)行 SCRIPT 循環(huán)。而在SCRIPT 循環(huán)中就是script語句的執(zhí)行以及對模式空間的清除。

sed的命令行書寫也就是script部分的書寫。
# 一行式。多個命令使用分號分隔 sed Address{cmd1;cmd2;cmd3...}
# 多個表達(dá)式時,可以使用"-e"選項,也可以不用,但使用分號分隔
sed Address1{cmd1;cmd2;cmd3};Address2{cmd1;cmd2;cmd3}...
sed -e 'Address1{cmd1;cmd2;cmd3}' -e 'Address2{cmd1;cmd2;cmd3}' ...
# 分行寫時
sed Address1{
    cmd1
    cmd2
    cmd3
} Address2{
    cmd1
    cmd2
    cmd3
}
如果寫在文件中即sed本
#!/usr/bin/sed -f 
#注釋行 
Address1{cmd1;cmd2...} 
Address2{cmd1;cmd2...} 
......

其中 cmd 部分還可以進(jìn)行模式匹配,也即類似于 Address{{pattern1}cmd1;{pattern2}cmd2}的寫法。例如,/^abc/{2d;p}。

sed 選項

可能用到的幾個選項:
'-n'
默認(rèn)情況下,sed 將在每輪 script 循環(huán)結(jié)束時自動輸出模式空間中的內(nèi)容。使用該選項 后可以使得這次自動輸出動作輸出空內(nèi)容,而不是當(dāng)前模式空間中的內(nèi)容。
注意,"-n"是 輸出空內(nèi)容而不是禁用輸出動作,雖然兩者的結(jié)果都是不輸出任何內(nèi)容,但在有些依賴于 輸出動作和輸出流的地方,
它們的區(qū)別是很大的,前者有輸出流,只是輸出空流,后者則 沒有輸出流。
'-e SCRIPT'
前文說了,SCRIPT 中包含的是命令的集合,"-e"選項就是向 SCRIPT 中添加命令的???以省略"-e"選項,但如果命令行容易產(chǎn)生歧義,則使用"-e"選項可明確說明這部分是 SCRIPT 中的命令。
另外,如果一個"-e"選項不方便描述所需命令集合時,可以指定多個"- e"選項。
'-f SCRIPT-FILE'
指定包含命令集合的 SCRIPT 文件,讓 sed 根據(jù) SCRIPT 文件中的命令集處理輸入流。
'-i[SUFFIX]'
該選項指定要將 sed 的輸出結(jié)果保存(覆蓋的方式)到當(dāng)前編輯的文件中。
GNU sed 是通過創(chuàng)建一個臨時文件并將輸入寫入到該臨時文件,然后重命名為源文件來實現(xiàn)的。
當(dāng)當(dāng)前輸入流處理結(jié)束后,臨時文件被重命名為源文件的名稱。如果還提供了 SUFFIX, 則在重命名臨時文件之前,
先使用該 SUFFIX修改源文件名,從而生成一個源文件的備份 文件
臨時文件總是會被重命名為源文件名稱,也就是說輸入流處理結(jié)束后,仍使用源文件名的文件是sed 修改后的文件。文件名中包含了 SUFFIX 的文件則是最原始文件的備份。
例如:源文件為 a.txt,sed -i'.log' SCRIPT a.txt 將生成兩個文件:a.txt 和 a.txt.log,前者 是 sed 修改后的文件,a.txt.log 是源 a.txt 的備份文件。

重命名的規(guī)則如下:如果擴(kuò)展名不包含符號"*",將 SUFFIX添加到原文件名的后面當(dāng)作文 件后綴;如果 SUFFIX中包含了一個或多個字符"*",則每個"*"都替換為原文件名。這使得 你可以為備份文件添加一個前綴,而不是后綴。如果沒有提供 SUFFIX,源文件被覆蓋, 且不會生成備份文件。
該選項隱含了"-s"選項。
'-r'
使用擴(kuò)展正則表達(dá)式,而不是使用默認(rèn)的基礎(chǔ)正則表達(dá)式。sed 所支持的擴(kuò)展正則表達(dá)式 和 egrep 一樣。使用擴(kuò)展正則表達(dá)式顯得更簡潔,因為有些元字符不用再使用反斜線"\"。 正則表達(dá)式見 grep 命令中文手冊。
'-s'
默認(rèn)情況下,如果為 sed 指定了多個輸入文件,如 sed OPTIONS SCRIPT file1 file2 file3,
則多個文件會被 sed 當(dāng)作一個長的輸入流,也就是說所有文件被當(dāng)成一個大文 件。
指定該選項后,sed將認(rèn)為命令行中給定的每個文件都是獨立的輸入流。
既然是獨立的輸入流,范圍定址(如/abc/,/def/)就無法跨越多個文件進(jìn)行匹配,行號也會在處理每個文件時重置,
"$"代表的也將是每個文件的最后一行。這也意味著,如果不使 用該選項,則這幾個行為都是可以完成的。

語句練習(xí)

1.只輸出 a.txt 中的第 5 行。
sed -n 5p a.txt
sed -n -e '5p' a.txt

2.輸出 a.txt,并輸出每行的行號。
sed '=' a.txt
3.分別輸出a.txt b.txt并分別保存到".bak"后綴的文件中。
sed -i'*.bak' -n '5p' a.txt b.txt
此處需要使用-s 但是-i里面已經(jīng)包含了-s 如果不使用-s會是他們兩者合并的第5行文件 
4.使用擴(kuò)展正則表達(dá)式,輸出 a.txt 和 b.txt 中能包含 "zip"的行。
sed -r -n '/zip+/p' a.txt b.txt

定址表達(dá)式

當(dāng) sed 將輸入流中的行讀取到模式空間后,就需要對模式空間中的內(nèi)容進(jìn)行匹配,如果能 匹配就能執(zhí)行對應(yīng)的命令,
如果不能匹配就直接輸出、清空模式空間并進(jìn)入下一個 sed 循 環(huán)讀取下一行。
匹配的過程稱為定址。定址表達(dá)式有多種,但總的來說,其格式為[ADDR1][ADDR2]。

定址表達(dá)式的三種方式:
1. ADDR1 和 ADDR2 都省略時,表示所有行都能被匹配上。
2. 省略ADDR2時,表示只有被ADDR1表達(dá)式匹配上的行才符合條件。
3. 不省略ADDR2時,是范圍地址。表示從ADDR1匹配成功的行開始,到ADDR2匹配成功的行結(jié)束

無論是 ADDR1 還是 ADDR2,都可以使用兩種方式進(jìn)行匹配:行號和正則表達(dá)式。如下:

'N'
指定一個行號,sed 將只匹配該行。(需要注意,除非使用了"-s"或"-i"選項,sed 將對所有 輸入文件的行連續(xù)計數(shù)。)\
'FIRST~STEP'
表示從第 FIRST 行開始,每隔 STEP 行就再取一次。也就是取行號滿足 FIRST+(N*STEP) (其中 N>=0)的行。因此,要選擇所有奇數(shù)行,使用"1~2";\
要從第 2 行開始每隔 3 行取一 次,使用"2~3";要從第 10 行開始每隔 5 行取一次,使用"10~5";而"50~0"則表示只取第 50 行。
'$'
默認(rèn)該符號匹配的是最后一個文件的最后一行,如果指定了"-i"或"-s",則匹配的是每個文 件的最后一行。\
總之,"$"匹配的是每個輸入流的最后一行
se d 采用行號計數(shù)器來臨時記錄當(dāng)前行的行號,因此 sed 在讀取到最后一行前即使是倒數(shù)第二行的時候,完全不知道最后一行是第幾行,
所以代表最后一行的"$"無法進(jìn)行任何數(shù)學(xué)運算,例如倒數(shù)第二行使用"$-1"表示是錯誤的。
而且,"$"只是一個額外的 標(biāo)記符號,當(dāng) sed讀取到輸入流的最后一行時,發(fā)現(xiàn)這就是最后一行,于是為此行打上 "$"記號,并讀取到模式空間中。

'/REGEXP/'
將選擇能被正則表達(dá)式 REGEXP 匹配的所有行。如果 REGEXP 中自身包含了字符"/",則 必須使用反斜線轉(zhuǎn)義,即"\/"。
'/REGEXP/I'
和"/REGEXP/"是一樣的,只不過匹配的時候不區(qū)分大小寫。
'\%REGEXP%'
('%'可以使用其他任意單個字符替換。) 這和上一個定址表達(dá)式的作用是一樣的,只不過是 使用符號"%"替換了符號"/"。
當(dāng) REGEXP 中包含"/"符號時,使用該定址表達(dá)式就無需對"/" 使用反斜線"\"轉(zhuǎn)義。
但如果此時 REGEXP 中包含了"%"符號時,該符號需要使用"\"轉(zhuǎn)義。 
總之,定址表達(dá)式中使用的分隔符在 REGEXP 中出現(xiàn)時,都需要使用反斜線轉(zhuǎn)義。
'ADDR1,+N'
匹配 ADDR1 和其后的 N 行。

'ADDR1,~N'
匹配 ADDR1 和其后的行直到出現(xiàn) N 的倍數(shù)行。倍數(shù)可為隨意整數(shù)倍,只要 N 的倍數(shù)是最 接近且大于 ADDR1 的即可。
如 ADDR1=1,N=3 匹配 1-3 行,ADDR1=5,N=4 匹配 5-8 行。 而"1,+3"匹配的是第一行和其后的 3 行即 1-4 行。
另外,在定址表達(dá)式的后面加"! "符號表示反轉(zhuǎn)匹配的含義。也就是說那些匹配的行將不被 選擇,而是不匹配的行被選擇。
定址的示例
sed -n '3p' INPUTFILE
sed -n '3,5!p' INPUTFILE
sed -n '3,/^# .*/! p' INPUTFILE
sed -n '/abc/,/xyz/p' INPUTFILE
sed -n '!p' INPUTFILE # 這個有悖常理,但確實是允許的

常用命令

強(qiáng)制輸出命令"p"

for ((line=1;line<=last_line_num;++line))
do
done done
    read $line to pattern_space; 
    while pattern_space is not null
    do
        execute cmd1 in SCRIPT; 
        execute cmd2 in SCRIPT; 
        ADDR1,ADDR2{print}; #"P"command
        ......
        auto_print; # '-n'影響的
        remove_pattern_space;
    done
done

在處理過程中這個是兩個動作一個是p命令的輸出一個是auto_print的輸出,使用'-n'就可以不讓 auto_print進(jìn)行輸出。\

僅輸出標(biāo)準(zhǔn)輸入的第 2 行內(nèi)容。
[root@VM-0-6-centos script]# echo -e 'abc\nxyz' | sed -n 2p
xyz
如果不用 -n 這里 auto_print會自動輸出一遍所以可以輸出兩遍
[root@VM-0-6-centos script]# echo -e 'abc\nxyz' | sed  2p
abc
xyz
xyz

刪除命令"d"

for ((line=1;line<=last_line_num;++line))
do
done done
    read $line to pattern_space; 
    while pattern_space is not null
    do
        execute cmd1 in SCRIPT; 
        execute cmd2 in SCRIPT; 
        ADDR1,ADDR2{delete;break}; #"D"command
        ......
        auto_print; # '-n'影響的
        remove_pattern_space;
    done
done

注意這里使用'd'命令時,會直接跳出script循環(huán)如果delete命令后還有其他命令時都不會被執(zhí)行了。
例如:刪除a.txt 第五行并保存原文件之中。

sed -i '5d' a.
這里不能使用重定向的方式保存,因為重定向是在 sed 命令執(zhí)行前被 shell 執(zhí)行的,所以 會截斷 a.txt,使得 sed 讀取的輸入流為空,\
或者結(jié)果出乎意料之外。而"-i"選項則不會操作原文件,而是生成臨時文件并在結(jié)束時重命名為原文件名。

刪除 a.sh 中包含"#"開頭的注釋行,但第一行的#!/bin/bash 不刪除。

sed '/^#/{1!d}' a.sh

如果"d"后面還有命令,在刪除模式空間后,這些命令不會執(zhí)行,因為會立即退出當(dāng)前 SCRIPT 循環(huán)。例如:

echo -e 'abc\nxyz' | sed '{/abc/d;=}' 
2
xyz

其中"="這個命令用于輸出行號,但是結(jié)果并沒有輸出被"abc"匹配的行的行號。

退出 sed 程序命令"q"和"Q"

使用"q"和"Q"命令的作用是立即退出當(dāng)前 sed 程序,使其不再執(zhí)行后面的命令,也不再讀 取后面的行。

# "q"命令
for ((line=1;line<=last_line_num;++line))
do
    read $line to pattern_space; 
    while pattern_space is not null
    do
        execute cmd1 in SCRIPT; 
        execute cmd2 in SCRIPT; 
        ADDR1,ADDR2{auto_print;exit}; # "q" command
        ......
        auto_print;
        remove_pattern_space;

    done 
done
# "Q"命令
for ((line=1;line<=last_line_num;++line))
do
    read $line to pattern_space; 
    while pattern_space is not null
    do
        execute cmd1 in SCRIPT; 
        execute cmd2 in SCRIPT; 
        ADDR1,ADDR2{exit}; # "Q" command
        ......
        auto_print; 
        remove_pattern_space;

    done 
done

例如,搜索腳本 a.sh,當(dāng)搜索到使用了"."或"source"命令加載環(huán)境配置腳本時就輸出并立 即退出。

sed -n -r '/^[ \t]*(\.|source) /{p;q}' a.sh

輸出行號命令

"="命令用于輸出最近被讀取行的行號。在 sed 內(nèi)部,使用行號計數(shù)器進(jìn)行行號計數(shù),每讀 取一行,行號計數(shù)器加 1。
計數(shù)器的值存儲在內(nèi)存中,在要求輸出行號時,直接插入在輸 出流中的指定位置。由于值是存在于內(nèi)存中,而非模式空間中,因此不受"-n"選項的影 響。
這是一個依賴于輸出流的命令,只要有輸出動作就會追加在該輸出流的尾部。
例如,搜索出 httpd.conf 中"DocumentRoot"開頭的行的行號,允許有前導(dǎo)空白字符。

sed -n '/^[ \t]*DocumentRoot/{p;=}' 
httpd.conf DocumentRoot "/var/www/html"
119

字符一一對應(yīng)替換命令"y"

該命令與tr命令映射功能一樣都是字符進(jìn)行一一替換
例如,將 a.txt 中包含大寫字母的 YES、Yes 等替換成小寫的 yes。

sed 'y/YES/yes/' a.txt

手動讀取下一行命令"n

在 sed 的循環(huán)過程中,每個 sed 循環(huán)的第一步都是讀取輸入流的下一行到模式空間中,這 是我們無法控制的動作。但 sed 有讀取下一行的命令"n"。
由于是讀取下一行,所以它會觸發(fā)自動輸出的動作,于是就有了輸出流。不僅如此,還應(yīng)
該記住的是:只要有讀取下一行的行為,在其真正開始讀取之前一定有隱式自動輸出的行

但需注意,當(dāng)沒有下一行可供"n"讀取時(例如文件的最后一行已經(jīng)被讀取過了),將輸出模 式空間內(nèi)容后直接退出 sed 程序,使得"n"命令后的所有命令都不會執(zhí)行,即使是那兩個隱 含動作。

for ((line=1;line<=last_line_num;++line))
do
    read $line to pattern_space; 
    while pattern_space is not null
    do
        execute cmd1 in SCRIPT;
        execute cmd2 in SCRIPT;
        ADDR1,ADDR2{ # "n" command
            if [ "$line" -ne "$last_line_num" ];then 
                auto_print;
                remove_pattern_space;
                read next_line to pattern_space;
            else
                auto_print; 
                remove_pattern_space; 
                exit;
            fi
        };
        ......
        auto_print; 
        remove_pattern_space;
    done 
done

注意,是先判斷是否有下一行可讀取,再輸出和清空 pattern space 中的內(nèi)容,所以 then 和 else 語句中都有這兩個動作。 也許感覺上似乎更應(yīng)該像下面這樣的優(yōu)化形式:

ADDR1,ADDR2{ # "n" command 
    auto_print;
    remove_pattern_space;
    [ "$line" -ne "$last_line_num" ] && read next_line to pattern_space || exit;
};

事實并非如上的代碼
例如,搜索 a.txt 中包含"redirect"字符串的行以及其下一行,并輸出。

sed -n '/redirect/{p;n;p}' a.txt
echo -e "abc\ndef\nxyz" | sed '/abc/{n;=;p}'
abc
2
def
def xyz
從結(jié)果中可以分析出,"n"讀取下一行前輸出了"abc",然后立即讀入了下一行,所以輸出 的行號是 2 而不是 1,
因為這時候行號計數(shù)器已經(jīng)讀取了下一行,隨后命令"p"輸出了該模 式空間的內(nèi)容,輸出后還有一次自動輸出的隱含動作,所以"def"被輸出了兩次

替換命令"s"

將匹配到的內(nèi)容替換成指定的內(nèi)容。

"s"命令的語法格式為:其中"/"可以替換成任意其他單個字符。
s/REGEXP/REPLACEMENT/FLAGS
它使用 REGEXP 去匹配行,將匹配到的那部分字符替換成 REPLACEMENT。FLAGS 是 "s"命令的修飾符,常見的有"g"、"p"和"i"或"I"。
    ? "g":表示替換行中所有能被 REGEXP 匹配的部分。不使用 g 時,默認(rèn)只替換行中 的第一個匹配內(nèi)容。此外,"g"還可以替換成一個數(shù)值 N,表示只替換行中第 N 個 被匹配的內(nèi)容。
    ? "p":輸出替換后模式空間中的內(nèi)容。
    ? "i"或"I":REGEXP 匹配時不區(qū)分大小寫。
REPLACEMENT 中可以使用"\N"(N 是從 1 到 9 的整數(shù))進(jìn)行后向引用,所代表的是 REGEXP第N個括號(...)中匹配的內(nèi)容。另外,REPLACEMENT中可以包含未轉(zhuǎn)義的"&"
符號,這表示引用 pattern space 中被匹配的整個內(nèi)容。需要注意,"&"是引用 pattern space 中的所有匹配,不僅僅只是括號的分組匹配。

例如,刪除 a.sh 中所有"#"開頭(可以包括前導(dǎo)空白)的注釋符號"#",但第一行"#! /bin/bash" 不處理。

sed -i '2,$s/^[ \t]*#//' a.sh

為 a.sh 文件中的第 5 行到最后一行的行首加上注釋符號"#"。

sed '5,$s/^/#/' a.sh

將 a.sh 中所有的"int"單詞替換成"SIGINT"

sed 's/\bint\b/SIGINT/g' a.sh

將 a.sh 中"cmd1 && cmd2 || cmd3"的 cmd2 和 cmd3 命令對調(diào)個位置。

sed 's%&&\(.*\) ||\(.*\)%\&\&\2 ||\1%' a.
這里使用了"%"代替"/",且在 REPLACEMENT 部分對"&"進(jìn)行了轉(zhuǎn)義,因為該符號在 REPLACEMENT 中時表示的是引用 REGEXP 所匹配的所有內(nèi)容。

追加、插入和修改命令"a"、"i"、"c"。

這 3 個命令的格式是"[a|i|c] TEXT",表示將 TEXT 內(nèi)容隊列化到內(nèi)存中,當(dāng)有輸出流或者 說有輸出動作的時候,半路追上輸出流,分別追加、插入和替換到該輸出流然后輸出。
追 加是指追加在輸出流的尾部,插入是指插入在輸出流的首部,替換是指將整個輸出流替換掉。
"c"命令和"a"、"i"命令有一絲不同,它替換結(jié)束后立即退出當(dāng)前 SCRIPT 循環(huán),并進(jìn)入下一個sed 循環(huán),因此"c"命令后的命令都不會被執(zhí)行。

例如:

echo -e "abc\ndef" | sed '/abc/a xyz' 
abc
xyz
def
echo -e "abc\ndef" | sed '/abc/i xyz' 
xyz
abc
def
echo -e "abc\ndef" | sed '/def/i xyz' 
abc
xyz
def
echo -e "abc\ndef" | sed '/def/c xyz' 
abc
xyz
echo -e "abc\ndef" | sed '/abc/c xyz' 
xyz
def

其實"a"、"i"和"c"命令的 TEXT 部分寫法是比較復(fù)雜的,如果 TEXT 只是幾個簡單字符,如 上即可。
但如果要 TEXT 是分行文本,或者包含了引號,或者這幾個命令是寫在"{}"中的,則上面的寫法就無法實現(xiàn)。
需要使用符號""來轉(zhuǎn)義行尾符號,這表示開啟一個新行,此后 輸入的內(nèi)容都是 TEXT,直到遇到引號或者";"開頭的行時\

例如,在 a.sh 的#!/bin/bash 行后添加一個注釋行"# Script filename: a.sh"以及一個空 行。由于是追加在尾部,所以使用"a"命令。

sed '\%#!/bin/bash%a\# Script filename: a.sh\n' a.sh

"a"命令后的第一個反斜線用于標(biāo)記 TEXT 的開始,"\n"用于添加空白行。如果分行寫,或
者"a"命令寫在大括號"{}"中,則格式如下:

sed '\%#!/bin/bash%a\
# Script filename: a.sh\n ' a.sh

sed '\%#!/bin/bash%{p;a\ # Script filename: a.sh\n ;p}' a.sh
最后需要說的是,這 3 個命令的 TEXT 是存放在內(nèi)存中的,不會進(jìn)入模式空間,因此不受 "-n"選項或某些命令的影響。
此外,這 3 個命令依賴于輸出流,只要有輸出動作,不管是 空輸出流還是非空的輸出流,只要有輸出,這幾個命令就會半路"劫殺"。
如果不理解這兩句話,這 3個命令的結(jié)果有時可能會比較疑惑。

例如,"a"命令是追加在當(dāng)前匹配行行尾的,但為什么下面的"haha"卻插入到匹配行"def"的 前面去了呢?

echo -e "abc\ndef\nxyz" | sed '/def/{a\ haha
;N}'
abc
haha
def
xyz

多行模式命令"N"、"D"、"P"簡單說明。

"N"命令:讀取下一行內(nèi)容追加到模式空間的尾部。其和"n"命令不同之處在于:"n" 命令會輸出模式空間的內(nèi)容(除非使用了"-n"選項)并清空模式空間,
然后才讀取下 一行到模式空間,也就是說"n"命令雖然讀取了下一行到模式空間,但模式空間仍 然是單行數(shù)據(jù)。
而"N"命令在讀取下一行前,雖然也有自動輸出和清空模式空間的 動作,但該命令會把當(dāng)前模式空間的內(nèi)容鎖住,使得自動輸出的內(nèi)容為空,
也無法 清空模式空間,然后讀取下一行追加到當(dāng)前模式空間中的尾部。追加時,原有內(nèi)容 和新讀取內(nèi)容使用換行符"\n"分隔,
這樣在模式空間中就實現(xiàn)了多行數(shù)據(jù)。即所謂 的"多行模式"。另外,當(dāng)無法讀取到下一行時(到了文件尾部),
將直接退出sed 程序,使得"N"命令后的命令不會再執(zhí)行,這和"n"命令是一樣的
例如:
echo -e "abc\ndef\nxyz" | sed '/def/{a\ haha;N}'
abc
 haha
def
xyz
echo -e "abc\ndef\nxyz" | sed '/def/{a\ haha;n}'
abc
def
 haha
xyz
"D"命令:刪除模式空間中第一個換行符"\n"之前的內(nèi)容,然后立即回到 SCRIPT 循 環(huán)的頂端,即進(jìn)入下一個 SCRIPT 循環(huán)。如果"D"刪除后,模式空間中已經(jīng)沒有內(nèi) 容了,則 SCRIPT 循環(huán)自動退出進(jìn)入下一個 sed 循環(huán);如果模式空間還有剩余內(nèi) 容,則繼續(xù)從頭執(zhí)行 SCRIPT 循環(huán)。也就是說,"D"命令后的命令不會被執(zhí)行。
"P"命令:輸出模式空間中第一個換行符"\n"之前的內(nèi)容。
# "N"命令的大致循環(huán)結(jié)構(gòu)
for ((line=1;line<=last_line_num;++line))
do
    read $line to pattern_space; 
    while pattern_space is not null
    do
        execute cmd1 in SCRIPT;
        execute cmd2 in SCRIPT; 
        ADDR1,ADDR2{ # "N" command
        if [ "$line" -ne "$last_line_num" ];then 
            lock pattern_space;
            auto_print;
            remove_pattern_space;
            unlock pattern_space;
            append "\n" to pattern_space; 
            read next_line to pattern_space;
        else
            auto_print;
            remove_pattern_space; 
            exit;
        fi
        };
        ......
        auto_print; 
        remove_pattern_space;
    done 
done
# "D"命令的大致循環(huán)結(jié)構(gòu)
for ((line=1;line<=last_line_num;++line))
do
    
    read $line to pattern_space; 
    while pattern_space is not null
    do
        execute cmd1 in SCRIPT;
        execute cmd2 in SCRIPT;
        ADDR1,ADDR2{ # "D" command
            delete first line in pattern_space;
            continue; 
        };
        ......
        auto_print;
        remove_pattern_space;
    done 
done
# "P"命令的大致循環(huán)結(jié)構(gòu)
for ((line=1;line<=last_line_num;++line))
do
    read $line to pattern_space;
    while pattern_space is not null
    do
        execute cmd1 in SCRIPT;
        execute cmd2 in SCRIPT;
        ADDR1,ADDR2{ # "P" command
        print first line in pattern_space; };
        ......
        auto_print; remove_pattern_space;
    done
done

buffer 空間數(shù)據(jù)交換命令"h"、"H"、"g"、"G"、"x"簡單說明。

ed 除了維護(hù)模式空間(pattern space),還維護(hù)另一個 buffer 空間:保持空間(hold space)。這兩個空間初始狀態(tài)都是空的

? "h"命令:將當(dāng)前模式空間中的內(nèi)容覆蓋到保持空間。
? "H"命令:在保持空間的尾部加上一個換行符"\n",并將當(dāng)前模式空間的內(nèi)容追加到
保持空間的尾部。
? "g"命令:將保持空間的內(nèi)容覆蓋到當(dāng)前模式空間。
? "G"命令:在模式空間的尾部加上一個換行符"\n",并將當(dāng)前保持空間的內(nèi)容追加到
模式空間的尾部。
? "x"命令:交換模式空間和保持空間的內(nèi)容。

總結(jié)

N n的證明

echo -e "abc\ndef\nxyz" | sed '/def/{a\
> haha
> ;N}'
abc
haha
def
xyz
echo -e "abc\ndef" | sed '/def/{a\         
haha
;N}'
abc
def
haha

解釋第一個命令為何"haha"會出現(xiàn)在匹配行"def"的前面。當(dāng) sed 讀取的行能匹配 "def"時,
將隊列化"haha"到內(nèi)存中,并在有輸出流的時候追加到輸出流尾部。由于這里的 輸出流來自于"a"命令后的"N"命令,
該命令將模式空間鎖住,使得隱含動作自動輸出的內(nèi) 容為空,但隊列化的內(nèi)容還是發(fā)現(xiàn)了這個空輸出流,
于是追加在這個空流的尾部。再之 后,"N"將下一行讀取到模式空間中,到了 SCRIPT 循環(huán)的結(jié)尾,再次自動輸出,此時模式空間有兩行:"def" 和 "xyz",這兩行同時被輸出。
顯然,在"def"被輸出之前,隊列化的 內(nèi)容已經(jīng)隨著空輸出流而輸出了。
再解釋為何第二個命令的結(jié)果中"haha"在"def"之后,這也是待證明的疑問。第二個命令 中,由于"def"已經(jīng)是輸入流的最后一行,
"N"已經(jīng)無法再讀取下一行,于是輸出當(dāng)前模式空 間內(nèi)容并退出 sed 程序。
假設(shè),"n"或"N"命令是先自動輸出、清空模式空間內(nèi)容,再判斷 是否有下一行可讀取的,那么在判斷之前自動輸出時,
"N"不知道是否還有下一行,于是隊 列化的內(nèi)容應(yīng)該同第一個命令一樣,插入在"def"之前。但結(jié)果卻并非如此。
如果先判斷是 否有下一行可供讀取,再輸出、清空模式空間,則隊列化內(nèi)容是跟隨著"N"退出 sed 程序前 輸出的,這正符合第二個命令的結(jié)果。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容