Linux “文本三剑客”之 sed

“文本三剑客”中,grep是文本过滤器,而sed是基于行的文本流编辑器。
sed是将文件中的文本逐行读取到内存中进行处理。

  1. sed工作原理
  2. sed命令
    2.1 OPTION
    2.2 SCRIPT
  3. sed使用示例

1 sed工作原理

sed有两个工作空间,Pattern space(模式空间)和Hold space(保持空间)。
Linux “文本三剑客”之 sed

Pattern space 模式空间

sed会把文件内容的每一行复制一份出来放到自己的Pattern space中,在其中处理以后,处理的结果送到stdout(标准输出):

  1. 默认情况sed会处理每一行,但我们可以让sed处理只被模式(如正则表达式)匹配到的文本。
  2. Pattern space中被用户给出的模式匹配到的内容,则edit(编辑)后再送到stdout。
  3. 如果不能匹配,则不做任何编辑操作,直接输出到stdout。

Hold space 保持空间

Hold space用于在使用高级编辑功能时,实现与Pattern space空间中的内容进行追加、覆盖、互换等操作。后边高级编辑示例中会详细解释。

2 sed命令

sed - stream editor for filtering and transforming text
sed [OPTION]… ‘SCRIPT’ [input-file]…
sed命令需要将命令选项OPTION和处理脚本SCRIPT结合使用,来对文本进行特定的处理动作。

2.1 OPTION

  • -n:不输出模式空间的内容至屏幕stdout
    [默认情况下,会将不被pattern匹配到的内容直接输出到stdout,-n则不输出这部分内容]
  • -e script,–expression=script:多点编辑;
    默认只支持一个script(被pattern匹配到的内容将被执行的编辑命令),加-e可以指定多个script。
    如:sed -e ‘s@^#[[:space:]]*@@’ -e ‘/^UUID/d’ /etc/fstab
  • -f /PATH/TO/SED_SCRIPT_FILE
    SED_SCRIPT_FILE中,每行都有一个script,从文件中逐行读取script,省去-e的繁琐
  • -r,–regexp-extended:支持扩展正则表达式
  • -i[SUFFIX],–in-place[=SUFFIX]:直接编辑原文件(有风险,用之前先做备份)(sed默认不直接编辑原文件)。

2.2 SCRIPT

SCRIPT由"地址定界"和"编辑命令"两部分组成,和vi编辑器的末行模式命令相似。两部分中间无空格。

地址定界的语法格式
1. 地址定界为空,表示全文;
2. 单地址:
   N:指定一个行号,表示第N行;
     /PATTERN/:给出一个模式,表示被模式匹配到的每一行。
3. 地址范围:
   M,N:表示从第M行到第N行;
     M,+N:表示从第M行开始,到往后N行之间的所有行(包含第M行);
     M,/PATTERN/:表示从第M行到被PATTERN匹配到的第一行之间的所有行
     /PATTERN1/,/PATTERN2/:表示从被PATTERN1匹配到的第一行,到被PATTERN2匹配到的第一行之间的所有行。
4. 用“~”表示步进:
   如:1\~2表示所有奇数行,2\~2表示所有偶数行。  
