# 多行命令

n 命令 -- next ,会将 sed 编辑器移动到数据流中的下一行文本,而不用重新回到命令的最开始执行

sed '/head/{n;d}' data
# 当匹配到 head 一行,sed 会马上移动到下一行开头,执行 d 命令

N 命令,将数据流中两个文本行合并到同一个模式空间中,两行文本仍以换行符相隔,但是会将两行文本当作一行处理。

cyan@cyan-virtual-machine:~/Templates$ cat data
cat
cat
cat
end
cyan@cyan-virtual-machine:~/Templates$ sed '/cat/{N;s/\n/ /}' data
cat cat
cat end

但是这个命令存在局限,比如前四行, sed 会将第 1,2 行看作整体,第 3,4 行看作整体,如果最后一行是第五行单行, sed 不会检查第五行,直接结束。

N,d 两个命令配合可以实现多行删除,对于匹配到的,模式空间中两行都会删除, D 就只会删除模式空间第一行,第二行被保留。

cyan@cyan-virtual-machine:~/Templates$ cat data
cat
cat
cat
cat
end
cyan@cyan-virtual-machine:~/Templates$ sed "N;/[c,e]*/d" data
end
# N 和 D 的配合使用,可以删除行前的空白行
cyan@cyan-virtual-machine:~/Templates$ cat data
cat
dog
mouse
monkey
cyan@cyan-virtual-machine:~/Templates$ sed "N;/ /D" data
cat
dog
mouse
monkey

N,P 命令可以实现多行打印, P 命令只打印模式空间的第一行。

cyan@cyan-virtual-machine:~/Templates$ cat data
cat
dog
mouse
monkey
cyan@cyan-virtual-machine:~/Templates$ sed -n "4{N;P}" data
mouse

关于 sedp,P 命令的区别,可以参考这篇文章

http://www.136.la/shida/show-410559.html

# 保持空间

模式空间是一块活跃的缓冲区,sed 编辑器还有另一块称作保持空间的缓冲区。对于保持空间,存在 5 条命令,用来将文本从模式空间复制到保持空间。这可以清空模式空间来加载其他要处理的字符串。

h--将模式空间复制到保持空间
H--将模式空间附加到保持空间
g--将保持空间复制到模式空间
G--将保持空间附加到模式空间
x--交换两个空间的内容
sed -n '/first/ {h;p;n;p;g;p}' data

现在你不用太理解,只需要有这么个空间即可。

# 排除命令

sed 基础那篇文章中,提到 ! 可以代替 /

sed 's/cat/dog/' data
sed 's!cat!dog!' data

sed 中,使用感叹号 ! 还可以排除命令,让原本会起作用的命令不起作用,比如在最后一行取消使用 N 命令

sed '$!N;其他命令' data

现在给出一个例子,通过这个例子,我们会用到模式空间,保持空间以及排除命令

# 逆序打印文本
cyan@cyan-virtual-machine:~/Templates$ echo -n "first
> second
> third
> " > data
cyan@cyan-virtual-machine:~/Templates$ sed -n '{1!G;h;$p}' data
third
second
first

上述命令, G,h 是关于保持空间的命令,每次将数据读取到模式空间,保持空间中的数据都会附加到模式空间 (第一行除外),然后再复制到保持空间。当然, Linux 命令 tac 可以直接逆序文本,其实 tac 就是 cat 反着写。

等一下,我好像还没讲过什么是模式空间: sed 每次读一行,都会把这一行数据读到模式空间,在使用命令处理数据,在打印到标准输出。

关于逆序打印文本,具体我们可以看下图

image-20220806111008122

如果你真的理解了,那么你就知道,这个命令也可以实现逆序

sed -n '{1!G;$!h;$p}' data

# 分支

分支命令共有两条:分支(b)和替代分支(t)。

# b 命令

基于地址,地址模式或地址区间排除整块命令: [address]b [label]

address 决定哪些行触发分支命令。 label 参数定义了要跳转的位置,如果此处没有 label 参数,跳转命令会跳转到脚本结尾(注意,是命令脚本的结尾)。

sed '{2,3b ; s/This is/Is this/ ;s/line./test?/}' data

sed 还可以使用标签,使用标签后,只会跳过部分命令,也就是标签之前的命令。直到这里你可能不是很懂,我要提醒的是,在 sed 中引入分支,我建议将命令放在 .sed 命令脚本中,这样使用起来才方便。

