sed 命令进阶
sed 是面向行处理的,但是有时可能希望针对多行作为一个单位进行处理,在 sed 中这也是可行的,本文将介绍如何使用 sed 来同时处理多行文本
理论基础
模式空间(Pattern Space):是一块活跃的缓冲区,sed 命令处理行时都
会将文本行读入到该空间执行相关的脚本,默认情况下每次只会从输入中读取一行输入文本到该空间中进行处理。
保持空间(Hold Space):该空间的作用是临时保存 “模式空间” 中处理的文本行
由于 sed 一般是直接在 “模式空间” 中对文本数据进行处理的,因此如果能够改变 “模式空间” 读取文本数据的行为,即可实现将“多行文本数据作为一个处理单位进行处理” 的功能。而要改变 “模式空间” 的默认行为,可以通过 D
、G
、H
、N
、P
等命令进行处理
多行文本处理
读取行
如果只是希望读取下一行,那么普通的 n
命令就可以做到,例如,如果希望将所有出现 “the” 的的行的下一行进行删除,可以执行如下的命令:
# n 命令读取下一行,d 命令进行删除
sed '/the/{n ; d}' data.txt
这里的 n
命令只会读取单行数据文本,然后进行对应的脚本进行处理,本质上在 “模式空间” 中还是只针对单行。如果要合并出现 ‘,’ 的两行,可以使用 N
命令来将读取到的文本行加入到 “模式空间” 中,如下所示:
# 注意将 \n 替换成空格,否则可能无法看到合并的结果
sed '/,/{N; s/\n/ /}' data.txt
注意: 由于 N
命令是获取下一行,因此如果处理的是最后一行的话,由于最后一行没有数据了,因此在读取到最后一行时,会停止 sed,使得最后一行的替换操作无法进行。
删除行
d
命令会删除匹配的行,当和 N
命令一起使用时可能不会达到你想要的结果,因此应该避免在使用 N
命令的同时使用 d
命令,为此可以考虑采用 D
命令来代替 d
命令进行删除
D
命令和 d
命令最大的不同在于 D
命令只会删除 “模式空间” 中的第一行,而不是将整个 “模式空间” 的所有文本行作为一个单元进行处理
D
命令的独特之处在于强制 sed 回到脚本的执行处,对同一 “模式空间” 中的内容重新执行这些脚本内容,
多行打印
当多行匹配时,通过 p
命令可以打印出当前匹配模式中存在的所有文本行,有时可能并不希望发生这样的行为,为此,可以通过 P
命令,该打印命令只会打印 “模式空间” 中的第一行文本内容
取反命令
可以通过在命令选项前加上 !
来使得原有的命令无法生效,例如,前文提到,N
命令在处理最后一行时会关闭 sed,使得最后一行的内容无法被正确处理,为此,可以通过加入 !
命令来禁用最后一行的行为,如下所示:
sed '$!N; s/the/a/' data.txt
现在,sed 就能够按照预期地将所有的文本行进行相关的处理
保持空间的相关操作
”保持空间“ 有以下五种选项进行对应的操作,如下表所示:
命令 | 描述 |
---|---|
h | 将模式空间中的内容复制到保持空间 |
H | 将模式空间中的内容附加到保持空间 |
g | 将保持空间中的内容复制到模式空间 |
G | 将保持空间中的内容附加到模式空间 |
x | 交换模式空间和保持空间的内容 |
例如,如果想要反向输出输入流中的文本内容,结合上文的取反命令,可以执行如下的脚本:
sed -n '1!G; h; $p' data.txt
解释:G
命令本身会将保持空间的内容附加到模式空间中,1G
的命令就是将保持空间的内容附加到模式空间的第一行,然而,加上 !
排除命令之后,使得 1G
的行为变成反向附加到模式空间中;通过 h
命令将模式空间中的内容复制到保持空间;$p
表示每次当到达数据流的末尾时,打印模式空间中的内容
改变流
sed 通过顺序处理输入流来对每一行的数据执行对应的脚本,直到数据流结束(D
命令除外),sed 编辑器提供了一个方法来改变脚本的执行流程。
分支
分支的主要目的是排除某些文本行,使得它们不会执行对应的脚本,具体的使用格式如系所示:
[address]b [label]
例如,如果不希望 data.txt
中的 \([2,3]\) 的文本行不会执行替换脚本,可以执行类似如下的命令:
sed '2,3b; s/the/a/' data.txt
[label] 是类似 goto
语句的结构,如果希望当匹配到 first
文本时,跳转到执行不同的脚本,可以执行类似下面的命令:
sed '{/first/b jump1; s/This/No Jump/
:jump1
s/This/Jump Here/
}' data.txt
当匹配到 “first” 时,则会将执行将 ”This“ 替换为 ”Jump Here“ 的脚本,而不是替换成为 ”No Jump“
注意: 标签名的最大长度为 \(7\) 个字符
测试
和分支命令类似,测试命令 t
(test)也可以用来改变 sed 编辑器脚本的执行流程,该命令和 if
语句类似。
使用格式如下:
[address]t [label]
例如,如下的脚本:
sed '{
s/first/matched/
t
s/This/No Matched/
}' data.txt
当处理的文本包含 ”first“ 时,会将 ”first“ 替换为 ”matched“,否则将该文本中的 ”This“ 替换为 ”No Matched“
测试命令 t
同样可以使用和分支类似的标签语义
模式替代
替换命令 s
已经十分了解,试想一下这样一种情况:希望为某个单词加上引号,似乎一般的 s
命令不能完美解决这个问题(无法知道匹配到的实际单词),为了解决这个问题,在传统的 Unix 中提供了 ‘&’ 符号用于表示匹配到的字符串,因此,如果希望给 ”the“ 加上引号,可以执行如下的脚本:
sed 's/th./"&"/' data.txt
有时可能希望替换掉一个字符串中的单词,那么可以考虑使用括号来进行单独的替换,括号表示一个子模式,如下所示:
# \1 表示匹配到的子模式
echo "This is System Administrator manual" | sed 's/\(System\).Administrator/\1 User/'
注意:子模式的括号需要使用 \
进行转义,否则会被视为一般的字符
”\1“ 表示第一个括号的子模式,”\2“ 表示第二个括号的子模式…………
结合正则表达式可能使用得更加得心应手
参考:
[1] 《Linux 命令行与 Shell 脚本编程大全》