学习《玩透sed》基础篇

通过学习《玩透sed》整理

作者: 骏马金龙
学习链接: https://www.junmajinlong.com/how_to_nav_posts/
学习来源: 骏马金龙

整体的思维导图查看

sed入门

sed的基本概率

sed是一个流式的编辑程序,它读取输入流(可以是文件、标准输入)的每一行放进模式空间 (pattern space),
同时将此行行号通过 sed 行号计数器记录在内存中,然后对模式空间中 的行进行模式匹配,如果能匹配上则使用 sed 程序内部的命令进行处理,
处理结束后,从模式空间中输出(默认)出去,并清空模式空间,
随后再从输入流中读取下一行到模式空间 中进行相同的操作,
直到输入流中的所有行都处理完成。由此可见,sed 是一个循环一个 循环处理内容的。

一次sed处理的过程:
1. 读取输入流的一行到模式空间。
2. 对模式空间中的内容进行匹配和处理。 
3. 自动输出模式空间内容。
4. 清空模式空间内容。
5. 读取输入流的下一行到模式空间。
PS:如果是文件会一次性加载一定量的到os buffer之中然后再一行行的读取,如果使用的是管道或者其他的输入流,就是直接从对应的缓存之中再一行行的读取。

我们在这个过程中可以通过我们命令所改变的是第二步,其他步骤都是固定的模式。
但是sed的3,4步可以通过sed的几个命令以及选项进行改变,使其输出空或者无法清空模式空间。

语法格式:
sed OPTIONS SCRIPT INPUT_STREAM

SCPIPT部分就是sed脚本,它是 sed 内部命令的集合,sed中的命令有些奇 特,它包含行匹配以及要执行的命令。
格式为 ADDR1[,ADDR2]cmd_list。例如,要对第2行执行删除命令,其命令为 sed 2d filename,只输出第 4 行到 6 行,其命令为 sed -n 4,6p。

因为SCRIPT是命令的集合所以循环可以修改为:
1. 读取输入流的一行到模式空间。
2. 对模式空间中内容执行SCRIPT。(包括上面示例中的"2d"和"4,6p") 
3. 读取输入流的下一行到模式空间。
4. 对模式空间中内容执行SCRIPT。

SCRIPT部分除了包含了 sed 命令行中的内部命令,还包括两个特殊动作:自动输出和清 空模式空间内容。这两个动作是一定会执行的,但是有些命令也可以改变让这两个命令不进行执行。

用shell程序实现过程就是这样:
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循环中读取下一行和执行 SCRIPT 循环。而在SCRIPT 循环中就是script语句的执行以及对模式空间的清除。

sed的命令行书写也就是script部分的书写。
# 一行式。多个命令使用分号分隔 sed Address{cmd1;cmd2;cmd3...}
# 多个表达式时,可以使用"-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 部分还可以进行模式匹配,也即类似于 Address{{pattern1}cmd1;{pattern2}cmd2}的写法。例如,/^abc/{2d;p}。

sed 选项