# 假设我们需要都数据文件进行处理,一行数据中一旦出现 cat 这个单词,我们就打印其所在行号
# 否则,就打印该行
# command.sed
/cat/b error_print
/**/b true_print
: error_print
=
b # 跳到结尾,不然会执行 true_print 的语句
: true_print
p
b

更改一下 data 的内容,执行 sed 命令

cyan@cyan-virtual-machine:~/Templates$ echo -n "dog
> monkey
> cat
> mouse
> " > data
cyan@cyan-virtual-machine:~/Templates$ sed -n -f command.sed data
dog
monkey
3
mouse

如果你把标签放到分支跳转的前面,很容易形成死循环。比如,通过 sed 将数据中的 , 全部删除

cyan@cyan-virtual-machine:~$  echo "This, is, a, test, to, remove, commas." | sed -n '{
> :start
> s/,//p
> b start
> }'
This is, a, test, to, remove, commas.
This is a, test, to, remove, commas.
This is a test, to, remove, commas.
This is a test to, remove, commas.
This is a test to remove, commas.
This is a test to remove commas.
^C
# 每次去掉一个,然后跳回 start 标签处
# 当然,直接使用 g 选项
cyan@cyan-virtual-machine:~$  echo "This, is, a, test, to, remove, commas." | sed -n 's/,//gp'
This is a test to remove commas.

上述例子,为了解决死循环,可以指定一个地址模式

cyan@cyan-virtual-machine:~$ echo "This, is, a, test, to, remove, commas." | sed -n '{
:start
s/,//p
/,/b start
}'
# 只有再次匹配到,才会跳转

你也可以看一下这篇文章。

https://blog.csdn.net/m0_51642814/article/details/111461137

# t 命令

测试命令 ( t ) 也可以用来改变 sed 执行流程,根据测试条件跳转,而不是地址: [address]t [label]

如果替换命令匹配并替换了一个模式,测试命令就会跳转到指定标签,否则不会跳转。

cyan@cyan-virtual-machine:~$ echo "This, is, a, test, to, remove, commas." | sed -n '{
:start
s/,//p
t start
}'
This is, a, test, to, remove, commas.
This is a, test, to, remove, commas.
This is a test, to, remove, commas.
This is a test to, remove, commas.
This is a test to remove, commas.
This is a test to remove commas.
# 最后一次匹配失败,停止跳转

# 模式替代

& 符号,该符号可以用来代表替换命令中的匹配的模式,比如我需要匹配 hello 这个字符串,那么我就可以用 & 来代替 hello

cyan@cyan-virtual-machine:~$ echo "The cat is cute." | sed 's/cu[^\.\ ]*/"&"/'
The cat is "cute".

& 在使用正则表达式时十分方便,因为我们也不知道匹配到的模式到底是什么,我们可以直接通过 & 拿到我们匹配到的模式。

替代单独单词: & 符号提取匹配替换命令指定模式的整个字符串,如果想要提取独立的单词,需要使用 ()

echo "The system Adminstrator manual" | sed '
> s/\(system\) Adminstrator/\1 user/'
The system User manual

被使用时,圆括号定义了替换模式中的子模式,并使用 \1,2,3.. 来表示,在替换命令中使用圆括号时,
必须使用转义字符将它们标示为分组字符而不是普通的圆括号。

所以最开始的例子我们也可以这么写

cyan@cyan-virtual-machine:~$ echo "The cat is cute." | sed 's/\(cu[^\.\ ]*\)/"\1"/'
The cat is "cute".

最后再举一个例子:每三位添加逗号 (类似于,金钱金额分隔计数)

cyan@cyan-virtual-machine:~$ echo $input | sed '{
> : start
> s/\(.*[0-9]\)\([0-9]\{3\}\)/\1,\2/
> t start
> }'

这里说明一下, grepsed 默认不支持 ERE -- 扩展正则表达式,如果要使用, grep 需要加 -E 选项, sed 需要使用 -r 选项。如果不加选项还想使用 {} ,就要加转义字符 \{m\}

在给例子之前,我需要提示一下,我们上面使用 () 其实就是扩展正则表达式里面的东西,因为之前没有使用 -r 选项,所以需要使用转义字符。

cyan@cyan-virtual-machine:~$ echo $input | sed -r '
: start;
s/(.*[0-9])([0-9]{3})/\1,\2/;
t start;
'
10,913,013

# 最后

这里有一篇求问博客,是关于 sed 和正则表达式问题的,你能先不看评论区,自己找到博主提出的问题吗?要是你能自己解决,那更是最好的。

https://oomake.com/question/4296530