Linxu-Sed 进阶

前言

sed 命令是 Linux 核心命令之一,熟悉 sed 命令可以让程序员在 Linux 环境下更加灵活的处理各种文本,做到事半功倍的效果,甚至再也不用打开文件逐行编辑。

笔者将个人对 sed 理解加以归纳整理,供大家学习借鉴,如果不当之处欢迎斧正。

基本格式

首先介绍下 sed 命令的基本格式,万变不离其宗

[address]command

其中 command 可以有多个,形如

[address]{
command
command
}

如果 address 为空,可直接省略花括号 {}

address

所谓 address 就是定义将 command[flags] 用在特定行上

如果 address 为空,则 sed 逐行执行 command

两种寻址方式

数字方式行寻址

我们可以简单的使用 1,2,3,4 等表示第几行,且 </code> 表示最后一行,比如</p><ul data-tool=

  • 2 表示选中第二行数据
  • 1,3 表示选中 1 ~ 3 行数据,闭区间
  • 1,"> 表示从第一行到最后一行,闭区间
  • 文本模式匹配寻址

    从数据内容出发,支持正则匹配找到对应行。假设有文本 text1 内容

    This is first line
    This is second line
    This is third line
    This is forth line
    • /third/可以匹配到第三行
    • /f/可以匹配第一行和第四行
    • /first/,/third/可以匹配 1 ~ 3 行,闭区间
    • /^This is [a-z]{4} line/</code>可以匹配 <code style=1,3,4

    前三个例子是文本模式匹配,最后一个例子是正则匹配,表示匹配 This is 和 line 之间有四个 a-z 组成的字母的行,对正则表达式不清楚的伙伴可以参考我之前发到文章「Linux-正则表达式」,对此做了详细的说明

    反向寻址

    在寻址后面加上 !表明选取这段地址之外的行,比如

    • 2,3!表示选取除了第二行和第三行之外的所有行
    • /first/! 表示选取 2,3,4

    command

    s 替换

    替换命令是最常见之一,使用格式为

    s/pattern/target/[flags]

    • 其中 pattern 支持正则匹配

    • 在 target 使用 &表示 pattern

    • 可以对 pattern 进行分组,在 target 中依次使用\1,\2表示组位置

    • flags 有四个可选项

    • 数字,表明每一行将替换第几处匹配成功的地方

    • g, 表明新文本将会替换所有匹配的文本

    • p,表明原先行的内容要打印出来

    • w file,将替换的结果写入一个文件中

    例如:

    • echo "I have a cat" | sed 's/.at/<&>/' 输出为 I have a <cat>

    在 posix 规范的正则中,点号 .表示任意字符,因此 pattern 部分可以匹配到 cat 字符。在 target 部分中,用 & 表示 pattern 即 cat,因为最终结果为用替换了 原文中的 cat

    • echo "I have a cat" | sed 's/c\(.*\)/h\1/'输出为I have a hat

    pattern: c\(.*\)

    target: h\1

    由于 sed 本身仅支持 BRE 规则,如果要使用分组功能,需要给小括号()加上反斜线来转义,括号内部匹配任意字符串,这里分组匹配到了 at。在 target 部分,使用 \1表示第一个分组内容,也就是 at,因此 pattern 部分组合起来就是 hat。

    对文本 text1(内容见上文) 做如下操作

    ⚡ root@dnvim  ~/test  sed 's/is/at/' test1
    That is first line
    That is second line
    That is third line
    That is forth line

    每行只对第一个 is 替换成了 at

    ⚡ root@dnvim  ~/test  sed 's/is/at/2' test1
    This at first line
    This at second line
    This at third line
    This at forth line

    每行对第二个 is 替换成了 at

    ⚡ root@dnvim  ~/test  sed 's/is/at/g' test1
    That at first line
    That at second line
    That at third line
    That at forth line

    所有的 is 都替换成 at

    ⚡ root@dnvim  ~/test  sed 's/is/at/gw test.out' test1
    That at first line
    That at second line
    That at third line
    That at forth line

    ⚡ root@dnvim  ~/test  cat test.out
    That at first line
    That at second line
    That at third line
    That at forth line

    将替换结果写入文件 test.out 中

    当然,也可以用上寻址标识,只对特定行数执行替换命令

    ⚡ root@dnvim  ~/test  sed '2s/is/at/g' test1
    This is first line
    That at second line
    This is third line
    This is forth line

    只针对第二行,将所有的 is 都替换成 at

    d 删除

    删除命令,相对简单,即将当前行删掉

    ⚡ root@dnvim  ~/test  sed '1d' test1
    This is second line
    This is third line
    This is forth line

    结合 address部分内容可知 1d含义是将 第一行删除

    ⚡ root@dnvim  ~/test  sed '/f/d' test1
    This is second line
    This is third line

    /f/d是将带有 f 的行都删掉

    ⚡ root@dnvim  ~/test  sed '/f/!d' test1
    This is first line
    This is forth line

    /f/!d这里对地址取反,对于不含有 f 的行都删掉

    i 插入

    插入命令,在指定行的前一行插入数据

    ⚡ root@dnvim  ~/test  sed '2i This is new line' test1
    This is first line
    This is new line
    This is second line
    This is third line
    This is forth line

    这里在第二行之前插入了一个 new line

    ⚡ root@dnvim  ~/test  sed '/f/i This is new line' test1
    This is new line
    This is first line
    This is second line
    This is third line
    This is new line
    This is forth line

    在所有带有 f 的行之前,都插入一个 new line

    a 附加

    与 「插入」命令类似,只是在指定行后插入

    ⚡ root@dnvim  ~/test  sed '2a This is new line' test1
    This is first line
    This is second line
    This is new line
    This is third line
    This is forth line

    在第二行之后插入一个 new line

    c 修改

    可以理解为替换,用指定数据替换指定的行内容

    ⚡ root@dnvim  ~/test  sed '2c This is new line' test1
    This is first line
    This is new line
    This is third line
    This is forth line

    可以看到第二行的内容被替换了

    y 转换

    转换命令可以单独修改一个字符,比如

    ⚡ root@dnvim  ~/test  sed 'y/Th/ta/' test1
    tais is first line
    tais is second line
    tais is taird line
    tais is forta line

    可以将 T 替换成 t , h 替换成 a

    替换的两个参数长度必须严格相等,否则会报错如下所示

    ⚡ root@dnvim  ~/test  sed 'y/Th/that/' test1
    sed: -e expression #1, char 10: strings for `y' command are different lengths

    p/=/l 打印

    p 只是原封不动的打印选中的行(专业术语:模式空间)

    = 打印行数

    l (小写的 L)打印完成的字符串,包括被转义的字符

    为了演示方便,我们在 sed 命令后加一个 -n 参数,表示关闭自动输出到控制台的能力

    ⚡ root@dnvim  ~/test  sed -n 'p' test1
    This is first line
    This is second line
    This is third line
    This is forth line

    ⚡ root@dnvim  ~/test  sed -n '=' test1
    1
    2
    3
    4

    ⚡ root@dnvim  ~/test  sed -n 'l' test1
    This is first line">
    This is second line
    This is forth line</code></pre><h3 data-tool=w 写入

    这里的 写入 是一个 command,需要和下文中 flag 中的写入区分。

    我们可以指定第几行写入到另一个文件中,格式为 [address]w file

    ⚡ root@dnvim  ~/test  sed '2,3w test2' test1
    This is first line
    This is second line
    This is third line
    This is forth line

    ⚡ root@dnvim  ~/test  cat test2
    This is second line
    This is third line

    我们将 第二行 和 第三行 内容写入到 test2 文件中

    r 读取

    可以理解为,我们从另一个文件中读取内容,并且 append到指定行之后,格式为 [address]r file

    ⚡ root@dnvim  ~/test  cat test3
    this is test3 file, line 1
    this is test3 file, line 2

    ⚡ root@dnvim  ~/test  sed '">r test3' test1
    This is first line
    This is second line
    This is third line
    This is forth line
    this is test3 file, line 1
    this is test3 file, line 2

    我们将文件 test3 的内容 append 到了 test1 文件的最后一行。这里我们如果不加 address 部分,那么对 test1 文件的每一行都会 append 上 test3 文件的内容

    ⚡ root@dnvim  ~/test  sed 'r test3' test1
    This is first line
    this is test3 file, line 1
    this is test3 file, line 2
    This is second line
    this is test3 file, line 1
    this is test3 file, line 2
    This is third line
    this is test3 file, line 1
    this is test3 file, line 2
    This is forth line
    this is test3 file, line 1
    this is test3 file, line 2

    进阶

    模式空间和保持空间

    • 模式空间:(pattern space)是一块活跃的缓冲区,在 sed 编辑器执行命令时它会保存待检查的文本
    • 保持空间:(hold space) 在处理模式空间中的某些行时,可以用保持空间来临时保存一些行

    sed 执行流程如下图所示:

    1. 从第一行开始(或者从 address 定义的地方开始),每读到一行,将该行内容写入模式空间
    2. sed 命令(command 部分) 处理 模式空间中的数据
    3. 如果 sed 命令 options 中不带有 -n,则将 模式空间 中的数据打印到 console
    4. 清空模式空间,并且读取文本文件的下一行内容(或者 address 指定的下一行内容)
    图片
    sed 模式空间和保持空间图解

    空间操作

    操作模式空间

    所有的 command 都是针对上述的步骤 2

    n 单行 next 命令

    告诉 sed 编辑器移动到数据流中的下一文本行,而不用重新回到命令的最开始再执行一遍,整体流程就好像:

    • (上述步骤 3) 如果 sed 命令 options 中不带有 -n,则将 模式空间 中的数据打印到 console
    • 将文本文件的下一行替换到模式空间中
    • 执行 command 剩余的命令(如果有)
    ⚡ root@dnvim  ~/test  sed '2{
    quote> n
    quote> s/This/That/
    quote> }' test1

    This is first line
    This is second line
    That is third line
    This is forth line

    这里用到了多行命令,我们指定寻址文本中的第二行数据,先用 n ,将 sed 移动到下一行,执行 替换命令

    N 合并文本行

    单行 next 命令会将数据流中的下一文本行移动到 sed 编辑器的工作空间(称为模式空间 )。多行版本的 next 命令(用大写 N)会将下一文本行添加到模式空间中已有的文本后。

    这样的作用是将数据流中的两个文本行合并到同一个模式空间中。文本行仍然用换行符分隔,但 sed 编辑器现在会将两行文本当成一行来处理。

    可以理解为将文本内容的下一行附加到了模式空间中

    假设我们有文本文件 test2 如下

    On Tuesday, the Linux System
    Administrator's group meeting will be held.
    All System Administrators should attend.
    Thank you for your attendance.

    我们想要替换 System Administrator 为 Desktop

    ⚡ root@dnvim  ~/test  sed 'N; s/System\nAdministrator/Desktop/' test2
    On Tuesday, the Linux Desktop's group meeting will be held.
    All System Administrators should attend.
    Thank you for your attendance.

    这里可以用 N 命令将下一行附加到当前行处理,不过需要注意的是,linux 中每个换行出有个 \n符号

    D 多行删除命令

    多行删除命令 D ,它只删除模式空间中的第一行。可以理解该命令会删除到换行符(含换行符)为止的所有字符。

    ⚡ root@dnvim  ~/test  sed 'N; /System\nAdministrator/D' test2
    Administrator's group meeting will be held.
    All System Administrators should attend.
    Thank you for your attendance.

    只删了第一行数据(截止到\n

    P 多行打印命令

    多行打印命令(P )沿用了同样的方法。它只打印多行模式空间中的第一行,这包括模式空间中直到换行符为止的所有字符。

    ⚡ root@dnvim  ~/test  sed -n 'N; /System\nAdministrator/P' test2
    On Tuesday, the Linux System

    这里为了突出 P 命令的功能,我们给 sed 添加 -n 选项,屏蔽常规输出。

    操作保持空间

    • h 将模式空间复制到保持空间

    • H 将模式空间追加到保持空间

    • g 将保持空间复制到模式空间

    • G 将保持空间追加到模式空间

    • x 交换模式空间和保持空间内容

    这些命令用来将文本从模式空间复制到保持空间。这可以清空模式空间来加载其他要处理的字符串。

    通常,在使用 h 或 H 命令将字符串移动到保持空间后,最终还要用 g 、G 或 x 命令将保存的字符串移回模式空间(否则,你就不用在一开始考虑保存它们了)。

    由于有两个缓冲区域,弄明白哪行文本在哪个缓冲区域有时会比较麻烦。这里有个简短的例子演示了如何用 h 和 g 命令来将数据在 sed 编辑器缓冲空间之间移动。

    ⚡ root@dnvim  ~/test  cat test1
    This is first line
    This is second line
    This is third line
    This is forth line
    ⚡ root@dnvim  ~/test  sed -n '/first/ {h ; p ; n ; p ; g ; p }' test1
    This is first line
    This is second line
    This is first line

    我们来一步一步看上面这个代码例子:

    1. sed 脚本在地址中用正则表达式来过滤出含有单词 first 的行;

    2. 当含有单词 first 的行出现时,h 命令将该行放到保持空间;

    3. p 命令打印模式空间也就是第一个数据行的内容;

    4. n 命令提取数据流中的下一行 (This is second line),并将它放到模式空间;

    5. p 命令打印模式空间的内容,现在是第二个数据行;

    6. g 命令将保持空间的内容(This is the first data line )放回模式空间,替换当前文本;

    7. p 命令打印模式空间的当前内容,现在变回第一个数据行了。

    通过使用保持空间来回移动文本行,你可以强制输出中第一个数据行出现在第二个数据行后面。如果丢掉了第一个 p 命令,你可以以相反的顺序输出这两行。

    ⚡ root@dnvim  ~/test  sed -n '/first/ {h ; n ; p ; g ; p }' test1
    This is second line
    This is first line

    这是个有用的开端。你可以用这种方法来创建一个 sed 脚本将整个文件的文本行反转

    流程控制

    流程控制中的 「分支」 和 「测试」 本质上还是一种命令command

    分支

    [address]b[label]

    之前提到过,sed 允许我们传入多个 command,这里再补充下,command 之间可以加一个 label (7 个字符以内),将命令分割开来。对于 b 命令,可以直接跳转到这个 label 位置,不用执行这个 label 之前的命令。

    ⚡ root@dnvim  ~/test  sed '{
    3b jump1
    s/T/t/
    :jump1s/ is/ was/}'
     test1
    this was first line
    this was second line
    This was third line
    this was forth line

    如上例, 3b jump1 表示在处理第三行的时候,command 直接跳转到 :jump1 位置, 忽略了 s/T/t/这条命令

    如果 b 命令后没有 label,则这行数据不会执行任何命令(直接跳转到 commands 最后一行)

    同理,如果 commands 中没有 b 后面的指定的 label,那么对应行数据也会得不到处理

    如果 b 命令前不指定 address,意味着每一行都会尝试「跳转」

    测试

    测试命令仅仅出现在替换命令中,如果替换命令发生替换(即 pattern 成功匹配),则触发跳转

    ⚡ root@dnvim  ~/test  sed '{
    s/second/SECOND/
    t jump1
    s/T/t/
    :jump1
    s/ is/ was/}'
     test1
    this was first line
    This was SECOND line
    this was third line
    this was forth line

    如上例,s/second/SECOND/在第二行发生,触发了「测试跳转」,略过 s/T/t/命令

    实战案例

    以上是 sed 的全部核心用法,根据上述内容,我们已经具备一些流式数据处理的能力了。比如有文本 tesst2 如下

    ⚡ root@dnvim  ~/test  cat test2
    This is first line



    This is second line



    This is third line




    This is forth line


    每行之间有很多空行,看起来非常不美观,我们想删除所有的空行可以怎么这么做

    ⚡ root@dnvim  ~/test  sed '/^/d'</span> test2<br>This is first line<br>This is second line<br>This is third line<br>This is forth line</code></pre><p data-tool=这里用到了 正则匹配 + 单行删除命令, 用 /^">/匹配所有空行,然后用 d 命令,将空行删除

    如果我们在每一行后面,只想保留一个空行,可以这么做

    ⚡ root@dnvim  ~/test  sed '/./,/^/!d'</span> test2<br>This is first line<br><br>This is second line<br><br>This is third line<br><br>This is forth line<br></code></pre><p data-tool=这里用到的是 正则匹配 + 地址取反 + 单行删除命令, 用 /./,/^">/匹配「数据+一个空行」,地址取反,不符合这个规则的行,我们全部删除

    这里看到文本末尾还是空行,如果想将末尾的空行删除,可以再加一个命令如下

    ⚡ root@dnvim  ~/test  sed -e '/./,/^d'
    This is first line

    This is second line

    This is third line

    This is forth line

    这里接上管道符号,在下一个 sed 命令中,删除最后一行空白

    本文使用 文章同步助手 同步

    你可能感兴趣的:(Linxu-Sed 进阶)