The Sed quit command
q 命令是在到达输入文件的尾部之前停止处理输入的方法。为什么会有人想去那样做呢?
很好的问题,如果你还记得,我们可以使用下面的命令来输出文件中第 1 到第 5 的行:
sed -n -e '1,5p' inputfile
对于大多数 Sed 的实现方式,工具将循环读取输入文件的所有行,那怕是你只处理结果中的前 5 行。如果你的输入文件包含了几百万行(或者更糟糕的情况是,你从一个无限的数据流,比如像 /dev/urandom 中读取)将有重大影响。
使用退出命令,相同的程序可以被修改的更高效:
sed -e '5q' inputfile
由于我在这里并不使用 -n 选项,Sed 将在每个循环结束后隐式输出模式空间的内容。但是在你处理完第 5 行后,它将退出,并且因此不会去读取更多的数据。
我们能够使用一个类似的技巧只输出文件中一个特定的行。这也是从命令行中提供多个 Sed 表达式的几种方法。下面的三个变体都可以从 Sed 中接受几个命令,要么是不同的 -e 选项,要么是在相同的表达式中新起一行,或用分号(;)隔开:
sed -n -e '5p' -e '5q' inputfile
sed -n -e '
5p
5q
' inputfile
sed -n -e '5p;5q' inputfile
如果你还记得,我们在前面看到过能够使用花括号将命令组合起来,在这里我们使用它来防止相同的地址重复两次:
替换命令# 组合命令
sed -e '5{
p
q
}' inputfile
# 可以简写为:
sed '5{p;q;}' inputfile
# 作为 POSIX 扩展,有些实现方式可以省略闭花括号之前的分号:
sed '5{p;q}' inputfile
你可以将替换命令(s)想像为 Sed 的“查找替换”功能,这个功能在大多数的“所见即所得”的编辑器上都能找到。Sed 的替换命令与之类似,但比它们更强大。替换命令是 Sed 中最著名的命令之一,在网上有大量的关于这个命令的文档。
The Sed `substitution` command
在前一篇文章 中我们已经讲过它了,因此,在这里就不再重复了。但是,如果你对它的使用不是很熟悉,那么你需要记住下面的这些关键点:
- 替换命令有两个参数:查找模式和替换字符串:sed s/:/-----/ inputfile
- s 命令和它的参数是用任意一个字符来分隔的。这主要看你的习惯,在 99% 的时间中我都使用斜杠,但也会用其它的字符:sed s%:%-----% inputfile、sed sX:X-----X inputfile 或者甚至是 sed 's : ----- ' inputfile
- 默认情况下,替换命令仅被应用到模式空间中匹配到的第一个字符串上。你可以通过在命令之后指定一个匹配指数作为标志来改变这种情况:sed 's/:/-----/1' inputfile、sed 's/:/-----/2' inputfile、sed 's/:/-----/3' inputfile、…
- 如果你想执行一个全局替换(即:在模式空间上的每个非重叠匹配上进行),你需要增加 g 标志:sed 's/:/-----/g' inputfile
- 在字符串替换中,出现的任何一个 & 符号都将被与查找模式匹配的子字符串替换:sed 's/:/-&&&-/g' inputfile、sed 's/.../& /g' inputfile
- 圆括号(在扩展的正则表达式中的 (...) ,或者基本的正则表达式中的 \(...\))被当做 捕获组(capturing group)。那是匹配字符串的一部分,可以在替换字符串中被引用。\1 是第一个捕获组的内容,\2 是第二个捕获组的内容,依次类推:sed -E 's/(.)(.)/\2\1/g' inputfile、sed -E 's/(.):x:(.):(.*)/\1:\3/' inputfile(后者之所能正常工作是因为 正则表达式中的量词星号表示尽可能多的匹配,直到不匹配为止 ,并且它可以匹配许多个字符)
- 在查找模式或替换字符串时,你可以通过使用一个反斜杠来去除任何字符的特殊意义:sed 's/:/--\&--/g' inputfile,sed 's/\//\\/g' inputfile
所有的这些看起来有点抽象,下面是一些示例。首先,我想去显示我的测试输入文件的第一个字段并给它在右侧附加 20 个空格字符,我可以这样写:
sed < inputfile -E -e '
s/:/ / # 用 20 个空格替换第一个字段的分隔符
s/(.{20}).*/\1/ # 只保留一行的前 20 个字符
s/.*/| & |/ # 为了输出好看添加竖条
'
第二个示例是,如果我想将用户 sonia 的 UID/GID 修改为 1100,我可以这样写:
sed -En -e '
/sonia/{
s/[0-9] /1100/g
p
}' inputfile
注意在替换命令结束部分的 g 选项。这个选项改变了它的行为,因此它将查找全部的模式空间并替换,如果没有那个选项,它只替换查找到的第一个。
顺便说一下,这也是使用前面讲过的输出(p)命令的好机会,可以在命令运行时输出修改前后的模式空间的内容。因此,为了获得替换前后的内容,我可以这样写:
sed -En -e '
/sonia/{
p
s/[0-9] /1100/g
p
}' inputfile
事实上,替换后输出一个行是很常见的用法,因此,替换命令也接受 p 选项:
sed -En -e '/sonia/s/[0-9] /1100/gp' inputfile
最后,我就不详细讲替换命令的 w 选项了,我们将在稍后的学习中详细介绍。
删除命令删除命令(d)用于清除模式空间的内容,然后立即开始下一个处理循环。这样它将会跳过隐式输出模式空间内容的行为,即便是你设置了自动输出标志(AP)也不会输出。
The Sed `delete` command
只输出一个文件前五行的一个很低效率的方法将是:
sed -e '6,$d' inputfile
你猜猜看,我为什么说它很低效率?如果你猜不到,建议你再次去阅读前面的关于退出命令的章节,答案就在那里!
当你组合使用正则表达式和地址,从输出中删除匹配的行时,删除命令将非常有用:
次行命令sed -e '/systemd/d' inputfile
如果 Sed 命令没有运行在静默模式中,这个命令(n)将输出当前模式空间的内容,然后,在任何情况下它将读取下一个输入行到模式空间中,并使用新的模式空间中的内容来运行当前循环中剩余的命令。
The Sed next command
用次行命令去跳过行的一个常见示例:
cat -n inputfile | sed -n -e 'n;n;p'
在上面的例子中,Sed 将隐式地读取输入文件的第一行。但是次行命令将丢弃对模式空间中的内容的输出(不输出是因为使用了 -n 选项),并从输入文件中读取下一行来替换模式空间中的内容。而第二个次行命令做的事情和前一个是一模一样的,这就实现了跳过输入文件 2 行的目的。最后,这个脚本显式地输出包含在模式空间中的输入文件的第三行的内容。然后,Sed 将启动一个新的循环,由于次行命令,它会隐式地读取第 4 行的内容,然后跳过它,同样地也跳过第 5 行,并输出第 6 行。如此循环,直到文件结束。总体来看,这个脚本就是读取输入文件然后每三行输出一行。
使用次行命令,我们也可以找到一些显示输入文件的前五行的几种方法:
cat -n inputfile | sed -n -e '1{p;n;p;n;p;n;p;n;p}'
cat -n inputfile | sed -n -e 'p;n;p;n;p;n;p;n;p;q'
cat -n inputfile | sed -e 'n;n;n;n;q'
更有趣的是,如果你需要根据一些地址来处理行时,次行命令也非常有用:
使用保持空间cat -n inputfile | sed -n '/pulse/p' # 输出包含 “pulse” 的行
cat -n inputfile | sed -n '/pulse/{n;p}' # 输出包含 “pulse” 之后的行
cat -n inputfile | sed -n '/pulse/{n;n;p}' # 输出包含 “pulse” 的行的下一行的下一行
到目前为止,我们所看到的命令都是仅使用了模式空间。但是,我们在文章的开始部分已经提到过,还有第二个缓冲区:保持空间,它完全由用户管理。它就是我们在第二节中描述的目标。
交换命令正如它的名字所表示的,交换命令(x)将交换保持空间和模式空间的内容。记住,你只要没有把任何东西放入到保持空间中,那么保持空间就是空的。