编辑命令
  1. d:delete pattern space,删除模式匹配到的内容(模式空间中的内容)
    d是一个特殊的操作
  2. p:print the current pattern space:打印模式匹配到的内容(模式空间中的内容)
    p也是一个特殊的操作,会将pattern匹配到的内容显示两遍,如果不加选项,显示结果为,模式空间中的全部内容+被pattern匹配到的内容;
    如果只想显示被pattern匹配的内容,配合使用-n选项,将不显示默认输出的模式空间全部内容。
  3. a \text:append,在模式匹配到的行后面追加文本“text”,支持使用\n(换行符号)实现多行追加(注意文本前用\转义)
  4. i:insert,在模式匹配到的行前面插入文本“text”,支持使用\n(换行符号)实现多行插入(注意文本前用\转义)
    比如,在fstab文件中所有以UUID开头的行前面加入注释:
    sed ‘/^UUID/i # Add device based on UUID.’ fstab
  5. c \text:把匹配到的行替换为"text"(注意是整行替换!!);
  6. w /PATH/TO/SOMEFILE:保存模式匹配到的行至指定的文件中;
    例如:将fstab中所有以非#开头的行保存到当前目录下的fstab.new文件中:
    sed ‘/[#]/w fstab.new’ fstab
  7. r /PATH/FROM/SOMEFILE:读取指定文件的内容至当前文件被模式匹配到的行后面;
    例如:将/etc/issue文件中的内容显示到fstab文件第3行的后面:
    sed ‘3r /etc/issue’ fstab
  8. =:为模式匹配到的行打印行号(在行的前面另起一个新行来打印行号);
    例如:将fstab中所有以UUID开头的行都打印行号:
    sed ‘/^UUID/=’ fstab
  9. !:条件取反(!放在地址定界和编辑命令之间)
    地址定界!编辑命令
    例如:前边有个例子,“将fstab中所有以非#开头的行保存到当前目录下的fstab.new文件中”
    我们使用 “sed ‘/[#]/w fstab.new’ fstab”;
    在这里可以这样“ sed ‘/^#/!w fstab.new’ fstab”,它表示所有#号开头的行都不写入fstab.new文件,即非#开头的行就写入。
  10. s///:查找替换,其分隔符可自行指定,常用的有s@@@,s###等;
    替换标记:
    g:全局替换
    w /PATH/TO/SOMEFILE:将替换成功的结果保存至指定文件中;
    p:显示替换成功的行;
高级编辑命令

高级编辑命令用于Pattern space和Hold space中内容的追加、覆盖和互换,有以下几个:

  1. Pttern space --> Hold space
    h:把模式空间中的内容覆盖至 > 保持空间(hold space)中;
    H:把模式空间中的内容追加至 >> 保持空间(hold space)中;
  2. Hold space --> Pattern space
    g:把保持空间中的内容覆盖至 > 模式空间中;
    G:把保持空间中的内容追加至 >> 模式空间中;
  3. 模式匹配的下一行 --> Pattern space
    n:覆盖读取匹配到的行的下一行至 > 模式空间中
    N:追加读取匹配到的行的下一行至 >> 模式空间中
  4. Pattern space <–> Hold space
    x:把模式空间中的内容与保持空间中的内容互换;
  5. 删除Pattern sapce中的行
    d:删除模式空间中的行
    D:删除多行模式空间中的所有行(多行模式空间比如,N命令追加到模式空间中行)

3 sed使用示例

普通编辑命令示例

sed是否用得好,主要是看SCRIPT的编写能力。

  1. 删除/etc/grub2.cfg文件中所有以空白字符开头的行的行首的所有空白字符:
    sed 's@^[[:space:]]\+@@' /etc/grub2.cfg
  2. 删除/etc/fstab文件中所有以#开头的行的行首的#号及#后边的所有空白字符:
    sed 's@^#[[:space:]]*@@' /etc/fstab
  3. 输出一个绝对路径给sed命令,要求取出其目录名,类似dirname命令执行结果:
    echo "/var/log/messages" | sed 's@[^/]\+/\?$@@'
    或使用-r选项支持扩展正则表达式:
    echo "/var/log/messages" | sed -r 's@[^/]+/?$@@'

高级编辑命令示例与命令解析

要使用sed的高级编辑命令,需要对sed的工作流程非常熟悉。

  • sed -n 'n;p' FILE
    显示偶数行(;分号用来分隔多个命令)
    命令解析:
    因为是n;p,没有地址定界,表示逐行读取所有内容。
    1、首先读取第1行到pattern space,遇到第一个命令是n,覆盖读取下一行(也就是第2行)到pattern space,所以现在pattern space中的内容变为第2行(第1行被覆盖掉);
    2、然后是p命令,将pattern space中的第2行显示出来;
    3、接着读取第3行到pattern space,同上,显示第4行;
    4、以此类推,显示所有偶数行。

注意:如果没有-n选项,则将显示全部内容,并且偶数行显示两遍。

  • sed '1!G;h;$!d' FILE
    逆序显示文件内容,相当于tac
    命令解析:
    1、读取第1行到pattern space,先碰到“1!G”命令,表示第1行不做G操作;
    2、然后碰到下一个命令“h”,表示将pattern space中的内容覆盖至hold space,就是将第1行内容复制到了hold space中;
    3、接下来碰到“ ! d ” 命 令 , 表 示 将 p a t t e r n s p a c e 中 不 是 最 后 一 行 的 内 容 全 部 删 除 , p a t t e r n s p a c e 中 当 前 还 有 第 1 行 的 内 容 , 则 将 其 删 除 。 第 1 行 的 读 取 到 此 结 束 。 4 、 接 着 读 取 第 2 行 , “ 1 ! G ” 命 令 将 h o l d s p a c e 中 的 内 容 ( 第 1 行 的 内 容 ) 覆 盖 至 p a t t e r n s p a c e , 所 以 目 前 p a t t e r n s p a c e 中 的 内 容 变 为 “ 第 2 行 + 第 1 行 ” ; 5 、 然 后 碰 到 “ h ” 命 令 , 将 p a t t e r n s p a c e 中 的 内 容 ( 第 2 行 + 第 1 行 ) 复 制 到 h o l d s p a c e 中 ( h o l d s p a c e 中 原 来 的 第 1 行 内 容 被 覆 盖 掉 ) , 所 以 现 在 h o l d s p a c e 中 的 内 容 为 “ 第 2 行 + 第 1 行 ” ; 6 、 下 一 个 命 令 “ !d”命令,表示将pattern space中不是最后一行的内容全部删除,pattern space中当前还有第1行的内容,则将其删除。第1行的读取到此结束。 4、接着读取第2行,“1!G”命令将hold space中的内容(第1行的内容)覆盖至pattern space,所以目前pattern space中的内容变为“第2行+第1行”; 5、然后碰到“h”命令,将pattern space中的内容(第2行+第1行)复制到hold space中(hold space中原来的第1行内容被覆盖掉),所以现在hold space中的内容为“第2行+第1行”; 6、下一个命令“ !d”命令,表示将patternspace中不是最后一行的内容全部删除,patternspace中当前还有第1行的内容,则将其删除。第1行的读取到此结束。4、接着读取第2行,“1!G”命令将holdspace中的内容(第1行的内容)覆盖至patternspace,所以目前patternspace中的内容变为“第2行+第1行”;5、然后碰到“h”命令,将patternspace中的内容(第2行+第1行)复制到holdspace中(holdspace中原来的第1行内容被覆盖掉),所以现在holdspace中的内容为“第2行+第1行”;6、下一个命令“!d”,同上,将pattern space中的“第2行+第1行”内容删除。
    第2行的读取到此结束。
    7、以此类推,知道读取第n-1行结束时,hold space的内容为“第n-1行+第n-2行…第2行+第1行”,pattern space为空;
    8、最后读取第n行,“1!G”命令将hold space内容追加到pattern space,则pattern space内容变为“第n行…第1行”;
    9、“h”命令将pattern space的内容覆盖复制到hold space,则现在hold space的内容为“第n行…第1行”;
    10、最后碰到“$!d”命令,因为是最后一行,则不删除pattern space的内容,所以pattern space中的内容还是“第n行…第1行”。
    11、读取完全部内容后,默认输出pattern space中的全部内容,即“第n行…第1行”,将原内容逆序显示!

  • sed '$!d' FILE
    取出最后一行,相当于tail -1
    命令解析:
    1、读取第1行到pattern space,pattern中的命令为“$!d”(不是最后一行,则删除),所以把第1行从pattern space中删了
    2、依次类推,一直删到倒数第2行;
    3、到最后一行,不删了,保留在pattern space中,然后输出到stdout。

  • 其他几个高级编辑命令示例
    sed '$!N;$!D' FILE:显示最后两行,相当于tail -2
    sed '/^$/d;G' FILE:删除原有所有空白行,再为所有非空白行后添加一个空白行
    sed 'n;d' FILE:显示奇数行;
    sed 'G' FILE:在原有的每行后方添加一个空白行;

上一篇:Linux正则表达式实践


下一篇:Linux's swap 分区的妙用