可能用到的几个选项:
'-n'
默认情况下,sed 将在每轮 script 循环结束时自动输出模式空间中的内容。使用该选项 后可以使得这次自动输出动作输出空内容,而不是当前模式空间中的内容。
注意,"-n"是 输出空内容而不是禁用输出动作,虽然两者的结果都是不输出任何内容,但在有些依赖于 输出动作和输出流的地方,
它们的区别是很大的,前者有输出流,只是输出空流,后者则 没有输出流。
'-e SCRIPT'
前文说了,SCRIPT 中包含的是命令的集合,"-e"选项就是向 SCRIPT 中添加命令的。可 以省略"-e"选项,但如果命令行容易产生歧义,则使用"-e"选项可明确说明这部分是 SCRIPT 中的命令。
另外,如果一个"-e"选项不方便描述所需命令集合时,可以指定多个"- e"选项。
'-f SCRIPT-FILE'
指定包含命令集合的 SCRIPT 文件,让 sed 根据 SCRIPT 文件中的命令集处理输入流。
'-i[SUFFIX]'
该选项指定要将 sed 的输出结果保存(覆盖的方式)到当前编辑的文件中。
GNU sed 是通过创建一个临时文件并将输入写入到该临时文件,然后重命名为源文件来实现的。
当当前输入流处理结束后,临时文件被重命名为源文件的名称。如果还提供了 SUFFIX, 则在重命名临时文件之前,
先使用该 SUFFIX修改源文件名,从而生成一个源文件的备份 文件
临时文件总是会被重命名为源文件名称,也就是说输入流处理结束后,仍使用源文件名的文件是sed 修改后的文件。文件名中包含了 SUFFIX 的文件则是最原始文件的备份。
例如:源文件为 a.txt,sed -i'.log' SCRIPT a.txt 将生成两个文件:a.txt 和 a.txt.log,前者 是 sed 修改后的文件,a.txt.log 是源 a.txt 的备份文件。

重命名的规则如下:如果扩展名不包含符号"*",将 SUFFIX添加到原文件名的后面当作文 件后缀;如果 SUFFIX中包含了一个或多个字符"*",则每个"*"都替换为原文件名。这使得 你可以为备份文件添加一个前缀,而不是后缀。如果没有提供 SUFFIX,源文件被覆盖, 且不会生成备份文件。
该选项隐含了"-s"选项。
'-r'
使用扩展正则表达式,而不是使用默认的基础正则表达式。sed 所支持的扩展正则表达式 和 egrep 一样。使用扩展正则表达式显得更简洁,因为有些元字符不用再使用反斜线"\"。 正则表达式见 grep 命令中文手册。
'-s'
默认情况下,如果为 sed 指定了多个输入文件,如 sed OPTIONS SCRIPT file1 file2 file3,
则多个文件会被 sed 当作一个长的输入流,也就是说所有文件被当成一个大文 件。
指定该选项后,sed将认为命令行中给定的每个文件都是独立的输入流。
既然是独立的输入流,范围定址(如/abc/,/def/)就无法跨越多个文件进行匹配,行号也会在处理每个文件时重置,
"$"代表的也将是每个文件的最后一行。这也意味着,如果不使 用该选项,则这几个行为都是可以完成的。

语句练习

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里面已经包含了-s 如果不使用-s会是他们两者合并的第5行文件 
4.使用扩展正则表达式,输出 a.txt 和 b.txt 中能包含 "zip"的行。
sed -r -n '/zip+/p' a.txt b.txt

定址表达式

当 sed 将输入流中的行读取到模式空间后,就需要对模式空间中的内容进行匹配,如果能 匹配就能执行对应的命令,
如果不能匹配就直接输出、清空模式空间并进入下一个 sed 循 环读取下一行。
匹配的过程称为定址。定址表达式有多种,但总的来说,其格式为[ADDR1][ADDR2]。

定址表达式的三种方式:
1. ADDR1 和 ADDR2 都省略时,表示所有行都能被匹配上。
2. 省略ADDR2时,表示只有被ADDR1表达式匹配上的行才符合条件。
3. 不省略ADDR2时,是范围地址。表示从ADDR1匹配成功的行开始,到ADDR2匹配成功的行结束

无论是 ADDR1 还是 ADDR2,都可以使用两种方式进行匹配:行号和正则表达式。如下:

