zoukankan      html  css  js  c++  java
  • 十二、sed文本处理

    一、概述

    1.sed 是一款流编辑工具,用来对文本进行过滤与替换工作,特别是当你想要对几十个配置文件做统计修改时,你会感受到 sed 的魅力!
    sed 通过输入读取文件内容,但一次仅读取一行内容进行某些指令处理后输出,所以 sed更适合于处理大数据文件。
    2.sed 流程:
    * 通过文件或管道读取文件内容。
    * sed 并不直接修改源文件,而是将读入的内容复制到缓冲区中,我们称之为模式空间(pattern space)。
    * 根据 sed 的指令对模式空间中的内容进行处理并输出结果,默认输出至标准输出即屏幕上。

    二、sed 语法和基本命令



    基本用法

    sed [options] {sed-commands} {input-file}

     sed 每次从 input-file 中读取一行记录,并在该记录上执行 sed-commands
    sed 首先从 input-file 中读取第一行,然后执行所有的 sed-commands;再读取第二行,执行所有 sed-commands,重复这个过程,直到 input-file 结束
    通过制定[options] 还可以给 sed 传递一些可选的选项

    常见命令选项;改变数据处理的方式

    命令选项    注释
    -n    屏蔽默认输出(全部文本),不再默认显示模式空间中的内容
    -i,--in-place      直接修改源文件内容
    -f    -f script-file,--file=script-file              从文件中读取脚本指令,对编写自动脚本程序来说很棒!
    -e SCRIPT -e SCRIPT:可以同时执行多个脚本
    -r    启用扩展的正则表达式,若与其他选项一起使用,应作为首个选项
    {}    可组合多个命令,以分号分隔
    --version                     显示 sed 版本。
    --help                           显示帮助文档。
    -l N, --line-length=N       该选项指定 l 指令可以输出的行长度,l 指令用于输出非打印字符。
    --posix                     禁用 GNU sed 扩展功能。

    下面的例子演示了 sed 的基本语法,它打印出/etc/passwd 文件中的所有行

    sed –n 'p' /etc/passwd


    该例子的重点在于, {sed-commands}既可以是单个命令,也可以是多个命令。你也可以把多个 sed 命令合并到一个文件中,这个文件被称为 sed 脚本,然后使用-f 选项调用它,如下面的例子:
    使用 sed 脚本的基本语法:

    sed [ options ] –f {sed-commands-in-a-file} {input-file}


    下面的例子演示了使用 sed 脚本的用法,这个例子将打印/etc/passwd 问中以 root nobody
    开头的行:

    vi test-script.sed
    /^root/ p
    /^nobody/ p
    $ sed –n –f test-script.sed /etc/passwd


    你也可以使用 –e 选项,执行多个 sed 命令,如下所示:
    -e 的使用方法

    sed [ options ] –e {sed-command-1} –e {sed-command-2} {input-file}

    下面的例子演示了-e 的使用方法,它打印/etc/passwd 中以 root nobody 开头的行:

    [root@ceph-node5 ~]# sed -n -e '/^root/ p' -e '/^nobody/ p' /etc/passwd 


    如果使用-e 执行多个命令,也可以使用反斜杠把它们分割到多行执行:

    sed -n 
    -e '/^root/ p' 
    -e '/^nobody/ p' 
    /etc/passwd


    也可以使用{ }将多个命令分组执行:
    {}的使用方法:

    sed [options] '{
    sed-command-1
    sed-command-2
    }' input-file


    下面的例子演示了{}的使用方法,打印/etc/passwd 中以 root nobody 开头的行:

    sed -n '{
    /^root/ p
    /^nobody/ p
    }' /etc/passwd


    注意:sed 绝不会修改原始文件 input-file,它只是将结果内容输出到标准输出设备。 如果要保持变更,应该使用重定向 > filename.txt

    Sed 脚本执行流程

    Sed 脚本执行遵从下面简单易记的顺序: Read,Execute,Print,Repeat(读取,执行,打印,重复)
    简称 REPR
    分析脚本执行顺序:

    读取一行到模式空间(sed 内部的一个临时缓存,用于存放读取到的内容)
    在模式空间中执行命令。如果使用了{ } 或 –e 指定了多个命令, sed 将依次执行每个命令
    打印模式空间的内容,然后清空模式空间
    重复上述过程,直到文件结束

    如图:

    打印模式空间(命令 p)

     

    所有的示例都要用到下面的 employee.txt 文件,请先行创建该文件。

    [root@ceph-node5 ~]#  vim employee.txt
    101,John Doe,CEO
    102,Jason Smith,IT Manager
    103,Raj Reddy,Sysadmin
    104,Anand Ram,Developer
    105,Jane Miller,Sales Manager

    使用命令 p,可以打印当前模式空间的内容。
    sed 在执行完命令后会默认打印模式空间的内容,既然如此,那么你可能会问为何还需要命令 p 呢。
    有如下原因,命令 p 可以控制只输出你指定的内容。通常使用 p 时,还需要使用-n 选项来
    屏蔽 sed 的默认输出,否则当执行命令 p 时,每行记录会输出两次。

    [root@ceph-node5 ~]# sed 'p' employee.txt
    101,John Doe,CEO
    101,John Doe,CEO
    102,Jason Smith,IT Manager
    102,Jason Smith,IT Manager
    103,Raj Reddy,Sysadmin
    103,Raj Reddy,Sysadmin
    104,Anand Ram,Developer
    104,Anand Ram,Developer
    105,Jane Miller,Sales Manager
    105,Jane Miller,Sales Manager

    输出 employee.txt 的内容,只打印一行(cat employee.txt 命令作用相同):

    [root@ceph-node5 ~]# sed -n 'p' employee.txt
    101,John Doe,CEO
    102,Jason Smith,IT Manager
    103,Raj Reddy,Sysadmin
    104,Anand Ram,Developer
    105,Jane Miller,Sales Manager

    定址符

    定址符,即[地址1,[地址2]]
    是可选项,用来指定处理的起、止行数
    不指定定址符时默认处理全部文本
    定址符的表示方式可以使用行号和正则表达式

     例如:

    Address:
    1、StartLine,EndLine
        比如1,100
        $:最后一行
    2、/RegExp/
        /^root/
    3、/pattern1/,/pattern2/
        第一次被pattern1匹配到的行开始,至第一次被pattern2匹配到的行结束,这中间的所有行
    4、LineNumber
        指定的行
    5、StartLine, +N
        从startLine开始,向后的N行;

    如果在命令前面不指定地址范围,那么默认会匹配所有行。 下面的例子,在命令前面指定了地址范围:

    只打印第 2 :

    [root@ceph-node5 ~]#  sed -n '2p' employee.txt  
    102,Jason Smith,IT Manager


    打印第 1 至第 4 :

    [root@ceph-node5 ~]# sed -n '1,4 p' employee.txt
    101,John Doe,CEO
    102,Jason Smith,IT Manager
    103,Raj Reddy,Sysadmin
    104,Anand Ram,Developer


    打印第 2 行至最后一行($代表最后一行):

    [root@ceph-node5 ~]#  sed -n '2,$ p' employee.txt
    102,Jason Smith,IT Manager
    103,Raj Reddy,Sysadmin
    104,Anand Ram,Developer
    105,Jane Miller,Sales Manager

    修改地址范围

    可以使用逗号、加号、和波浪号来修改地址范围。
    上面的例子里面,就已经使用了逗号参与地址范围的指定。其意思很明了: n,m 代表第 n 至第 m 行。
    加号+配合逗号使用,可以指定相的若干行,而不是绝对的几行。如 n,+m 表示从第 n 行开始后的 m
    波浪号~也可以指定地址范围。 它指定每次要跳过的行数。如 n~m 表示从第 n 行开始,每次跳过 m 行:

    1~2 匹配 1,3,5,7,……
    2~2 匹配 2,4,6,8,……
    1~3 匹配 1,4,7,10,…..
    2~3 匹配 2,5,8,11,…..

     只打印奇数行

    [root@ceph-node5 ~]# sed -n '1~2 p' employee.txt
    101,John Doe,CEO
    103,Raj Reddy,Sysadmin
    105,Jane Miller,Sales Manager


    模式匹配


    一如可以使用数字指定地址(或地址范围),也可以使用一个模式(或模式范围)来匹配,如下面的例子所示。
    打印匹配模式"Jane"的行:

    [root@ceph-node5 ~]# sed -n '/Jane/ p' employee.txt
    105,Jane Miller,Sales Manager


    打印第一次匹配 Jason 的行至第 4 行的内容:

    [root@ceph-node5 ~]#  sed -n '/Jason/,4 p' employee.txt
    102,Jason Smith,IT Manager
    103,Raj Reddy,Sysadmin
    104,Anand Ram,Developer



    注意:如果开始的 4 行中,没有匹配到 Jason,那么 sed 会打印第 4 行以后匹配到 Jason 的内容


    打印从第一次匹配 Raj 的行到最后的所有行:

    [root@ceph-node5 ~]# sed -n '/Raj/,$ p' employee.txt
    103,Raj Reddy,Sysadmin
    104,Anand Ram,Developer
    105,Jane Miller,Sales Manager


    打印自匹配 Raj 的行开始到匹配 Jane 的行之间的所有内容:

    [root@ceph-node5 ~]# sed -n '/Raj/,/Jane/ p' employee.txt
    103,Raj Reddy,Sysadmin
    104,Anand Ram,Developer
    105,Jane Miller,Sales Manager



    打印匹配 Jason 的行和其后面的两行:

    [root@ceph-node5 ~]#  sed -n '/Jane/,+2 p' employee.txt
    105,Jane Miller,Sales Manager

    删除行

    命令 d 用来删除行,需要注意的是它只删除模式空间的内容,和其他 sed 命令一样,命令 d不会修改原始文件的内容。
    如果不提供地址范围, sed 默认匹配所有行,所以下面的例子什么都不会输出,因为它匹配了所有行并删除了它们:

    sed 'd' employee.txt


    指定要删除的地址范围更有用,下面是几个例子:
    只删除第 2 :

    [root@ceph-node5 ~]# sed '2 d' employee.txt
    101,John Doe,CEO
    103,Raj Reddy,Sysadmin
    104,Anand Ram,Developer
    105,Jane Miller,Sales Manager


    删除第 1 至第 4 行:

    [root@ceph-node5 ~]# sed '1,4 d' employee.txt
    105,Jane Miller,Sales Manager

    删除第 2 行至最后一行:

    [root@ceph-node5 ~]# sed '2,$ d' employee.txt
    101,John Doe,CEO


    只删除奇数行:

    [root@ceph-node5 ~]#  sed '1~2 d' employee.txt
    102,Jason Smith,IT Manager
    104,Anand Ram,Developer



    删除匹配 Manager 的行:

    [root@ceph-node5 ~]#  sed '/Manager/ d' employee.txt
    101,John Doe,CEO
    103,Raj Reddy,Sysadmin
    104,Anand Ram,Developer


    删除从第一次匹配 Jason 的行至第 4 行:

    [root@ceph-node5 ~]# sed '/Jason/,4 d' employee.txt
    101,John Doe,CEO
    105,Jane Miller,Sales Manager


    如果开头的 4 行中,没有匹配 Jason 的行,那么上述命令将删除第 4 行以后匹配 Manager的行


    删除从第一次匹配 Raj 的行至最后一行:

    [root@ceph-node5 ~]# sed '/Raj/,$ d' employee.txt
    101,John Doe,CEO
    102,Jason Smith,IT Manager


    删除第一次匹配 Jason 的行和紧跟着它后面的两行:

    [root@ceph-node5 ~]# sed '/Jason/,+2 d' employee.txt
    101,John Doe,CEO
    105,Jane Miller,Sales Manager



    常用的删除命令示例:
    删除所有空行

    [root@ceph-node5 ~]# sed '/^$/ d' employee.txt
    


    删除所有注释行(假定注释行以#开头)

    sed '/^#/' employee.txt

    注意:如果有多个命令, sed 遇到命令 d 时,会删除匹配到的整行数据,其余的命令将无法操作被删除的行。

    把模式空间内容写到文件中(w 命令)

    命令 w 可以把当前模式空间的内容保存到文件中。默认情况下模式空间的内容每次都会打印到标准输出,如果要把输出保存到文件同时不显示到屏幕上,还需要使用-n 选项。
    下面是几个例子:
    employee.txt 的内容保存到文件 output.txt,同时显示在屏幕上

    [root@ceph-node5 ~]# sed 'w output.txt' employee.txt
    101,John Doe,CEO
    102,Jason Smith,IT Manager
    103,Raj Reddy,Sysadmin
    104,Anand Ram,Developer
    105,Jane Miller,Sales Manager

    [root@ceph-node5 ~]# cat output.txt
    101,John Doe,CEO
    102,Jason Smith,IT Manager
    103,Raj Reddy,Sysadmin
    104,Anand Ram,Developer
    105,Jane Miller,Sales Manager


    employee.txt 的内容保存到文件 output.txt,但不在屏幕上显示

    [root@ceph-node5 ~]#  sed -n 'w output.txt' employee.txt
    [root@ceph-node5 ~]# cat output.txt
    101,John Doe,CEO
    102,Jason Smith,IT Manager
    103,Raj Reddy,Sysadmin
    104,Anand Ram,Developer
    105,Jane Miller,Sales Manager


    只保存第 2 行:

    [root@ceph-node5 ~]# sed -n '2 w output.txt' employee.txt
    [root@ceph-node5 ~]# cat output.txt
    102,Jason Smith,IT Manager


    保存第 1 至第 4 行:

    [root@ceph-node5 ~]# sed -n '1,4 w output.txt' employee.txt
    [root@ceph-node5 ~]# cat output.txt
    101,John Doe,CEO
    102,Jason Smith,IT Manager
    103,Raj Reddy,Sysadmin
    104,Anand Ram,Developer


    保存第 2 行起至最后一行:

    [root@ceph-node5 ~]# sed -n '2,$ w output.txt' employee.txt
    [root@ceph-node5 ~]# cat output.txt
    102,Jason Smith,IT Manager
    103,Raj Reddy,Sysadmin
    104,Anand Ram,Developer
    105,Jane Miller,Sales Manager


    只保存奇数行:

    [root@ceph-node5 ~]# sed -n '1~2 w output.txt' employee.txt
    [root@ceph-node5 ~]# cat output.txt
    101,John Doe,CEO
    103,Raj Reddy,Sysadmin
    105,Jane Miller,Sales Manager



    保存匹配 Jane 的行:

    [root@ceph-node5 ~]# sed -n '/Jane/ w output.txt' employee.txt
    [root@ceph-node5 ~]# cat output.txt
    105,Jane Miller,Sales Manager



    保存第一次匹配 Jason 的行至第 4 行:

    [root@ceph-node5 ~]# sed -n '/Jason/,4 w output.txt' employee.txt
    [root@ceph-node5 ~]# cat output.txt
    102,Jason Smith,IT Manager
    103,Raj Reddy,Sysadmin
    104,Anand Ram,Developer

    注意: 如果开始的 4 行里没有匹配到 Jason,那么该命令只保存第 4 行以后匹配到 Jason


    保存第一次匹配 Raj 的行至最后一行:

    [root@ceph-node5 ~]#  sed -n '/Raj/,$ w output.txt' employee.txt
    [root@ceph-node5 ~]# cat output.txt
    103,Raj Reddy,Sysadmin
    104,Anand Ram,Developer
    105,Jane Miller,Sales Manager


    保存匹配 Raj 的行至匹配 Jane 的行:

    [root@ceph-node5 ~]# sed -n '/Raj/,/Jane/ w output.txt' employee.txt
    [root@ceph-node5 ~]# cat output.txt
    103,Raj Reddy,Sysadmin
    104,Anand Ram,Developer
    105,Jane Miller,Sales Manager


    保存匹配 Jason 的行以及紧跟在其后面的两行:

    [root@ceph-node5 ~]# sed -n '/Jason/,+2 w output.txt' employee.txt
    [root@ceph-node5 ~]# cat output.txt
    102,Jason Smith,IT Manager
    103,Raj Reddy,Sysadmin
    104,Anand Ram,Developer

    三、sed 替换命令

     流编辑器中最强大的功能就是替换(substitute),

    sed 替换命令语法

    sed '[address-range|pattern-range] s/original-string/replacement-string/[substitute-flags]' input file

     上面提到的语法为:

    address-range 或 pattern-range(即地址范围和模式范围)是可选的。 如果没有指定,那么 sed 将在所有行上进行替换。
    s 即执行替换命令 substitute
    original-string 是被 sed 搜索然后被替换的字符串,它可以是一个正则表达式
    replacement-string 替换后的字符串
    substitute-flags 是可选的,下面会具体解释

    请谨记原始输入文件不会被修改, sed 只在模式空间中执行替换命令,然后输出模式空间的内容。
    下面是一些简单的替换示例(替换的部分用黑体标明)
    Director 替换所有行中的 Manager:

    [root@ceph-node5 ~]# sed 's/Manager/Director/' employee.txt
    101,John Doe,CEO
    102,Jason Smith,IT Director
    103,Raj Reddy,Sysadmin
    104,Anand Ram,Developer
    105,Jane Miller,Sales Director


    只把包含 Sales 的行中的 Manager 替换为 Director:

    [root@ceph-node5 ~]# sed '/Sales/s/Manager/Director/' employee.txt
    101,John Doe,CEO
    102,Jason Smith,IT Manager
    103,Raj Reddy,Sysadmin
    104,Anand Ram,Developer
    105,Jane Miller,Sales Director

    注意: 本例由于使用了地址范围限制,所以只有一个 Manager 被替换了

    全局标志 g

    g 代表全局(global) 默认情况下, sed 至会替换每行中第一次出现的 original-string。如果你要替换每行中出现的所有 original-string,就需要使用 g


    用大写 A 替换第一次出现的小写字母 a

    [root@ceph-node5 ~]#  sed 's/a/A/' employee.txt
    101,John Doe,CEO
    102,JAson Smith,IT Manager
    103,RAj Reddy,Sysadmin
    104,AnAnd Ram,Developer
    105,JAne Miller,Sales Manager



    把所有小写字母 a 替换为大写字母 A

    [root@ceph-node5 ~]# sed 's/a/A/g' employee.txt
    101,John Doe,CEO
    102,JAson Smith,IT MAnAger
    103,RAj Reddy,SysAdmin
    104,AnAnd RAm,Developer
    105,JAne Miller,SAles MAnAger


    注意:上述例子会在所有行上替换,因为没有指定地址范围。

     数字标志(1,2,3 ….)

    使用数字可以指定 original-string 出现的次序。 只有第 n 次出现的 original-string 才会触发替换。 每行的数字从 1 开始,最大为 512
    比如/11 会替换每行中第 11 次出现的 original-string
    下面是几个例子:
    把第二次出现的小写字母 a 替换为大写字母 A

    [root@ceph-node5 ~]# sed 's/a/A/2' employee.txt
    101,John Doe,CEO
    102,Jason Smith,IT MAnager
    103,Raj Reddy,SysAdmin
    104,Anand RAm,Developer
    105,Jane Miller,SAles Manager

    为了方便,请先建立如下文件:

    [root@ceph-node5 ~]# vim substitute-locate.txt
    locate command is used to locate files
    locate command uses database to locate files
    locate command can also use regex for searching



    使用刚才建立的文件,把每行中第二次出现的 locate 替换为 find:

    [root@ceph-node5 ~]#  sed 's/locate/find/2' substitute-locate.txt
    locate command is used to find files
    locate command uses database to find files
    locate command can also use regex for searching


    注意:第 3 行中 locate 只出现了一次,所以没有替换任何内容

    打印标志 p(print)

    命令 p 代表 print。当替换操作完成后,打印替换后的行。与其他打印命令类似, sed 中比较有用的方法是和-n 一起使用以抑制默认的打印操作。
    只打印替换后的行:

    [root@ceph-node5 ~]# sed -n 's/John/Johnny/p' employee.txt
    101,Johnny Doe,CEO


    在之前的数字标志的例子中,使用/2 来替换第二次出现的 locate。第 3 行中 locate 只出现了一次,所以没有替换任何内容。使用 p 标志可以只打印替换过的两行。


    把每行中第二次出现的 locate 替换为 find 并打印出来:

    [root@ceph-node5 ~]# sed -n 's/locate/find/2p' substitute-locate.txt
    locate command is used to find files
    locate command uses database to find files

    写标志 w

    标志 w 代表 write。当替换操作执行成功后,它把替换后的结果保存的文件中。 多数人更倾向于使用 p 打印内容,然后重定向到文件中。

    为了对 sed 标志有个完整的描述, 在这里把这个标志也提出来了

    只把替换后的内容写到 output.txt :

    [root@ceph-node5 ~]# sed -n 's/John/Johnny/w output.txt' employee.txt
    [root@ceph-node5 ~]# cat output.txt
    101,Johnny Doe,CEO


    和之前使用的命令 p 一样,使用 w 会把替换后的内容保存到文件 output.txt 中。
    把每行第二次出现的 locate 替换为 find,把替换的结果保存到文件中,同时显示输入文件所有内容:

    [root@ceph-node5 ~]# sed 's/locate/find/2w output.txt' substitute-locate.txt
    locate command is used to find files
    locate command uses database to find files
    locate command can also use regex for searching
    [root@ceph-node5 ~]# cat output.txt
    locate command is used to find files
    locate command uses database to find files

    忽略大小写标志 i (ignore)

    替换标志 i 代表忽略大小写。 可以使用 i 来以小写字符的模式匹配 original-string。 该标志只有 GNU Sed 中才可使用。
    下面的例子不会把 John 替换为 Johnny,因为 original-string 字符串是小写形式:

    [root@ceph-node5 ~]# sed 's/john/Johnny/' employee.txt
    101,John Doe,CEO
    102,Jason Smith,IT Manager
    103,Raj Reddy,Sysadmin
    104,Anand Ram,Developer
    105,Jane Miller,Sales Manager



    john John 替换为 Johnny:

    [root@ceph-node5 ~]#  sed 's/john/Johnny/i' employee.txt
    101,Johnny Doe,CEO
    102,Jason Smith,IT Manager
    103,Raj Reddy,Sysadmin
    104,Anand Ram,Developer
    105,Jane Miller,Sales Manager

     

    执行命令标志 e (excuate)


    替换标志 e 代表执行(execute)。该标志可以将模式空间中的任何内容当做 shell 命令执行,
    并把命令执行的结果返回到模式空间。 该标志只有 GNU Sed 中才可使用。
    下面是几个例子:
    为了下面的例子,请先建立如下文件:

    [root@ceph-node5 ~]# vim files.txt
    /etc/passwd
    /etc/group



    files.txt 文件中的每行前面添加 ls –l 并打击结果:

    [root@ceph-node5 ~]#  sed 's/^/ls -l/' files.txt
    ls -l/etc/passwd
    ls -l/etc/group


    files.txt 文件中的每行前面添加 ls –l 并把结果作为命令执行:

    [root@ceph-node5 ~]#  sed 's/^/ls -l /e' files.txt
    -rw-r--r--. 1 root root 890 Oct 29 16:20 /etc/passwd
    -rw-r--r--. 1 root root 457 Oct 29 16:20 /etc/group



    使用替换标志组合


    根据需要可以把一个或多个替换标志组合起来使用。
    下面的例子将把每行中出现的所有 Manager manager 替换为 Director。然后把替换后的内容打印到屏幕上,同时把这些内容保存到 output.txt 文件中。
    使用 g,i,p w 的组合:

    [root@ceph-node5 ~]# sed -n 's/manager/Director/igpw output.txt' employee.txt
    102,Jason Smith,IT Director
    105,Jane Miller,Sales Director
    [root@ceph-node5 ~]# cat output.txt
    102,Jason Smith,IT Director
    105,Jane Miller,Sales Director

    sed 替换命令分界符


    上面所有例子中,我们都是用了 sed 默认的分界符/,s/original-string/replacement-string/g

    如果在 original-string replacement-string 中有/,那么需要使用反斜杠来转义。为了便于示例,请先建立下面文件:

    [root@ceph-node5 ~]# vim path.txt
    reading /usr/local/bin directory


    限制使用 sed /usr/local/bin 替换为/usr/bin。 在下面例子中, sed 默认的分界符/都被转义了:

    [root@ceph-node5 ~]# sed 's//usr/local/bin//usr/bin/' path.txt
    reading /usr/bin directory


    这很难看。 如果要替换一个很长的路径,每个/前面都使用转义,会显得很混乱。幸运的是,你可以使用任何一个字符作为 sed 替换命令的分界符,如 | ^ @ 或者 !
    下面的所有例子都比较易读。 这里不再显示下面例子的结果,因为它们的结果和上面的例子是相同的。 我个人比较倾向于使用@或者!来替换路径,但使用什么看你自己的偏好了。

    sed 's|/usr/local/bin|/usr/bin|' path.txt
    sed 's^/usr/local/bin^/usr/bin^' path.txt
    sed 's@/usr/local/bin@/usr/bin@' path.txt
    sed 's!/usr/local/bin!/usr/bin!' path.txt

     

    单行内容上执行多个命令


    一如之前所说, sed 执行的过程是读取内容、执行命令、打印结果、重复循环。 其中执行命令部分,可以由多个命令执行, sed 将一个一个地依次执行它们。
    例如,你有两个命令, sed 将在模式空间中执行第一个命令,然后执行第二个命令。

    如果第一个命令改变了模式空间的内容,第二个命令会在改变后的模式空间上执行(此时模式空间的内容已经不是最开始读取进来的内容了)
    下面的例子演示了在模式空间内执行两个替换命令的过程:
    Developer 替换为 IT Manager,然后把 Manager 替换为 Director:

    [root@ceph-node5 ~]# sed '{
    > s/Developer/IT Manager/
    > s/Manager/Director/
    >  }' employee.txt
    101,John Doe,CEO
    102,Jason Smith,IT Director
    103,Raj Reddy,Sysadmin
    104,Anand Ram,IT Director
    105,Jane Miller,Sales Director


    我们来分析下第 4 行的执行过程:

    1.读取数据: 在这一步, sed 读取内容到模式空间,此时模式空间的内容为:104,Anand Ram,Developer
    2.执行命令: 第一个命令, s/Developer/IT Manager/执行后,模式空间的内容为:104,Anand Ram,IT Manager
    现在在模式空间上执行第二个命令 s/Manager/Director/,执行后,模式空间内容为:104,Anand Ram,IT Director
    谨记: sed 在第一个命令执行的结果上,执行第二个命令。
    3.打印内容:打印当前模式空间的内容,如下:104,Anand Ram,IT Director
    4.重复循环: 移动的输入文件的下一行,然后重复执行第一步,即读取数据 

    &的作用——获取匹配到的模式


    当在 replacement-string 中使用&时,它会被替换成匹配到的 original-string 或正则表达式,这是个很有用的东西。
    看下面示例:
    给雇员 ID(即第一列的 3 个数字)加上[ ],101 改成[101]

    [root@ceph-node5 ~]# sed 's/^[0-9][0-9][0-9]/[&]/g' employee.txt
    [101],John Doe,CEO
    [102],Jason Smith,IT Manager
    [103],Raj Reddy,Sysadmin
    [104],Anand Ram,Developer
    [105],Jane Miller,Sales Manager



    把每一行放进< >中:

    [root@ceph-node5 ~]# sed 's/^.*/<&>/' employee.txt
    <101,John Doe,CEO>
    <102,Jason Smith,IT Manager>
    <103,Raj Reddy,Sysadmin>
    <104,Anand Ram,Developer>
    <105,Jane Miller,Sales Manager>

    分组替换(单个分组)


    跟在正则表达式中一样, sed 中也可以使用分组。分组以(开始,以)结束。 分组可以用在回溯引用中。
    回溯引用即重新使用分组所选择的部分正则表达式, 在 sed 替换命令的 replacement-string

    中和正则表达式中,都可以使用回溯引用。
    单个分组:

    [root@ceph-node5 ~]# sed 's/([^,]*).*/1/g' employee.txt
    101
    102
    103
    104
    105



    上面例子中:

    正则表达式([^,]*)匹配字符串从开头到第一个逗号之间的所有字符(并将其放入第一个分组中)
    replacement-string 中的1 将替代匹配到的分组
    g 即是全局标志

    下面这个例子只会显示/etc/passwd 的第一列,即用户名:

    [root@ceph-node5 ~]#  sed 's/([^:]*).*/1/' /etc/passwd


    下面的例子,如果单词第一个字符为大写,那么会给这个大写字符加上()

    [root@ceph-node5 ~]# echo "The Geek Stuff"|sed 's/([A-Z])/(1)/g'
    (T)he (G)eek (S)tuff


    请先建立下面文件,以便下面的示例使用:

    [root@ceph-node5 ~]# vim numbers.txt
    1
    12
    123
    1234
    12345
    123456



    格式化数字,增加其可读性:

    [root@ceph-node5 ~]# sed 's/(^|[^0-9.])([0-9]+)([0-9]{3})/12,3/g' numbers.txt
    1
    12
    123
    1,234
    12,345
    123,456

    分组替换(多个分组)


    你可以使用多个()划分多个分组,使用多个分组时,需要在 replacement-string 中使用 来指定第 n 个分组。如下面的示例。
    只打印第一列(雇员 ID)和第三列(雇员职位):

    [root@ceph-node5 ~]#  sed 's/^([^,]*),([^,]*),([^,]*)/1,3/' employee.txt
    101,CEO
    102,IT Manager
    103,Sysadmin
    104,Developer
    105,Sales Manager


    在这个例子中,可以看到, original-string 中,划分了 3 个分组,以逗号分隔。

    ([^,]*) 第一个分组,匹配雇员 ID,为字段分隔符
    ([^,]*) 第二个分组,匹配雇员姓名,为字段分隔符
    ([^,]*) 第三个分组,匹配雇员职位,为字段分隔符,
    上面的例子演示了如何使用分组
    1 代表第一个分组(雇员 ID),出现在第一个分组之后的逗号 3 代表第二个分组(雇员职位)

    注意: sed 最多能处理 9 个分组,分别用1 9 表示。

    交换第一列(雇员 ID)和第二列(雇员姓名)

    [root@ceph-node5 ~]# sed 's/^([^,]*),([^,]*),([^,]*)/2,1,3/' employee.txt
    John Doe,101,CEO
    Jason Smith,102,IT Manager
    Raj Reddy,103,Sysadmin
    Anand Ram,104,Developer
    Jane Miller,105,Sales Manager

    GNU Sed 专有的替换标志


    下面的标志,只有 GNU 版的 sed 才能使用。 它们可以用在替换命令中的 replacement-string里面.

    l 标志


    当在 replacement-string 中使用l 标志时,它会把紧跟在其后面的字符当做小写字符来处理。
    如你所知,下面的例子将把 John 换成 JOHNNY:

    [root@ceph-node5 ~]# sed 's/John/JOHNNY/' employee.txt  
    101,JOHNNY Doe,CEO
    102,Jason Smith,IT Manager
    103,Raj Reddy,Sysadmin
    104,Anand Ram,Developer
    105,Jane Miller,Sales Manager


    下面的例子,在 replacement-string 中的 H 前面放置了l 标志,它会把 JOHNNY 中的 H 换成小写的 h:

    [root@ceph-node5 ~]# sed -n 's/John/JOlHNNY/p' employee.txt
    101,JOhNNY Doe,CEO

    L 标志


    当在 replacement-string 中使用l 标志时,它会把后面所有的字符都当做小写字符来处理。
    下面的例子,在 replacement-string 中的 H 前面放置了L 标志,它会把 H 和它后面的所有字符都换成小写:

    [root@ceph-node5 ~]#  sed -n 's/John/JOLHNNY/p' employee.txt
    101,JOhnny Doe,CEO

    u 标志


    l 类似,只不过是把字符换成大写。 当在 replacement-string 中使用u 标志时,它会把紧跟在其后面的字符当做大写字符来处理。

    下面的例子中, replacement-string 里面的 h 前面有u 标志,所以 h 将被换成大写的 H:

    [root@ceph-node5 ~]#  sed -n 's/John/jouhnny/p' employee.txt
    101,joHnny Doe,CEO

    U 标志


    当在 replacement-string 中使用U 标志时,它会把后面所有的字符都当做大写字符来处理。
    下面的例子中, replacement-string 里面的 h 前面有U 标志,所以 h 及其以后的所有字符,都将被换成大写:

    [root@ceph-node5 ~]# sed -n 's/John/joUhnny/p' employee.txt
    101,joHNNY Doe,CEO

    E 标志


    E 标志需要和U L 一起使用,它将关闭U L 的功能。下面的例子将把字符串"Johnny Boy"的每个字符都以大写的形式打印出来,

    因为在 replacement-string 前面使用了U 标志:

    [root@ceph-node5 ~]# sed -n 's/John/UJohnny Boy/p' employee.txt
    101,JOHNNY BOY Doe,CEO


    下面将把 John 换成 JOHNNY Boy:

    [root@ceph-node5 ~]# sed -n 's/John/UJohnnyE Boy/p' employee.txt
    101,JOHNNY Boy Doe,CEO

    这个例子只把 Johnny 显示为大写,因为在 Johnny 后面使用了E 标志(关闭了U 的功能)替换标志的用法

    上面的例子仅仅展示了这些标志的用法和功能。 然而,如果你使用的是具体的字符串,那么这些选项未必有什么作用,因为你可以在需要的地方写出精确的字符串,

    而不需要使用这些标志进行转换。和分组配合使用时,这些选项就显得很有用了。

    前面例子中我们已经学会了如何使用分组调换第一列和第三列的位置。 使用上述标志,可以把整个分组转换为小写或大写。
    下面的例子,雇员 ID 都显示为大写,职位都显示为小写:

    [root@ceph-node5 ~]# sed 's/([^,]*),([^,]*),([^,]*)/U2E,1,L3/' employee.txt
    JOHN DOE,101,ceo
    JASON SMITH,102,it manager
    RAJ REDDY,103,sysadmin
    ANAND RAM,104,developer
    JANE MILLER,105,sales manager


    这个例子中:

    U2E 把第二个分组转换为大写,然后用E 关闭转换
    L3 把第三个分组转换为小写

    四、简单正则表达式


    从以上案例中我们不难发现,我们编写的脚本指令需要指定一个地址来决定操作范围,如果不指定则默认对文件的所有行操作。

    如:sed   'd'     test.txt              将删除 test.txt 的所有行,而'2d'
    则仅删除第二行。
    1.为 sed 指令确定操作地址:
    number              指定输入文件的唯一行号。
    first~step       以 first 开始,并指定操作步长为 step,如 1~2,指定第一行,
    第三行,第五行... 为操作地址。
    [jacob@localhost ~] #sed   -n   '1~2p'   test.txt              打印文件的奇数行。
    DEVICE=eth0
    BOOTPROTO=static
    NETMASK=255.255.255.0
    2~5,则可以指定第二行开始,每 5 行匹配一次操作地址。
    $                     匹配文件的最后一行。
    /regexp/         //中间包含的是正则表达式,通过正则表达式匹配操作地址。
    注: //空的正则表达式,匹配最近一次正则表达式的匹配地址,会在后面使用看出效果。
    cregexpc         匹配扩展正则表达式,c 字符可以使用任意字符替代。
    addr1,addr2       匹配从操作地址 1 到操作地址 2 的所有行。
    [jacob@localhost ~] #sed   '2,8d'   test.txt              #删除 28 中间的所有行。
    DEVICE=eth0
    addr1,+N              匹配地址 1 以及后面的 N 行内容。



    2.正则表达式概述(对你要找内容的一种描述)

    char                     字符本身就匹配字符本身,如/abc/就是定位包含 abc的行。
    *                     匹配前面表达式出现了 0 或若干次,如/a*/可以帮你找到a,aa,aaa,... ...等等。
    +                     类似于*,但匹配前面表达式的 1 次或多次,这属于扩展正则表达式。
    ?                     类似于*,但匹配前面表达式的 0 次或 1 次,这属于扩展正则表达式。
    {i}                类似于*,但匹配前面表达式的 i 次(i 为整数),如: a{3}可以帮你找到 aaa。
    {i,j}            匹配前面表达式的 i 到 j 次,如 a{1,2}可以帮你找到 a 或aa 或 aaa。
    {i,}              匹配前面表达式至少 i 次。
    ( )                将( )内的模式存储在保留空间。最多可以存储 9 个独立子模式,
                         可通过转义19 重复保留空间的内容至此点。
    
                         转义19 重复保留空间的内容至此点。
    例:test.txt 的内容为 ssttss
    grep '(ss)tt1' test.txt                     1 表示将 ss 重复在 tt 后面
    该 grep 命令等同于grep   ssttss   test.txt       在 test.txt 文件中找 ssttss
    .                     (点)匹配任意字符。
    ^                     匹配行的开始,如^test       将匹配所有以 test 开始的行。
    $                     匹配行的结尾,如 test$       将匹配所有以 test 结尾的行。
    []                   匹配括号中的任意单个字符,如 a[nt]       将匹配 an 或at。
    [^]                 匹配不包含在[]中的字符,如[^a-z]       将匹配除 a-z以外的字符。
    
                       匹配换行符。
    char              转义特殊字符,如*,就是匹配字面意义上的星号。

    行的开头 ( ^ )

    ^ 匹配每一行的开头


    显示以 103 开头的行:

    [root@ceph-node5 ~]# sed -n '/^103/ p' employee.txt
    103,Raj Reddy,Sysadmin


    只有^出现在正则表达式开头时,它才匹配行的开头。 所以, ^N 匹配所有以 N 开头的行。


    行的结尾
    ( $ )


    $匹配行的结尾。


    显示以字符
    r 结尾的行:

    [root@ceph-node5 ~]# sed -n '/r$/ p' employee.txt
    102,Jason Smith,IT Manager
    104,Anand Ram,Developer
    105,Jane Miller,Sales Manager

     单个字符 ( . )

    元字符点 . 匹配除换行符之外的任意单个字符

    .匹配单个字符
    .. 匹配两个字符
    ...匹配三个字符
    ....以此类推

    匹配 0 次或多次 ( * )


    星号*匹配 0 个或多个其前面的字符。如: 1* 匹配 0 个或多个 1
    先建立下面文件:

    [root@ceph-node5 ~]# cat log.txt
    log: input.txt
    log:
    log: testing resumed
    log:
    log:output created


    假设你想查看那些包含 log 并且后面有信息的行, log 和信息之间可能有 0 个或多个空格,同时不想查看那些 log:后面没有任何信息的行。
    显示包含
    log:并且 log 后面有信息的行, log 和信息之间可能有空格:

    [root@ceph-node5 ~]#  sed -n '/log: *./p' log.txt
    log: input.txt
    log: testing resumed
    log:output created

    注意: 上面例子中,后面的点.是必需的,如果没有, sed 只会打印所有包含 log 的行。


    匹配一次或多次
    ( + )


    “+”匹配一次或多次它前面的字符,例如 空格+ “ +”匹配至少一个或多个空格。
    仍旧使用
    log.txt 这个文件来作示例。
    显示包含
    log:并且 log:后面有一个或多个空格的所有行:

    [root@ceph-node5 ~]# sed -n '/log: +/ p' log.txt
    log: input.txt
    log: testing resumed



    注意:这个例子既没有匹配只包含 log:的行,也没有匹配 log:output craeted 这一行,因为 log:后面没有空格。


    零次或一次匹配
    ( ? )


    ?匹配 0 次或一次它前面的字符。 如:

    [root@ceph-node5 ~]# sed -n '/log: +/ p' log.txt
    log: input.txt
    log: testing resumed
    [root@ceph-node5 ~]# sed -n '/log: ?/ p' log.txt
    log: input.txt
    log:
    log: testing resumed
    log:
    log:output created

     

    转义字符 ( )

    如果要在正则表达式中搜寻特殊字符(:*,.),必需使用来转义它们。

    sed -n '/127.0.0.1/ p' /etc/hosts
    127.0.0.1 localhost

    字符集 ( [0-9] )

    字符集匹配方括号中出现的任意一个字符。
    匹配包含
    23 或者 4 的行:

    [root@ceph-node5 ~]# sed -n '/[234]/ p' employee.txt
    102,Jason Smith,IT Manager
    103,Raj Reddy,Sysadmin
    104,Anand Ram,Developer


    在方括号中,可以使用连接符-指定一个字符范围。如[0123456789]可以用[0-9]表示,字母可以用[a-z],[A-Z]表示,等等。
    匹配包含
    23 或者 4 的行(另一种方式):

    [root@ceph-node5 ~]# sed -n '/[2-4]/ p' employee.txt
    102,Jason Smith,IT Manager
    103,Raj Reddy,Sysadmin
    104,Anand Ram,Developer

     或操作符 ( | )


    管道符号|用来匹配两边任意一个子表达式。 子表达式 1|子表达式 2 匹配子表达式 1 或者子表达式 2
    打印包含 101 或者包含 102 的行:

    [root@ceph-node5 ~]# sed -n '/101|102/ p' employee.txt
    101,John Doe,CEO
    102,Jason Smith,IT Manager


    需要注意, | 需要用转义。
    打印包含数字 2~3 或者包含 105 的行:

    [root@ceph-node5 ~]# sed -n '/[2-3]|105/ p' employee.txt
    102,Jason Smith,IT Manager
    103,Raj Reddy,Sysadmin
    105,Jane Miller,Sales Manager

    精确匹配 m ( {m} )




    正则表达式后面跟上{m}标明精确匹配该正则 m 次。
    请先建立如下文件:

    [root@ceph-node5 ~]# vim numbers.txt
    1
    12
    123
    1234
    12345
    123456


    打印包含任意数字的行(这个命令将打印所有行):

    [root@ceph-node5 ~]# sed -n '/[0-9]/ p' numbers.txt
    1
    12
    123
    1234
    12345
    123456


    打印包含 5 个数字的行:

    [root@ceph-node5 ~]# sed -n '/^[0-9]{5}$/ p' numbers.txt
    12345


    注意这里一定要有开头和结尾符号,即^$,并且{}都要用转义

     

    匹配 m n ( {m,n} ):


    正则表达式后面跟上{m,n}表明精确匹配该正则至少 m,最多 n 次。 m n 不能是负数,并且要小于 255.
    打印由 3 5 个数字组成的行:

    [root@ceph-node5 ~]# sed -n '/^[0-9]{3,5}$/ p' numbers.txt
    123
    1234
    12345



    正则表达式后面跟上{m,}表明精确匹配该正则至少 m,最多不限。 (同样,如果是{,n}表明最多匹配 n 次,最少一次)

    字符边界 (  )

     用来匹配单词开头(xx)或结尾(xx)的任意字符,因此the 将匹配 the,但不匹配 they。the 将匹配 the they.
    请先建立如下文件:

    [root@ceph-node5 ~]# vim words.txt
    word matching using: the
    word matching using: thethe
    word matching using: they

    匹配包含 the 作为整个单词的行:

    [root@ceph-node5 ~]# sed -n '/the/ p' words.txt
    word matching using: the


    注意:如果没有后面那个,将匹配所有行。
    匹配所有以 the 开头的单词:

    [root@ceph-node5 ~]# sed -n '/the/ p' words.txt
    word matching using: the
    word matching using: thethe
    word matching using: they

    回溯引用 ( )

    使用回溯引用,可以给正则表达式分组,以便在后面引用它们。
    只匹配重复 the 两次的行:

    [root@ceph-node5 ~]# sed -n '/(the)1/ p' words.txt
    word matching using: thethe


    同理, ”([0-9])1” 匹配连续两个相同的数字,如 11,22,33 …..

    sed 替换中使用正则表达式

    下面是一些使用正则表达式进行替换的例子。
    employee.txt 中每行最后两个字符替换为”,Not Defined”:

    [root@ceph-node5 ~]# sed -n 's/..$/,Not Defined/ p' employee.txt
    101,John Doe,C,Not Defined
    102,Jason Smith,IT Manag,Not Defined
    103,Raj Reddy,Sysadm,Not Defined
    104,Anand Ram,Develop,Not Defined
    105,Jane Miller,Sales Manag,Not Defined


    删除以 Manager 开头的行的后面的所有内容:

    [root@ceph-node5 ~]# sed 's/^Manager.*//' employee.txt
    101,John Doe,CEO
    102,Jason Smith,IT Manager
    103,Raj Reddy,Sysadmin
    104,Anand Ram,Developer
    105,Jane Miller,Sales Manager


    这个示例好像有点问题啊,我觉得应该是 sed ‘s/^Manager.*/Manager/’
    删除所有以#开头的行:

    [root@ceph-node5 ~]#  sed -e 's/#.*// ; /^$/ d' employee.txt 
    101,John Doe,CEO
    102,Jason Smith,IT Manager
    103,Raj Reddy,Sysadmin
    104,Anand Ram,Developer
    105,Jane Miller,Sales Manager


    我觉得用 sed ‘/^#/ d’ 更好
    建立下面的 test.html 文件:

    [root@ceph-node5 ~]# vim test.html
    <html><body><h1>Hello World!</h1></body></html>


    清除 test.html 文件中的所有 HTML 标签:

    [root@ceph-node5 ~]# sed 's/<[^>]*>//g' test.html
    Hello World!


    删除所有注释行和空行:

    [root@ceph-node5 ~]# sed -e 's/#.*// ; /^$/ d' /etc/profile


    只删除注释行,保留空行:

    [root@ceph-node5 ~]# sed '/#.*/ d' /etc/profile 

    使用 sed 可以把 DOS 的换行符(CR/LF)替换为 Unix 格式。 当把 DOS 格式的文件拷到 Unix 上,你会发现,每行结尾都有 .
    使用 sed DOS 格式的文件转换为 Unix 格式:

    sed ‘s/.$//’ filename 

     

    五、执行 sed

    单行内执行多个 sed 命令

     

    1. 使用多命令选项 –e
    多命令选项-e 使用方法如下:

    sed –e 'command1' –e 'command2' –e 'command3'


    /etc/passwd 文件中,搜索 rootnobody mail:

    [root@ceph-node5 ~]# sed -n -e '/^root/ p' -e '/^nobody/ p' -e '/^mail/ p' /etc/passwd
    root:x:0:0:root:/root:/bin/bash
    mail:x:8:12:mail:/var/spool/mail:/sbin/nologin
    nobody:x:99:99:Nobody:/:/sbin/nologin




    2. 使用 折行执行多个命令


    在执行很长的命令,比如使用
    -e 选项执行多个 sed 命令时,可以使用来把命令折到多行

    [root@ceph-node5 ~]# sed -n -e '/^root/ p' 
    > -e '/^nobody/ p' 
    > -e '/^mail/ p' 
    > /etc/passwd
    root:x:0:0:root:/root:/bin/bash
    mail:x:8:12:mail:/var/spool/mail:/sbin/nologin
    nobody:x:99:99:Nobody:/:/sbin/nologin


    3. 使用{ }把多个命令组合
    如果要执行很多
    sed 命令,可以使用{ }把他们组合起来执行,如:

    [root@ceph-node5 ~]# sed -n '{
    > /^root/ p
    > /^nobody/ p
    > /^mail/ p
    > }' /etc/passwd
    root:x:0:0:root:/root:/bin/bash
    mail:x:8:12:mail:/var/spool/mail:/sbin/nologin
    nobody:x:99:99:Nobody:/:/sbin/nologin

    sed 脚本文件

    如果用重复使用一组 sed 命令,那么可以建立 sed 脚本文件,里面包含所有要执行的 sed 命令,然后用-f 选项来使用。
    首先建立下面文件,里面包含了所有要执行的
    sed 命令。 前面已经解释过各个命令的含义,
    现在你应该知道所有命令的意思了。

    [root@ceph-node5 ~]# vim mycommands.sed
    s/([^,]*),([^,]*),(.*).*/2,1, 3/g
    s/^.*/<&>/
    s/Developer/IT Manager/
    s/Manager/Director/

    现在执行脚本里面的命令:

    [root@ceph-node5 ~]# sed -f mycommands.sed employee.txt
    <John Doe,101, CEO>
    <Jason Smith,102, IT Director>
    <Raj Reddy,103, Sysadmin>
    <Anand Ram,104, IT Director>
    <Jane Miller,105, Sales Director>

    sed 注释

    sed 注释以#开头。因为 sed 是比较晦涩难懂的语言,所以你现在写下的 sed 命令,时间一长,再看时就不那么容易理解了。

    因此,建议把写脚本时的初衷作为注释,写到脚本里面。 如下所示:

    [root@ceph-node5 ~]#  vim mycommands.sed
    #交换第一列和第二列 s/([^,]*),([^,]*),(.*).*/2,1, 3/g #把整行内容放入<>中 s/^.*/<&>/ #把 Developer 替换为 IT Manager s/Developer/IT Manager/ #把 Manager 替换为 Director s/Manager/Director/


    注意: 如果 sed 脚本第一行开始的两个字符是#n 的话, sed 会自动使用-n 选项(即不自动打印模式空间的内容)

    sed 当做命令解释器使用


    一如你可以把命令放进一个 shell 脚本中,然后调用脚本名称来执行它们一样,你也可以把sed 用作命令解释器。

    要实现这个功能,需要在 sed 脚本最开始加入"#!/bin/sed –f",如下所示:

    [root@ceph-node5 ~]#  vim myscript.sed
    #!/bin/sed -f
    #交换第一列和第二列
    s/([^,]*),([^,]*),(.*).*/2,1, 3/g
    #把整行内容放入<>中
    s/^.*/<&>/
    #把 Developer 替换为 IT Manager
    s/Developer/IT Manager/
    #把 Manager 替换为 Director
    s/Manager/Director/

    现在,给这个脚本加上可执行权限,然后直接在命令行调用它:

    [root@ceph-node5 ~]#  chmod u+x myscript.sed
    [root@ceph-node5 ~]#  ./myscript.sed employee.txt
    <John Doe,101, CEO>
    <Jason Smith,102, IT Director>
    <Raj Reddy,103, Sysadmin>
    <Anand Ram,104, IT Director>
    <Jane Miller,105, Sales Director>


    也可以指定-n 选项来屏蔽默认输出:

    [root@ceph-node5 ~]# vim testscript.sed
    #!/bin/sed -nf
    /root/ p
    /nobody/ p
    /mail/ p



    然后加上可执行权限,执行:

    [root@ceph-node5 ~]# chmod u+x testscript.sed
    [root@ceph-node5 ~]# ./testscript.sed /etc/passwd
    root:x:0:0:root:/root:/bin/bash
    mail:x:8:12:mail:/var/spool/mail:/sbin/nologin
    operator:x:11:0:operator:/root:/sbin/nologin
    nobody:x:99:99:Nobody:/:/sbin/nologin


    处于测试目的,把 testscript.sed 里面的-n 去掉,然后再执行一次, 观察它是如何运行的。
    重要提示
    :使用-n 时,必须是-nf.如果你写成-fn,执行脚本时就会获得下面的错误:

    [root@ceph-node5 ~]#  ./testscript.sed /etc/passwd
    /bin/sed: couldn't open file n: No such file or directory

    直接修改输入文件

    目前为止,你知道 sed 默认不会修改输入文件, 它只会把输出打印到标准输出上。 当想保存结果时,把输出重定向到文件中(或使用 w 命令)
    执行下面的例子之前,先备份一下
    employee.txt 文件:

    [root@ceph-node5 ~]# cp employee.txt{,.orig}


    为了修改输入文件,通常方法是把输出重定向到一个临时文件,然后重命名该临时文件:

    [root@ceph-node5 ~]# sed 's/John/Johnny/' employee.txt > new-employee.txt  
    [root@ceph-node5 ~]# mv new-employee.txt employee.txt


    相比这种传统方法,可以在 sed 命令中使用-i 选项,使 sed 可以直接修改输入文件:
    在原始文件 employee.txt 中,把 John 替换为 Johnny:

    [root@ceph-node5 ~]# sed -i 's/John/Johnny/' employee.txt
    [root@ceph-node5 ~]# cat employee.txt
    101,Johnnyny Doe,CEO
    102,Jason Smith,IT Manager
    103,Raj Reddy,Sysadmin
    104,Anand Ram,Developer
    105,Jane Miller,Sales Manager


    再次提醒: -i 会修改输入文件。 或许这正是你想要的,但是务必小心。 一个保护性的措施是,在-i 后面加上备份扩展,这一 sed 就会在修改原始文件之前,备份一份。
    在原始文件
    employee.txt 中,把 John 替换为 Johnny,但在替换前备份 employee.txt:

    [root@ceph-node5 ~]# sed -ibak 's/John/Johnny/' employee.txt


    备份的文件如下:

    [root@ceph-node5 ~]# cat employee.txtbak
    101,Johnnyny Doe,CEO
    102,Jason Smith,IT Manager
    103,Raj Reddy,Sysadmin
    104,Anand Ram,Developer
    105,Jane Miller,Sales Manager


    修改后的原始文件为:

    [root@ceph-node5 ~]# cat employee.txt
    101,Johnnynyny Doe,CEO
    102,Jason Smith,IT Manager
    103,Raj Reddy,Sysadmin
    104,Anand Ram,Developer
    105,Jane Miller,Sales Manager


    除了使用-i,也可以使用完整样式 –in-place.下面两个命令是等价的:

    [root@ceph-node5 ~]# sed -ibak 's/John/Johnny/' employee.txt 
    [root@ceph-node5 ~]# sed -in-place=bak 's/John/Johnny/' employee.txt



    最后,为了继续下面的例子,把原来的 employee.txt 还原回去:

    cp employee.txt.orig employee.txt



    六、sed 附加命令

    追加命令(命令 a)

     



    使用命令 a 可以在指定位置的后面插入新行。
    语法:

    sed '[address] a the-line-to-append' input-file


    在第 2 行后面追加一行(原文这里可能有问题,没有写明行号):

    [root@ceph-node5 ~]# sed '2 a 203,Jack Johnson,Engineer' employee.txt
    101,John Doe,CEO
    102,Jason Smith,IT Manager
    203,Jack Johnson,Engineer
    103,Raj Reddy,Sysadmin
    104,Anand Ram,Developer
    105,Jane Miller,Sales Manager


    employee.txt 文件结尾追加一行:

    [root@ceph-node5 ~]# sed '$ a 106,Jack Johnson,Engineer' employee.txt
    101,John Doe,CEO
    102,Jason Smith,IT Manager
    103,Raj Reddy,Sysadmin
    104,Anand Ram,Developer
    105,Jane Miller,Sales Manager
    106,Jack Johnson,Engineer



    在匹配
    Jason 的行的后面追加两行:

    [root@ceph-node5 ~]# sed '/Jason/a
    > 203,Jack Johnson,Engineer
    > 204,Mark Smith,Sales Engineer' employee.txt
    101,John Doe,CEO
    102,Jason Smith,IT Manager
    203,Jack Johnson,Engineer
    204,Mark Smith,Sales Engineer
    103,Raj Reddy,Sysadmin
    104,Anand Ram,Developer
    105,Jane Miller,Sales Manager


    追加多行之间可以用 来换行,这样就不用折行了,上面的命令等价于:
    sed '/Jason/a 203,Jack Johnson,Engineer 204,Mark Smith,Sales Engineer' employee.txt

    插入命令(命令 i)


    插入命令 insert 命令和追加命令类似,只不过是在指定位置之前插入行。
    语法:

     sed '[address] i the-line-to-insert' input-file


    employee.txt 的第 2 行之前插入一行:

    [root@ceph-node5 ~]# sed '2 i 203,Jack Johnson,Engineer' employee.txt
    101,John Doe,CEO
    203,Jack Johnson,Engineer
    102,Jason Smith,IT Manager
    103,Raj Reddy,Sysadmin
    104,Anand Ram,Developer
    105,Jane Miller,Sales Manager


    employee.txt 最后一行之前,插入一行:

    [root@ceph-node5 ~]# sed '$ i 108,Jack Johnson,Engineer' employee.txt
    101,John Doe,CEO
    102,Jason Smith,IT Manager
    103,Raj Reddy,Sysadmin
    104,Anand Ram,Developer
    108,Jack Johnson,Engineer
    105,Jane Miller,Sales Manager


    sed
    也可以插入多行。
    在匹配
    Jason 的行的前面插入两行:

    sed '/Jason/i
    203,Jack Johnson,Engineer
    204,Mark Smith,Sales Engineer' employee.txt
    101,John Doe,CEO
    203,Jack Johnson,Engineer
    204,Mark Smith,Sales Engineer
    102,Jason Smith,IT Manager
    103,Raj Reddy,Sysadmin
    104,Anand Ram,Developer
    105,Jane Miller,Sales Manager


    修改命令(命令 c)
    修改命令 change 可以用新行取代旧行。
    语法:

    sed '[address] c the-line-to-insert' input-file



    用新数据取代第 2 :

    [root@ceph-node5 ~]# sed '2 c 202,Jack,Johnson,Engineer' employee.txt
    101,John Doe,CEO
    202,Jack,Johnson,Engineer
    103,Raj Reddy,Sysadmin
    104,Anand Ram,Developer
    105,Jane Miller,Sales Manager


    这里命令 c 等价于替换: $ sed '2s/.*/202,Jack,Johnson,Engineer/' employee.txt
    sed 也可以用多行来取代一行。
    用两行新数据取代匹配
    Raj 的行:

    [root@ceph-node5 ~]# sed '/Raj/c
    > 203,Jack Johnson,Engineer
    > 204,Mark Smith,Sales Engineer' employee.txt
    101,John Doe,CEO
    102,Jason Smith,IT Manager
    203,Jack Johnson,Engineer
    204,Mark Smith,Sales Engineer
    104,Anand Ram,Developer
    105,Jane Miller,Sales Manager


    命令 a、 i 和 c 组合使用


    命令 ai c 可以组合使用。下面的例子将完成三个操作:

    a 在"Jason"后面追加"Jack Johnson"
    i 在"Jason"前面插入"Mark Smith"
    c 用"Joe Mason"替代"Jason"


    如下:

    [root@ceph-node5 ~]# sed '/Jason/ {a
    > 204,Jack Johnson,Engineer
    > i
    > 202,Mark Smith,Sales Engineer
    > c
    > 203,Joe Mason,Sysadmin
    > }' employee.txt
    101,John Doe,CEO
    202,Mark Smith,Sales Engineer
    203,Joe Mason,Sysadmin
    204,Jack Johnson,Engineer
    103,Raj Reddy,Sysadmin
    104,Anand Ram,Developer
    105,Jane Miller,Sales Manager

    打印不可见字符(命令 l)


    命令 l 可以打印不可见的字符,比如制表符 ,行尾标志$等。
    请先建立下面文件以便用于后续测试,请确保字段之间使用制表符
    (Tab )分开:

    [root@ceph-node5 ~]# vim  tabfile.txt
    fname First Name
    lname Last Name
    mname Middle Name

     

    使用命令 l,把制表符显示为 ,行尾标志显示为 EOL:

    [root@ceph-node5 ~]# sed -n 'l' tabfile.txt
    fname First Name$
    lname Last Name$
    mname Middle Name$


    如果在 l 后面指定了数字,那么会在第 n 个字符处使用一个不可见自动折行,效果如下:

    [root@ceph-node5 ~]# sed -n 'l 20' employee.txt
    101,John Doe,CEO$
    102,Jason Smith,IT 
    Manager$
    103,Raj Reddy,Sysad
    min$
    104,Anand Ram,Devel
    oper$
    105,Jane Miller,Sal
    es Manager$


    这个功能只有 GNU sed 才有。

    打印行号(命令=)


    命令=会在每一行后面显示该行的行号。

    打印所有行号:

    [root@ceph-node5 ~]#  sed '=' employee.txt
    1
    101,John Doe,CEO
    2
    102,Jason Smith,IT Manager
    3
    103,Raj Reddy,Sysadmin
    4
    104,Anand Ram,Developer
    5
    105,Jane Miller,Sales Manager


    提示: 把命令=和命令 N 配合使用,可以把行号和内容显示在同一行上(下文解释)
    只打印
    1,2,3 行的行号:

    [root@ceph-node5 ~]# sed '1,3 =' employee.txt
    1
    101,John Doe,CEO
    2
    102,Jason Smith,IT Manager
    3
    103,Raj Reddy,Sysadmin
    104,Anand Ram,Developer
    105,Jane Miller,Sales Manager


    打印包含关键字”Jane”的行的行号,同时打印输入文件中的内容:

    [root@ceph-node5 ~]#  sed '/Jane/ =' employee.txt
    101,John Doe,CEO
    102,Jason Smith,IT Manager
    103,Raj Reddy,Sysadmin
    104,Anand Ram,Developer
    5
    105,Jane Miller,Sales Manager


    如果你想只显示行号但不显示行的内容,那么使用-n 选项来配合命令=:

    [root@ceph-node5 ~]# sed -n '/Raj/ =' employee.txt
    3


    打印文件的总行数:

    [root@ceph-node5 ~]# sed -n '$ =' employee.txt
    5

    转换字符(命令 y)


    命令 y 根据对应位置转换字符。好处之一便是把大写字符转换为小写,反之亦然。
    下面例子中,将把
    a 换为 Ab 换为 Bc 换为 C,以此类推:

    [root@ceph-node5 ~]#  sed 'y/abcde/ABCDE/' employee.txt
    101,John DoE,CEO
    102,JAson Smith,IT MAnAgEr
    103,RAj REDDy,SysADmin
    104,AnAnD RAm,DEvElopEr
    105,JAnE MillEr,SAlEs MAnAgEr


    把所有小写字符转换为大写字符:

    [root@ceph-node5 ~]# sed 'y/abcdefghijklmnopqrstuvwxyz/ABCDEFGHIJKLMNOPQRSTUVWXYZ/' employee.txt
    101,JOHN DOE,CEO
    102,JASON SMITH,IT MANAGER
    103,RAJ REDDY,SYSADMIN
    104,ANAND RAM,DEVELOPER
    105,JANE MILLER,SALES MANAGER

    操作多个文件

     


    之前的例子都只操作了单个文件, sed 也可以同时处理多个文件。
    /etc/passwd 中搜索 root 并打印出来:

    [root@ceph-node5 ~]# sed -n '/root/ p' /etc/passwd
    root:x:0:0:root:/root:/bin/bash
    operator:x:11:0:operator:/root:/sbin/nologin


    /etc/group 中搜索 root 并打印出来:

    [root@ceph-node5 ~]# sed -n '/root/ p' /etc/group
    root:x:0:


    同时在/etc/passwd /etc/group 中搜索 root:

    [root@ceph-node5 ~]# sed -n '/root/ p' /etc/passwd /etc/group
    root:x:0:0:root:/root:/bin/bash
    operator:x:11:0:operator:/root:/sbin/nologin
    root:x:0:


     退出 sed(命令 q)


    命令 q 终止正在执行的命令并退出 sed
    之前提到,正常的
    sed 执行流程是:读取数据、执行命令、打印结果、重复循环。
    sed 遇到 q 命令,便立刻退出,当前循环中的后续命令不会被执行,也不会继续循环。
    打印第
    1 行后退出:

    [root@ceph-node5 ~]# sed 'q' employee.txt
    101,John Doe,CEO


    打印第 5 行后退出,即只打印前 5 行:

    [root@ceph-node5 ~]# sed '5 q' employee.txt
    101,John Doe,CEO
    102,Jason Smith,IT Manager
    103,Raj Reddy,Sysadmin
    104,Anand Ram,Developer
    105,Jane Miller,Sales Manager



    打印所有行,直到遇到包含关键字 Manager 的行:

    [root@ceph-node5 ~]# sed '/Manager/ q' employee.txt
    101,John Doe,CEO
    102,Jason Smith,IT Manager


    注意: q 命令不能指定地址范围(或模式范围),只能用于单个地址(或单个模式)

     

    从文件读取数据(命令 r)

    在处理输入文件是,命令 r 会从另外一个文件读取内容,并在指定的位置打印出来。

    下面的例子将读取 log.txt 的内容,并在打印 employee.txt 最后一行之后,把读取的内容打印出来。事实上它把 employee.txt log.txt 合并然后打印出来。

    [root@ceph-node5 ~]# sed '$ r log.txt' employee.txt
    101,John Doe,CEO
    102,Jason Smith,IT Manager
    103,Raj Reddy,Sysadmin
    104,Anand Ram,Developer
    105,Jane Miller,Sales Manager
    log: input.txt
    log:
    log: testing resumed
    log:
    log:output created


    也可以给命令 r 指定一个模式。下面的例子将读取 log.txt 的内容,并且在匹配’Raj’的行后面打印出来:

    [root@ceph-node5 ~]# sed '/Raj/ r log.txt' employee.txt
    101,John Doe,CEO
    102,Jason Smith,IT Manager
    103,Raj Reddy,Sysadmin
    log: input.txt
    log:
    log: testing resumed
    log:
    log:output created
    104,Anand Ram,Developer
    105,Jane Miller,Sales Manager

    sed 模拟 Unix 命令(cat,grep,read)

     之前的例子的完成的功能都很像标准的 Unix 命令。使用 sed 可以模拟很多 Unix 命令。完成它们,以便更好地理解 sed 的工作过程

    模拟 cat 命令

     

    cat employee.txt


    下面的每个 sed 命令的输出都和上面的 cat 命令的输出一样:

    sed 's/JUNK/&/p' employee.txt 
    sed -n 'p' employee.txt
    sed 'n' employee.txt
    sed 'N' employee.txt

     

    模拟 grep 命令



     

    grep Jane employee.txt


    下面的每个 sed 命令的输出都和上面的 grep 命令的输出一样:

    sed -n 's/Jane/&/ p' employee.txt 
    sed -n '/Jane/ p' employee.txt



    grep –v (打印不匹配的行):

    sed -n '/Jane/ !p' employee.txt 


    注意: 这里不能使用 sed –n 's/Jane/&/ !p' 了。



    模拟 head 命令

     

    head -10 /etc/passwd


    下面的每个 sed 命令的输出都和上面的 head 命令的输出一样:

    sed '11,$ d' /etc/passwd 
    sed -n '1,10 p' /etc/passwd
    sed '10 q' /etc/passwd

     

    sed 命令选项



    -n 选项


    之前已经讨论过多次,并且在很多例子中也使用到了-n 选项。 该选项屏蔽 sed 的默认输出。
    也可以使用—quiet,或者—silent 来代替-n,它们的作用是相同的。
    下面所有命令都是等价的:

    sed -n 'p' employee.txt 
    sed --quiet 'p' employee.txt

     

    -f 选项

     


    可以把多个 sed 命令保存在 sed 脚本文件中,然后使用-f 选项来调用,这点之前已经演示过
    了。你也可以使用--
    file 来代替-f
    下面的命令是等价的:

    sed -n -f test-script.sed /etc/passwd  
    sed -n -file=test-script.sed /etc/passwd

     

    -e 选项


    该选项执行来自命令行的一个
    sed 命令,可以使用多个-e 来执行多个命令。也可以使用-expression 来代替。
    下面所有命令都是等价的
    :

    sed -n -e '/root/ p' /etc/passwd  
    sed -n --expression '/root/ p' /etc/passwd



    -i 选项

    下面所有命令都是等价的:
    我们已经提到, sed 不会修改输入文件,只会把内容打印到标准输出,或则使用 w 命令把内容写到不同的文件中。 我们也展示了如何使用-i 选项来直接修改输入文件。
    在原始文件
    employee.txt 中,用 Johnny 替换 John:

    sed -i 's/John/Johnny/' employee.txt 


    执行和上面相同的命令,但在修改前备份原始文件:

    sed -ibak 's/John/Johnny/' employee.txt 


    也可以使用--in-place 来代替-i:
    下面的命令是等价的:

     sed -ibak 's/John/Johnny/' employee.txt
    sed --in-place=bak 's/John/Johnny/' employee.txt



    -c 选项

    该选项应和-i 配合使用。使用-i 时,通常在命令执行完成后, sed 使用临时文件来保持更改后的内容,然后把该临时文件重命名为输入文件。

    但这样会改变文件的所有者(奇怪的是我的测试结果是它不会改变文件所有者),配合 c 选项,可以保持文件所有者不变。也可以使用--copy 来代替。
    下面的命令是等价的
    :

    sed -ibak -c 's/John/Johnny/' employee.txt 
    sed --in-place=bak --copy 's/John/Johnny/' employee.txt



    -l 选项


    指定行的长度,需要和
    l 命令配合使用(注意选项 l 和命令 l,不要弄混了,上面提到的命令 i
    和选项 i 也不要搞错)使用-l 选项即指定行的长度。也可以使用--line-length 来代替.
    下面的命令是等价的:

    sed -n -l 20 'l' employee.txt



    请注意,下面的例子不使用-l(原文是-n,应该是弄错)选项,同样可以获取相同的输出:

    sed -n 'l 20' employee.txt --posix option  

    打印模式空间(命令 n)


    命令 n 打印当前模式空间的内容,然后从输入文件中读取下一行。 如果在命令执行过程中遇到 n,那么它会改变正常的执行流程。
    打印每一行在模式空间中的内容:

    sed n employee.txt


    如果使用了-n 选项,将没有任何输出:

     sed -n n employee.txt


    再次提醒:不要把选项-n 和命令 n 弄混了)
    前面提到, sed 正常流程是读取数据、执行命令、打印输出、重复循环。
    命令
    n 可以改变这个流程,它打印当前模式空间的内容,然后清除模式空间,读取下一行进
    来,然后继续执行后面的命令。
    假定命令
    n 前后各有两个其他命令,如下:

    sed-command-1
    sed-command-2
    n
    sed-command-3
    sed-command-4


    这种情况下, sed-command-1 sed-command-2 会在当前模式空间中执行,然后遇到 n,它打印当前模式空间的内容,并清空模式空间,读取下一行,

    然后把 sed-command-3 sed-command-4 应用于新的模式空间的内容。
    提示:上面的例子中可以看到,命令
    n 本身没有多大用处,然而当它和保持空间的命令配合使用时,就很强大了,后面会详细解释。

    七、保持空间和模式空间命令

     

    SED之所以能以行为单位的编辑或修改文本,其原因在于它使用了两个空间:一个是活动的模式空间(pattern space),另一个是起辅助作用的暂存缓冲区(holdingspace)这2个空间的使用。

    模式空间:如你所知,模式空间用于 sed 执行的正常流程中。 该空间 sed 内置的一个缓冲区,用来存放、修改从输入文件读取的内容。
    
     保持空间: 保持空间是另外一个缓冲区,用来存放临时数据。 Sed 可以在保持空间和模式空间交换数据,但是不能在保持空间上执行普通的 sed 命令。 我们已经讨论
    过,每次循环读取数据过程中,模式空间的内容都会被清空,然而保持空间的内容则保持不变,不会在循环中被删除。

    sed编辑器逐行处理文件,并将输出结果打印到屏幕上。sed命令将当前处理的行读入模式空间(pattern space)进行处理,sed在该行上执行完所有命令后就将处理好的行打印到屏幕上(除非之前的命令删除了该行),sed处理完一行就将其从模式空间中删除,然后将下一行读入模式空间,进行处理、显示。处理完文件的最后一行,sed便结束运行。sed在临时缓冲区(模式空间)对文件进行处理,所以不会修改原文件,除非显示指明-i选项。

    模式空间:可以想成工程里面的流水线,数据之间在它上面进行处理,用于处理文本行。

    保持空间:可以想象成仓库,我们在进行数据处理的时候,作为数据的暂存区域,用于保留文本行,是保存已经处理过的输入行,默认有一个空行。

    与模式空间和暂存空间(hold space)相关的命令:

    n 输出模式空间行,读取下一行替换当前模式空间的行,执行下一条处理命令而非第一条命令。
    
    N 读入下一行,追加到模式空间行后面,此时模式空间有两行。
    
    h 把模式空间的内容复制到保留空间,覆盖模式
    
    H 把模式空间的内容追加到保留空间,追加模式
    
    g 把保留空间的内容复制到模式空间,覆盖模式
    
    G 把保留空间的内容追加到模式空间,追加模式
    
    x 将暂存空间的内容于模式空间里的当前行互换。
    
    ! 对所选行以外的所有行应用命令。

    注意:暂存空间里默认存储一个空行。



    请先建立如下文件,以用于保持空间的示例:

    [root@ceph-node5 ~]# vim empnametitle.txt
    John
    Doe
    CEO
    Jason Smith
    IT Manager
    Raj Reddy
    Sysadmin
    Anand Ram
    Developer
    Jane Miller
    Sales Manager


    可以看到,在这个文件中,每个雇员的名称和职位位于连续的两行内。

    用保持空间替换模式空间(命令 x)

    命令 x(Exchange)交换模式空间和保持空间的内容。 该命令本身没有多大用处,但如果和其他命令配合使用,则非常强大了。
    假定目前模式空间内容为"
    line 1",保持空间内容为"line 2"。那么执行命令 x 后,模式空间的内容变为”line 2”,保持空间的内容变为"line 1"

     

    把模式空间的内容复制到保持空间(命令 h)

     


    命令 h(hold)把模式空间的内容复制到保持空间,和命令 x 不同,命令 h 不会修改当前模式空间的内容。 执行命令 h 时,当前保持空间的内容会被模式空间的内容覆盖。假定目前模式空间内容为"line 1",保持空间内容为"line 2"。那么执行命令 h 后,模式空间的内容仍然为"line 1",保持空间的内容则变为"line 1"
    打印管理者的名称
    :

    [root@ceph-node5 ~]# sed -n -e '/Manager/!h' -e '/Manager/{x;p}' empnametitle.txt
    Jason Smith
    Jane Miller

    上面例子中:

    /Manager/!h –如果模式空间内容不包含关键字’Manager’(模式后面的!表示不匹配该模式), 那么复制模式空间内容到保持空间。 (这样一来,保持空间的内容可能会
    是雇员名称或职位,而不是’Manager’.)注意, 和前面的例子不同,这个例子中没有使用命令 n 来获取下一行,而是通过正常的流程来读取后续内容。
    

    /Manager/{x;p} –如果模式空间内容包含关键字’Manager’,那么交换保持空间和模式空间的内容,并打印模式空间的内容。 这和我们在命令 x 的示例中的用法是相同的

    你也可以把上面命令保存到 sed 脚本中执行:

    [root@ceph-node5 ~]# vi h.sed
    #!/bin/sed -nf
    /Manager/!h
    /Manager/{x;p}
    
    [root@ceph-node5 ~]#  chmod u+x h.sed
    [root@ceph-node5 ~]# ./h.sed empnametitle.txt
    Jason Smith
    Jane Miller

    把模式空间内容追加到保持空间(命令 H)

    大写 H 命令表示把模式空间的内容追加到保持空间,追加之前保持空间的内容不会被覆盖;
    相反, 它在当前保持空间内容后面加上换行符
    ,然后把模式空间内容追加进来。
    假定目前模式空间内容为
    ”line 1”,保持空间内容为”line 2”。那么执行命令 H 后,模式空间的内容没有改变, 仍然为”line 1”,保持空间的内容则变为”line2 line 1”
    打印管理者的名称和职位
    (在不同的行上):

    [root@ceph-node5 ~]# sed -n -e '/Manager/!h' -e '/Manager/{H;x;p}' empnametitle.txt
    Jason Smith
    IT Manager
    Jane Miller
    Sales Manager


    上面例子中:

    /Manager/!h –如果模式空间内容不包含关键字’Manager’(模式后面的!表示不匹配该模式),那么复制模式空间内容到保持空间。 (这样一来,保持空间的内容可能会
    是雇员名称或职位,而不是’Manager’.)。这和之前使用命令 h 的方法是一样的。
    

    /Manager/{H;x;p} –如果模式空间内容包含关键字’Manager’,那么命令 H 把模式空间的内容(也就是管理者的职位)作为新行追加到保持空间,所以保持空间内容会变 为”雇员名称 职位”(职位包含关键字 Manager)。 然后命令 x 交换模式空间和保持空间的内容,随后命令 p 打印模式空间的内容。

    你也可以把上面命令保存到 sed 脚本中执行:

    [root@ceph-node5 ~]# vi H-upper.sed 
    #!/bin/sed -nf
    /Manager/!h
    /Manager/{H;x;p}
    [root@ceph-node5 ~]# chmod u+x H-upper.sed
    [root@ceph-node5 ~]# ./H-upper.sed empnametitle.txt
    Jason Smith
    IT Manager
    Jane Miller
    Sales Manager


    如果想把雇员名称和职位显示在同一行,以分号分开,那么只需稍微修改一下即可,如下:

    [root@ceph-node5 ~]#  sed -n -e '/Manager/!h' -e '/Manager/{H;x;;s/
    /:/p}' empnametitle.txt
    Jason Smith:IT Manager
    Jane Miller:Sales Manager


    这个例子除了在第二个-e 后面的命令中加入了替换命令之外,和前面的例子一样。 Hx p 都完成和之前相同的操作;在交换模式空间和保持空间之后,命令 s 把换行符 替换为分号,然后打印出来。
    你也可以把上面命令保存到
    sed 脚本中执行:

    [root@ceph-node5 ~]# vi H1-upper.sed
    #!/bin/sed -nf
    /Manager/!h
    /Manager/{H;x;s/
    /:/;p}
    [root@ceph-node5 ~]# chmod u+x H1-upper.sed
    [root@ceph-node5 ~]# ./H1-upper.sed empnametitle.txt
    Jason Smith:IT Manager
    Jane Miller:Sales Manager

     

     

    把保持空间内容复制到模式空间(命令 g)

     

    命令 g(get)把保持空间的内容复制到模式空间。
    这样理解:命令 h 保持(hold)住保持空间(hold space),命令 g 从保持空间获取(get)内容。
    假定当前模式空间内容为”line 1”,保持空间内容为”line 2”;执行命令 g 之后,模式空间内容变为”line 2”,保持空间内容仍然为”line 2”

    打印管理者的名称:

    [root@ceph-node5 ~]# sed -n -e '/Manager/!h' -e '/Manager/{g;p}' empnametitle.txt
    Jason Smith
    Jane Miller




    上面例子中:

    /Manager/!h –最近几个例子都在用它。如果模式空间内容不包含关键字’Manager’,那么就把他复制到保持空间。
    /Manager/{g;p} –把保持空间的内容丢到模式空间中,然后打印出来

    你也可以把上面命令保存到 sed 脚本中执行:

    [root@ceph-node5 ~]# vi g.sed
    #!/bin/sed -nf
    /Manager/!h
    /Manager/{g;p}
    [root@ceph-node5 ~]# chmod u+x g.sed
    [root@ceph-node5 ~]# ./g.sed empnametitle.txt
    Jason Smith
    Jane Miller

    把保持空间追加到模式空间(命令 G)


    大写 G 命令把当前保持空间的内容作为新行追加到模式空间中。模式空间的内容不会被覆盖,该命令在模式空间后面加上换行符 ,然后把保持空间内容追加进去。
    G g 的用法类似于 H h;小写命令替换原来的内容,大写命令追加原来的内容。
    假定当前模式空间内容为
    ”line 1”,保持空间内容为”line 2”;命令 G 执行后,模式空间内容变为”line 1 line 2”,同时保持空间内容不变,仍然为”line 2”
    以分号分隔,打印管理者的名称和职位
    :

    [root@ceph-node5 ~]# sed -n -e '/Manager/!h' -e '/Manager/{x;G;s/
    /:/;p}' empnametitle.txt
    Jason Smith:IT Manager
    Jane Miller:Sales Manager


    上面例子中:

    /Manager/!h –和前面的例子一样,如果模式空间内容不包含关键字’Manager’,那么就把他复制到保持空间。
    /Manager/{x;G;s/
    /:/;p} –如果模式空间包含’Manager’,那么:
      x –交换模式空间和保持空间的内容。
      G –把保持空间的内容追加到模式空间。
      s/
    /:/ --在模式空间中,把换行符替换为分号:。
      p 打印模式空间内容
      注意: 如果舍去命令 x,即使用/Manager/{G;s/
    /:/;p},那么结果会由“雇员职位: 雇员名称”变成”雇员名称: 雇员职位

    也可把上述命令写到 sed 脚本中然后执行:

    [root@ceph-node5 ~]# vi G-upper.sed
    #!/bin/sed -nf
    /Manager/!h
    /Manager/{x;G;s/
    /:/;p}
    [root@ceph-node5 ~]# ./G-upper.sed empnametitle.txt
    Jason Smith:IT Manager
    Jane Miller:Sales Manager

    八、sed 多行模式及循环

     
     Sed 默认每次只处理一行数据,除非使用 H,G 或者 N 等命令创建多行模式,每行之间用换行符分开。
    本章将解释适用于多行模式的
    sed 命令。
    提示:在处理多行模式是,请务必牢记
    ^只匹配该模式的开头,即最开始一行的开头,且$只匹配该模式的结尾,即最后一行的结尾。

    读取下一行数据并附加到模式空间(命令 N)

    就像大写的命令 H G 一样,只会追加内容而不是替换内容,命令 N 从输入文件中读取下一行并追加到模式空间,而不是替换模式空间。
    前面提到过,小写命令
    n 打印当前模式空间的内容, 并清空模式空间,从输入文件中读取下一行到模式空间,然后继续执行后面的命令。
    大写命令
    N,不会打印模式空间内容,也不会清除模式空间内容,而是在当前模式空间内容后加上换行符 ,并且从输入文件中读取下一行数据,

    追加到模式空间中,然后继续执行后面的命令。
    以分号分隔,打印雇员名称和职位
    :

    [root@ceph-node5 ~]# sed -e '{N;s/
    /:/}' empnametitle.txt
    John:Doe
    CEO:Jason Smith
    IT Manager:Raj Reddy
    Sysadmin:Anand Ram
    Developer:Jane Miller
    Sales Manager



    这个例子中:

    N 追加换行符
     到当前模式空间(雇员名称)的最后,然后从输入文件读取下一行数据,追加进来。因此,当前模式空间内容变为”雇员名称
     雇员职位”。
    s/
    /:/ 把换行符
     替换为分号,把分号作为雇员名称和雇员职位的分隔符

    流程如下图所示:

    打印多行模式中的第一行(命令 P)

    目前为止,我们已经学会了三个大写的命令(H,N,G),每个命令都是追加内容而不是替换内容。
    现在我们来看看大写的
    D P,虽然他们的功能和小写的 d p 非常相似,但他们在多行模式中有特殊的功能。
    之前说到,小写的命令
    p 打印模式空间的内容。大写的 P 也打印模式空间内容,直到它遇到换行符

    下面的例子将打印所有管理者的名称:

    ( cat << EOF
    John
    Doe
    CEO
    Jason Smith
    IT Manager
    Raj Reddy
    Sysadmin
    Anand Ram
    Developer
    Jane Miller
    Sales Manager
    EOF
    ) > empname.txt
    [root@ceph-node5 ~]# sed -n -e '/Manager/!h' -e'/Manager/{x;p}' empname.txt
    Jason Smith
    Jane Miller

     

    删除多行模式中的第一行(命令 D)

    之前提到,小写命令 d 会删除模式空间内容,然后读取下一条记录到模式空间,并忽略后面的命令,从头开始下一次循环。
    大写命令
    D,既不会读取下一条记录,也不会完全清空模式空间(除非模式空间内只有一行)
    它只会:

    删除模式空间的部分内容,直到遇到换行符
    
    忽略后续命令,在当前模式空间中从头开始执行命令

    假设有下面文件,每个雇员的职位都用@包含起来作为注释。 需要注意的是,有些注释是跨
    行的。 如
    @Information Technology officer@就跨了两行。请先建立下面示例文件:

    [root@ceph-node5 ~]# vim empnametitle-with-commnet.txt
    John Doe
    CEO @Chief Executive Officer@
    Jason Smith
    IT Manager @Infromation Technology
    Officer@
    Raj Reddy
    Sysadmin @System Administrator@
    Anand Ram
    Developer @Senior
    Programmer@
    Jane Miller
    Sales Manager @Sales
    Manager@


    现在我们的目标是,去掉文件里的注释:

    [root@ceph-node5 ~]#  sed -e '/@/{N;/@.*@/{s/@.*@//;P;D}}' empnametitle-with-commnet.txt
    John Doe
    CEO 
    Jason Smith
    IT Manager 
    Raj Reddy
    Sysadmin 
    Anand Ram
    Developer 
    Jane Miller
    Sales Manager 

    也可把上述命令写到 sed 脚本中然后执行:

    [root@ceph-node5 ~]# vim D-upper.sed
    #!/bin/sed -f
    /@/{
    N
    /@.*@/{s/@.*@//;P;D}
    }
    [root@ceph-node5 ~]# ./D-upper.sed empnametitle-with-commnet.txt
    John Doe
    CEO
    Jason Smith
    IT Manager
    Raj Reddy
    Sysadmin
    Anand Ram
    Developer
    Jane Miller
    Sales Manager


    这个例子中:

    /@/{ 这是外传循环。 Sed 搜索包含@符号的任意行,如果找到,就执行后面的命令;如果没有找到,则读取下一行。 为了便于说明,以第 4 行,即”@Information
    Technology”(这条注释跨了两行)为例,它包含一个@符合,所以后面的命令会被执行。
    N 从输入文件读取下一行,并追加到模式空间,以上面提到的那行数据为例,这里 N 会读取第 5 行,即”Officer@”并追加到模式空间,因此模式空间内容变
    为”@Informatioin Technology
    Officer@”。
    /@.*@/ 在模式空间中搜索匹配/@.*@/的模式,即以@开头和结尾的任何内容。当前模式空间的内容匹配这个模式,因此将继续执行后面的命令。
    s/@.*@//;P;D 这个替换命令把整个@Information Technology
    Officer@”替换为空(相当于删除)。 P 打印模式空间中的第一行,然后 D 删除模式空间中的第一行,然
    后从头开始执行命令(即不读取下一条记录,又返回到/@/处执行命令)

     

    循环和分支(命令 b :label 标签)

    使用标签和分支命令 b,可以改变 sed 的执行流程:

    :label 定义一个标签
    b lable 执行该标签后面的命令。 Sed 会跳转到该标签,然后执行后面的命令。
    注意:命令 b 后面可以不跟任何标签,这种情况下,它会直接跳到 sed 脚本的结尾

    下面例子将把 empnametitle.txt 文件中的雇员名称和职位合并到一行内,字段之间以分号:
    分隔,并且在管理者的名称前面加上一个星号
    *

    [root@ceph-node5 ~]# vim label.sed
    #!/bin/sed -nf
    h;n;H;x
    s/
    /:/
    /Manager/!b end
    s/^/*/
    :end
    p


    这个脚本中,鉴于之前的例子,你已经知道 h;n;H;x s/ /:/的作用了。下面是关于分支的操作

    Manager/!b end 如果行内不包含关键字”Manager”,则 跳转到’end’标签,请注意,
    你可以任意设置你想要的标签名称。 因此,只有匹配 Manager 的雇员名称签名,才会执行 s/^/*/(在行首加上星号*)。
    :end 即是标签

    给这个脚本加上可执行权限,然后执行:

    $ chmod u+x label.sed
    $ ./label.sed empnametitle.txt
    John Doe:CEO
    *Jason Smith:IT Manager
    Raj Reddy:Sysadmin
    Anand Ram:Developer
    *Jane Miller:Sales Manager


    个人觉得脚本里面的 h;n;H;x 可以用一个 N 替代, 这样就不用使用保持空间了。
    如果不使用标签,还可以: sed 'N;s/ /:/;/Manager/s/^/.*/' empnametitle.txt

    注意:我这里显示的结果:

    [root@ceph-node5 ~]#  chmod u+x label.sed
    [root@ceph-node5 ~]# ./label.sed empnametitle.txt
    John:Doe
    CEO:Jason Smith
    *IT Manager:Raj Reddy
    Sysadmin:Anand Ram
    Developer:Jane Miller

    使用命令 t 进行循环


    命令 t 的作用是,如果前面的命令执行成功,那么就跳转到 t 指定的标签处,继续往下执行后续命令。 否则,仍然继续正常的执行流程。
    下面例子将把 empnametitle.txt 文件中的雇员名称和职位合并到一行内,字段之间以分号:分隔,并且在管理者的名称前面加上三个星号*
    提示:我们只需把前面例子中的替换命令改为 s/^/***/即可带到该目的,下面这个例子仅仅是为了解释命令 t 是如何运行的。

    $ vi lable-t.sed
    #!/bin/sed -nf
    h;n;H;x
    s/
    /:/
    : repeat
    /Manager/s/^/*/
    /***/! t repeat
    p
    $ chmod u+x lable-t.sed
    $ ./lable-t.sed empnametitle.txt
    John Doe:CEO
    ***Jason Smith:IT Manager
    Raj Reddy:Sysadmin
    Anand Ram:Developer
    ***Jane Miller:Sales Manager
    这个例子中:

    这个例子中:

    下面的代码执行循环
    :repeat
    /Manager/s/^/*/
    /***/! t repeat
    /Manager/s/^/*/ 如果匹配到 Manager,在行首加上星号*
    /***/!t repeat 如果没有匹配到三个连续的星号*(用/***/!来表示),并且前面
    一行的替换命令成功执行了,则跳转到名为 repeat 的标签处(即 t repeat)
    :repeat 标签

    九·、综合案例(案例摘自 GNU sed 官网)

    综合案例 1:重命名文件名为小写

    [jacob@localhost ~] #cat   sed.sh
    #! /bin/sh
    # rename files to lower/upper case...
    #
    # usage:
    #       move-to-lower *
    #       move-to-upper *
    # or
    #       move-to-lower -R .
    #       move-to-upper -R .
    #
    help()
    {
    cat << eof
    Usage: $0 [-n] [-r] [-h] files...
    -n           do nothing, only see what would be done
    -R           recursive (use find)
    -h           this message
    files     files to remap to lower case
    Examples:
    $0 -n *               (see if everything is ok, then...)
    $0 *
    $0 -R .
    eof
    }
    apply_cmd='sh'
    finder='echo "$@" | tr " " "
    "'
    files_only=
    while :
    do
    case "$1" in
    -n) apply_cmd='cat' ;;
    -R) finder='find "$@" -type f';;
    -h) help ; exit 1 ;;
    *) break ;;
    esac
    shift
    done
    if [ -z "$1" ]; then
    echo Usage: $0 [-h] [-n] [-r] files...
    exit 1
    fi
    LOWER='abcdefghijklmnopqrstuvwxyz'
    UPPER='ABCDEFGHIJKLMNOPQRSTUVWXYZ'
    case `basename $0` in
    *upper*) TO=$UPPER; FROM=$LOWER ;;
    *)             FROM=$UPPER; TO=$LOWER ;;
    esac
    eval $finder | sed -n '
    # remove all trailing slashes
    s//*$//
    # add ./ if there is no path, only a filename
    ///! s/^/.//
    # save path+filename
    h
    # remove path
    s/.*///
    # do conversion only on filename
    y/'$FROM'/'$TO'/
    # now line contains original path+file, while
    # hold space contains the new filename
    x
    # add converted file name to line, which now contains
    # path/file-name
    converted-file-name
    G
    # check if converted file name is equal to original file name,
    # if it is, do not print nothing
    /^.*/(.*)
    1/b
    # now, transform path/fromfile
    , into
    # mv path/fromfile path/tofile and print it
    s/^(.*/)(.*)
    (.*)$/mv "12" "13"/p
    ' | $apply_cmd
    综合案例 2:获取 bash 环境变量
    #!/bin/sh
    set | sed -n '
    :x
    # if no occurrence of ‘=()’ print and load next line
    /=()/! { p; b; }
    / () $/! { p; b; }
    # possible start of functions section
    # save the line in case this is a var like FOO="() "
    h
    # if the next line has a brace, we quit because
    # nothing comes after functions
    n
    /^{/ q
    # print the old line
    x; p
    # work on the new line now
    x; bx

     sed文件块处理

    文件块处理动作

    操作符    用途            指令示例
    i      行前插入文本    2iYY 在第2行之前添加文本行”YY”
                     4,7iYY 在第4-7行的每一行前添加文本行
    a    行后插入文本       2aYY 在第2行之后添加文本
                     /^XX/aYY 在以XX开头的行之后添加文本
    c    替换当前行      2cYY 将第2行的内容修改为”YY”

    sed逐字符替换

    操作y操作可实现逐个字符替换

    修改后的文本有多行时

    以换行符
    分隔
    或者,以强制换行

     sed行替换的应用

     sed行替换的应用:

    找到主机名配置中的HOSTNAME行

    整行替换为新的主机名配置

     

    [root@localhost shell]# grep ^HOSTNAME /etc/sysconfig/network
    HOSTNAME=localhost.localdomain              //修改前
    [root@localhost shell]# sed -i '/HOSTNAME/cHOSTNAME=mysvr.tarena.com' /etc/sysconfig/network         //整行替换操作
    [root@localhost shell]# grep ^HOSTNAME                         /etc/sysconfig/networkHOSTNAME=mysvr.tarena.com         //修改后

     

    sed导入导出

    导入导出动作

    r动作应结合-i选项才会存入,否则只输出
    w 动作以覆盖的方式另存为新文件

    例子:

    操作符      用途        指令示例
    r      读入其他文件      3r b.txt 在第3行下方插入文件b.txt
                       4,7r b.txt 在第4-7每一行后插入文件b.txt
    w      写入其他文件      3w c.txt 将第3行另存为文件c.txt
                       4,7w c.txt 将第4-7行另存为文件c.txt

    sed复制剪切

    模式空间

    存放当前处理的行,将处理结果输出
    若当前行不符合处理条件,则原样输出
    处理完当前行再读入下一行来处理

     保持空间

    作用类似于'剪切板'
    默认存放一个空行(换行符
    )

    主要处理动作

    复制到剪贴板:

    
    H    模式空间 ---[追加]--->保持空间
    h    模式空间 ---[覆盖]--->保持空间

    读取剪贴板内容并粘贴:

    G    保持空间 ---[追加]--->模式空间
    g    保持空间 ---[覆盖]--->模式空间

     sed 的工作过程:

    把要处理的行先读入自己模式空间,然后用处理动作处理,处理完后输出处理后的结果,并把源数据输出,然后读入下一行到模式空间进行处理
    * 要处理的数据必须在模式空间,且模式空间不存储数据
    保持空间 保持空间里默认只保存一个换行符号
    
    要想存数据放到保持空间里,要手动把数据存进来了才可以;
     保持空间只负责存储数据,不会输出数据
    要想保持空间里的数据被sed处理,必须手动把保存空间里的数据调入模式空间
    * 保持空间只负责存储数据 放在保持空间里的数据不会被输出也不会被处理。保持空间里默认只保存一个换行符号
    

    sed流控制 

    参数选项      注释
    !取反操作    根据定址条件取反
    n读下一行    读入下一行进行处理(产生隔行的效果)

     

    sed练习:

    1、删除/etc/grub.conf文件中行首的空白符;
    sed -r 's@^[[:spapce:]]+@@g' /etc/grub.conf
    2、替换/etc/inittab文件中"id:3:initdefault:"一行中的数字为5;
    sed 's@(id:)[0-9](:initdefault:)@152@g' /etc/inittab
    3、删除/etc/inittab文件中的空白行;
    sed '/^$/d' /etc/inittab
    4、删除/etc/inittab文件中开头的#号;
    sed 's@^#@@g' /etc/inittab
    5、删除某文件中开头的#号及后面的空白字符,但要求#号后面必须有空白字符;
    sed -r 's@^#[[:space:]]+@@g' /etc/inittab
    6、删除某文件中以空白字符后面跟#类的行中的开头的空白字符及#
    sed -r 's@^[[:space:]]+#@@g' /etc/inittab
    7、取出一个文件路径的目录名称;
    echo "/etc/rc.d/" | sed -r 's@^(/.*/)[^/]+/?@1@g'    
    基名:
    echo "/etc/rc.d/" | sed -r 's@^/.*/([^/]+)/?@1@g'    
  • 相关阅读:
    冗余换性能从Backbone的triggerEvents说开了去
    Sublime Text3 中安装 Emmet
    windows下安装dig
    掺合模式(Mixin)
    Backbone.sync将模型同步到服务器
    Sublime text jQuery插件
    快捷键汇集
    动态创建script在IE中缓存js文件时导致编码不正确bug
    Firefox中使用location.hash会自动decodeURI Bug
    Backbone事件模块
  • 原文地址:https://www.cnblogs.com/zhongguiyao/p/8995915.html
Copyright © 2011-2022 走看看