'N'
指定一个行号,sed 将只匹配该行。(需要注意,除非使用了"-s"或"-i"选项,sed 将对所有 输入文件的行连续计数。)\
'FIRST~STEP'
表示从第 FIRST 行开始,每隔 STEP 行就再取一次。也就是取行号满足 FIRST+(N*STEP) (其中 N>=0)的行。因此,要选择所有奇数行,使用"1~2";\
要从第 2 行开始每隔 3 行取一 次,使用"2~3";要从第 10 行开始每隔 5 行取一次,使用"10~5";而"50~0"则表示只取第 50 行。
'$'
默认该符号匹配的是最后一个文件的最后一行,如果指定了"-i"或"-s",则匹配的是每个文 件的最后一行。\
总之,"$"匹配的是每个输入流的最后一行
se d 采用行号计数器来临时记录当前行的行号,因此 sed 在读取到最后一行前即使是倒数第二行的时候,完全不知道最后一行是第几行,
所以代表最后一行的"$"无法进行任何数学运算,例如倒数第二行使用"$-1"表示是错误的。
而且,"$"只是一个额外的 标记符号,当 sed读取到输入流的最后一行时,发现这就是最后一行,于是为此行打上 "$"记号,并读取到模式空间中。

'/REGEXP/'
将选择能被正则表达式 REGEXP 匹配的所有行。如果 REGEXP 中自身包含了字符"/",则 必须使用反斜线转义,即"\/"。
'/REGEXP/I'
和"/REGEXP/"是一样的,只不过匹配的时候不区分大小写。
'\%REGEXP%'
('%'可以使用其他任意单个字符替换。) 这和上一个定址表达式的作用是一样的,只不过是 使用符号"%"替换了符号"/"。
当 REGEXP 中包含"/"符号时,使用该定址表达式就无需对"/" 使用反斜线"\"转义。
但如果此时 REGEXP 中包含了"%"符号时,该符号需要使用"\"转义。 
总之,定址表达式中使用的分隔符在 REGEXP 中出现时,都需要使用反斜线转义。
'ADDR1,+N'
匹配 ADDR1 和其后的 N 行。

'ADDR1,~N'
匹配 ADDR1 和其后的行直到出现 N 的倍数行。倍数可为随意整数倍,只要 N 的倍数是最 接近且大于 ADDR1 的即可。
如 ADDR1=1,N=3 匹配 1-3 行,ADDR1=5,N=4 匹配 5-8 行。 而"1,+3"匹配的是第一行和其后的 3 行即 1-4 行。
另外,在定址表达式的后面加"! "符号表示反转匹配的含义。也就是说那些匹配的行将不被 选择,而是不匹配的行被选择。
定址的示例
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 # 这个有悖常理,但确实是允许的

常用命令

强制输出命令"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进行输出。\

仅输出标准输入的第 2 行内容。
[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循环如果delete命令后还有其他命令时都不会被执行了。
例如:删除a.txt 第五行并保存原文件之中。

sed -i '5d' a.
这里不能使用重定向的方式保存,因为重定向是在 sed 命令执行前被 shell 执行的,所以 会截断 a.txt,使得 sed 读取的输入流为空,\
或者结果出乎意料之外。而"-i"选项则不会操作原文件,而是生成临时文件并在结束时重命名为原文件名。

删除 a.sh 中包含"#"开头的注释行,但第一行的#!/bin/bash 不删除。

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

如果"d"后面还有命令,在删除模式空间后,这些命令不会执行,因为会立即退出当前 SCRIPT 循环。例如:

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

其中"="这个命令用于输出行号,但是结果并没有输出被"abc"匹配的行的行号。

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

使用"q"和"Q"命令的作用是立即退出当前 sed 程序,使其不再执行后面的命令,也不再读 取后面的行。

# "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,当搜索到使用了"."或"source"命令加载环境配置脚本时就输出并立 即退出。

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

输出行号命令

"="命令用于输出最近被读取行的行号。在 sed 内部,使用行号计数器进行行号计数,每读 取一行,行号计数器加 1。
计数器的值存储在内存中,在要求输出行号时,直接插入在输 出流中的指定位置。由于值是存在于内存中,而非模式空间中,因此不受"-n"选项的影 响。
这是一个依赖于输出流的命令,只要有输出动作就会追加在该输出流的尾部。
例如,搜索出 httpd.conf 中"DocumentRoot"开头的行的行号,允许有前导空白字符。

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

字符一一对应替换命令"y"

该命令与tr命令映射功能一样都是字符进行一一替换
例如,将 a.txt 中包含大写字母的 YES、Yes 等替换成小写的 yes。

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

手动读取下一行命令"n

在 sed 的循环过程中,每个 sed 循环的第一步都是读取输入流的下一行到模式空间中,这 是我们无法控制的动作。但 sed 有读取下一行的命令"n"。
由于是读取下一行,所以它会触发自动输出的动作,于是就有了输出流。不仅如此,还应
该记住的是:只要有读取下一行的行为,在其真正开始读取之前一定有隐式自动输出的行

但需注意,当没有下一行可供"n"读取时(例如文件的最后一行已经被读取过了),将输出模 式空间内容后直接退出 sed 程序,使得"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;
        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 中的内容,所以 then 和 else 语句中都有这两个动作。 也许感觉上似乎更应该像下面这样的优化形式:

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
从结果中可以分析出,"n"读取下一行前输出了"abc",然后立即读入了下一行,所以输出 的行号是 2 而不是 1,
因为这时候行号计数器已经读取了下一行,随后命令"p"输出了该模 式空间的内容,输出后还有一次自动输出的隐含动作,所以"def"被输出了两次

替换命令"s"

将匹配到的内容替换成指定的内容。

"s"命令的语法格式为:其中"/"可以替换成任意其他单个字符。
s/REGEXP/REPLACEMENT/FLAGS
它使用 REGEXP 去匹配行,将匹配到的那部分字符替换成 REPLACEMENT。FLAGS 是 "s"命令的修饰符,常见的有"g"、"p"和"i"或"I"。
    • "g":表示替换行中所有能被 REGEXP 匹配的部分。不使用 g 时,默认只替换行中 的第一个匹配内容。此外,"g"还可以替换成一个数值 N,表示只替换行中第 N 个 被匹配的内容。
    • "p":输出替换后模式空间中的内容。
    • "i"或"I":REGEXP 匹配时不区分大小写。
REPLACEMENT 中可以使用"\N"(N 是从 1 到 9 的整数)进行后向引用,所代表的是 REGEXP第N个括号(...)中匹配的内容。另外,REPLACEMENT中可以包含未转义的"&"
符号,这表示引用 pattern space 中被匹配的整个内容。需要注意,"&"是引用 pattern space 中的所有匹配,不仅仅只是括号的分组匹配。

例如,删除 a.sh 中所有"#"开头(可以包括前导空白)的注释符号"#",但第一行"#! /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 命令对调个位置。

sed 's%&&\(.*\) ||\(.*\)%\&\&\2 ||\1%' a.
这里使用了"%"代替"/",且在 REPLACEMENT 部分对"&"进行了转义,因为该符号在 REPLACEMENT 中时表示的是引用 REGEXP 所匹配的所有内容。

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

这 3 个命令的格式是"[a|i|c] TEXT",表示将 TEXT 内容队列化到内存中,当有输出流或者 说有输出动作的时候,半路追上输出流,分别追加、插入和替换到该输出流然后输出。
追 加是指追加在输出流的尾部,插入是指插入在输出流的首部,替换是指将整个输出流替换掉。
"c"命令和"a"、"i"命令有一丝不同,它替换结束后立即退出当前 SCRIPT 循环,并进入下一个sed 循环,因此"c"命令后的命令都不会被执行。

例如:

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 部分写法是比较复杂的,如果 TEXT 只是几个简单字符,如 上即可。
但如果要 TEXT 是分行文本,或者包含了引号,或者这几个命令是写在"{}"中的,则上面的写法就无法实现。
需要使用符号""来转义行尾符号,这表示开启一个新行,此后 输入的内容都是 TEXT,直到遇到引号或者";"开头的行时\

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

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

"a"命令后的第一个反斜线用于标记 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"选项或某些命令的影响。
此外,这 3 个命令依赖于输出流,只要有输出动作,不管是 空输出流还是非空的输出流,只要有输出,这几个命令就会半路"劫杀"。
如果不理解这两句话,这 3个命令的结果有时可能会比较疑惑。

例如,"a"命令是追加在当前匹配行行尾的,但为什么下面的"haha"却插入到匹配行"def"的 前面去了呢?

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

多行模式命令"N"、"D"、"P"简单说明。

"N"命令:读取下一行内容追加到模式空间的尾部。其和"n"命令不同之处在于:"n" 命令会输出模式空间的内容(除非使用了"-n"选项)并清空模式空间,
然后才读取下 一行到模式空间,也就是说"n"命令虽然读取了下一行到模式空间,但模式空间仍 然是单行数据。
而"N"命令在读取下一行前,虽然也有自动输出和清空模式空间的 动作,但该命令会把当前模式空间的内容锁住,使得自动输出的内容为空,
也无法 清空模式空间,然后读取下一行追加到当前模式空间中的尾部。追加时,原有内容 和新读取内容使用换行符"\n"分隔,
这样在模式空间中就实现了多行数据。即所谓 的"多行模式"。另外,当无法读取到下一行时(到了文件尾部),
将直接退出sed 程序,使得"N"命令后的命令不会再执行,这和"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"之前的内容,然后立即回到 SCRIPT 循 环的顶端,即进入下一个 SCRIPT 循环。如果"D"删除后,模式空间中已经没有内 容了,则 SCRIPT 循环自动退出进入下一个 sed 循环;如果模式空间还有剩余内 容,则继续从头执行 SCRIPT 循环。也就是说,"D"命令后的命令不会被执行。
"P"命令:输出模式空间中第一个换行符"\n"之前的内容。
# "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; 
        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"命令的大致循环结构
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"命令的大致循环结构
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 空间数据交换命令"h"、"H"、"g"、"G"、"x"简单说明。

ed 除了维护模式空间(pattern space),还维护另一个 buffer 空间:保持空间(hold space)。这两个空间初始状态都是空的

• "h"命令:将当前模式空间中的内容覆盖到保持空间。
• "H"命令:在保持空间的尾部加上一个换行符"\n",并将当前模式空间的内容追加到
保持空间的尾部。
• "g"命令:将保持空间的内容覆盖到当前模式空间。
• "G"命令:在模式空间的尾部加上一个换行符"\n",并将当前保持空间的内容追加到
模式空间的尾部。
• "x"命令:交换模式空间和保持空间的内容。

总结

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"会出现在匹配行"def"的前面。当 sed 读取的行能匹配 "def"时,
将队列化"haha"到内存中,并在有输出流的时候追加到输出流尾部。由于这里的 输出流来自于"a"命令后的"N"命令,
该命令将模式空间锁住,使得隐含动作自动输出的内 容为空,但队列化的内容还是发现了这个空输出流,
于是追加在这个空流的尾部。再之 后,"N"将下一行读取到模式空间中,到了 SCRIPT 循环的结尾,再次自动输出,此时模式空间有两行:"def" 和 "xyz",这两行同时被输出。
显然,在"def"被输出之前,队列化的 内容已经随着空输出流而输出了。
再解释为何第二个命令的结果中"haha"在"def"之后,这也是待证明的疑问。第二个命令 中,由于"def"已经是输入流的最后一行,
"N"已经无法再读取下一行,于是输出当前模式空 间内容并退出 sed 程序。
假设,"n"或"N"命令是先自动输出、清空模式空间内容,再判断 是否有下一行可读取的,那么在判断之前自动输出时,
"N"不知道是否还有下一行,于是队 列化的内容应该同第一个命令一样,插入在"def"之前。但结果却并非如此。
如果先判断是 否有下一行可供读取,再输出、清空模式空间,则队列化内容是跟随着"N"退出 sed 程序前 输出的,这正符合第二个命令的结果。

你可能感兴趣的:(学习《玩透sed》基础篇)