• 文本处理三剑客之 sed 流编辑器(基础部分)


    文本处理三剑客之 sed 流编辑器(基础部分)


    SED 即 Stream EDitor。和交互式编辑器如 vi 需要打开整个文件不同,sed 是行编辑器,每次处理一行,比较适合在脚本中进行无交互编辑,对于大文件如果不大量使用多行技术,一般不会出现内存不足的问题。

    sed 工作原理

    对于 sed,我们可以这样理解,sed 就是一个店面,有两个房间,一个房间用于处理数据,另一个房间用于暂时保存数据,类似于临时仓库。店面的门口排队着一行行来自文件或管道的文本,店家让排队的行自动进入房间,但是一次只能进入一行,然后进行匹配和处理,直到全部处理完毕。

    处理用的房间是根据指定的条件对读入的文本行确定是否要处理的,而条件可以通过指定具体行号(例如第 3 行),或者正则表达式的模式(pattern)来匹配输入行(例如,包含 /hello/的行)、或混合使用以上两种方式确定两行来组成的连续行范围(例如,第 5 行至第 10 行,或第 1 行至匹配 /foo/ 为止)。从广义角度看,正则的 pattern 和行号都是某种模式,因此该房间称为模式空间 (Pattern Space),另一个用于保存数据的房间称为保持空间 (Hold Space)。

    模式空间中处理的动作包括:删除行、读入文件、输出至文件或屏幕、插入文本、修改或替换文本、或者把模式空间的行暂时保存至保持空间及其反向操作等等。

    我们一定要明确的是:sed 命令会自动进行循环的,一次只读入一行,读入的文本先删除其尾随的换行符,然后在模式空间中进行匹配和处理,在开启下一轮循环之前如果没有禁用默认的自动打印,或者使用打印命令输出,则在输出行后追加换行符,确保换行。另外,sed 默认不会修改原文件,除非指定 -i 选项。

    1. sed 命令格式

    sed [option]... [script] [input-file]...
    
    • 1

    其中:script 表示要执行的命令脚本,input-file 表示要进行处理的文件。

    这里先举例部分工作可能需要的示例:

    示例一: 禁用 SELinux

    # sed -Ei.bak 's/^(SELINUX=).*/\1disabled/' /etc/selinux/config
    
    • 1

    以上命令中选项 -E 表示使用扩展正则表达式,选项 -i.bak 表示先备份原文件并修改原文件。脚本包含在单或双引号中,s/// 替换命令先搜索以 ‘SELINUX=’ 开头的行,如果匹配,则替换为 ‘SELINUX=disabled’,其中的一对小括号表示分组,\1 代表引用前面匹配到的分组内容。最后是要修改的文件。

    示例二: 替换 ‘/etc/httpd/htthttpd.conf’ 中两行文本为 ‘User apache’ 和 ‘Group apache’

    # sed -Ei.bak -e 's/^(User).*/\1 apache/' -e 's/^(Group).*/\1 apache/' /etc/httpd/httpd.conf
    
    • 1

    以上命令中两个选项 -e 分别指定脚本。

    示例三: 修改 CentOS 8 中文件 ‘/etc/default/grub’,在匹配行最后双引号前增加 ’ net.ifname=0’, 实现网卡名使用 ‘eth0’ 这样的格式,注意完成以下操作后要重启系统

    # sed -Ei.bak '/^GRUB_CMDLINE_LINUX/s/"$/ net.ifnames=0"/' /etc/default/grub
    
    # cat /etc/default/grub
    GRUB_TIMEOUT=5
    GRUB_DISTRIBUTOR="$(sed 's, release .*$,,g' /etc/system-release)"
    GRUB_DEFAULT=saved
    GRUB_DISABLE_SUBMENU=true
    GRUB_TERMINAL_OUTPUT="console"
    GRUB_CMDLINE_LINUX="resume=UUID=2a7ff86b-764e-490a-b082-bd8e26dcc35e rhgb quiet net.ifname=0"
    GRUB_DISABLE_RECOVERY="true"
    GRUB_ENABLE_BLSCFG=true
    
    # grub2-mkconfig -o /boot/grub2/grub.cfg  # 适用 CentOS 7 及以上
    # grub-mkconfig -o /boot/grub/grub.cfg   # 适用 Ubuntu
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    以上 sed 脚本中 /^GRUB_CMDLINE_LINUX/ 用以匹配要修改的文本行,s/// 则替换匹配行中的最后那个双引号($ 表示行尾位置)变成 ’ net.ifname=0"'。

    示例四: 找出 ‘eth0’ 网卡的 ip 地址

    # ifconfig eth0 | sed -En '2s/^[^0-9]+([0-9.]+) .*$/\1/p'
    10.0.0.100
    
    • 1
    • 2

    以上脚本中的 ‘2’ 指定行号为 2 的地址条件,替换命令中搜索部分 ^[^0-9]+ 表示以非数字组成的开头字符串,小括号内表示以数字和点号组成的内容,最后空格开始至最后,替换部分 \1 引用小括号分组的内容,p 打印出替换后的结果。

    示例五: 如果想在 sed 脚本使用变量的值,这时需要双引号或使用三个单引号

    # var=bar; echo foo | sed 's/foo/$var/'
    $var
    # var=bar; echo foo | sed "s/foo/$var/"
    bar
    # var=bar; echo foo | sed 's/foo/'''$var'''/'
    bar
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    2. 常用选项

    -n, --quiet, --silent
        sed 默认在每次脚本执行循环结束前打印模式空间中的内容,而该选项禁用该自动打印功能。
    
    -e script, --expression=script
        把指定脚本中的命令添加到要执行的命令集合中,命令行上可以使用多次。
    
    -f script-file, --file=script-file
        把包含在脚本文件中的命令添加到准备运行的命令集合中。
    
    -E, -r, --regexp-extended
        使用扩展正则表达式,而不是基础正则表达式。
    
    -i[SUFFIX], --in-place[=SUFFIX]
        指定该选项将对原文件进行就地编辑。如果不使用该选项,默认不会对原文件进行修改。
    
        这是通过创建一个临时文件并把处理后的输出重定向到此文件,当处理完所有行到达输入文件结尾时,
        将该临时文件重命名为原始文件名(覆盖原文件)以实现就地编辑。
    
        如果提供了扩展名后缀(SUFFIX),则在临时文件重命名之前,先对原文件进行重命名,即在
        其原名称后面追加指定的后缀,从而生成备份副本。
    
        如果没有提供扩展名后缀,则会覆盖原始文件而不备份!!!
    
        注意:由于 '-i' 可以有一个可选的参数,所以,在其后不能跟随其他短选项,例如:
            sed -Ei '...' filename
        这与没有备份后缀的选项 '-E -i' 一样。filename 就会就地编辑,不会创建备份文件。而
            sed -iE '...' filename
        这与 '--in-place=E' 长选项一样,从而创建一个名为 'filenameE' 的备份文件。
    
        警告:小心同时使用 '-n -i' 选项,前者禁用自动打印,后者则在没有备份的情况下就地编辑原文件。
        如果不小心使用了该混合选项组合(且没有显示使用 'p' 命令),那么原文件将为空:
          sed -ni 's/foo/bar/' filename
    
    -follow-symlinks
        此选项仅在支持符号链接的平台可用,并且仅在指定了选项 '-i' 时有效。如果指定的文件是符号链接,
        则 sed 将会编辑符号链接引用的最终目标文件。默认是断开符号链接,不修改链接的目标文件。
    
    -s, --separate
        默认 sed 会把指定的多个文件视为一个单独的连续的大文件。通过该选项,允许把它们视为各自单独的文件。
        因此,像 '/abc/, /def/' 这样的地址范围匹配不会跨越多个文件,行号与每个文件相联系,
        '$' 引用每个文件的最后一行,并且,使用 'R' 命令调用文件时,会倒回到每个文件的开始。
    
    -u, --unbuffered
        尽可能及时输入和输出。如果输入来自像 'tail -f',并且希望尽早看到处理后的结果,特别有用。
    
    -z, --null-data, --zero-terminated
        把输入的内容当成文本行集合,每一行由零字节(ASCII 'NUL' 字符)结尾,而不是换行符结束。
        该选项与 'sort -z''find -print0' 等命令一起使用,可以处理任何文件名。
    
    --version
        打印正在运行的 sed 版本,然后退出
    
    --help
        打印使用 sed 的帮助
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54

    如果在命令行上没有使用 -e-f 选项,那么,命令行上第一个非选项参数将被当成要执行的脚本。

    如果在处理完上述内容后仍有任何参数,则这些参数都将解释为要处理的输入文件名。文件名 - 表示标准输入流。如果未指定文件名,则处理标准输入。

    3. sed 脚本

    3.1 sed 脚本概述

    sed 脚本由一个或多个 sed 命令组成,这些命令通过一个或多个 -e-f 选项后的参数传入,或者,如果没有使用这些选项,则第一个非选项参数传入的视为 sed 命令脚本。

    sed 命令遵循以下语法:

    [addr]X[options]
    
    • 1

    其中 X 是一个单字符的 sed 命令占位符。[addr] 是一个可选的行地址,如果没有指定地址,命令 X 会对所有输入行进行处理。addr 可以是一个单独的行号、一个正则表达式匹配的行、或者,由行号或正则匹配的行(需要两个地址)组成形成连续的行范围。[optinos] 是可用于某些 sed 命令的选项。

    下面的示例会删除输入文件的第30行到第35行,并将输出结果重定向到 output.txt 文件中,其中 30, 35 是一个地址范围,d(delete) 是删除命令:

    sed '30, 35d' input.txt > output.txt
    
    • 1

    下面的示例将打印所有的输入行,直到匹配以 foo 单词开头的行为止。如果匹配到指定的模式,则 sed 就立即中止,退出状态码为 42,如果匹配不带模式,同时没有发生其他错误,sed 退出时状态码为 0。其中以双斜杆包围的表达式 /^foo/ 是正则表达式,q(quit) 是退出命令,42 是退出命令时的状态码:

    sed '/^foo/q42' input.txt > output.txt
    
    • 1

    脚本或脚本文件中的多个命令之间可以使用分号或换行符分隔,也可以使用多个 -e-f 选项指定多个脚本。

    下面的示例都是等价的。它们完成两个 sed 操作:先逐行读入,删除匹配正则表达式 /^foo/ 的行;然后再用 world 替换行中的第一个出现的 hello (如果有的话):

    sed '/^foo/d; s/hello/world/' input.txt > output.txt
    sed -e '/^foo/d' -e 's/hello/world' input.txt > output.txt
    
    echo '/^foo/d' > script.sed               # 把脚本写入文件中
    echo 's/hello/world' >> script.sed        # 把脚本追加到文件中
    sed -f script.sed input.txt > output.txt
    
    echo 's/hello/world' > script2.sed
    sed -e '/^foo/d' -f script2.sed input.txt > output.txt
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    由于语法原因,命令 a(append)、c(change)、i(insert) 后面不能使用分号作为命令之间的分隔,因此,应该使用换行作为这些命令之间的分隔,或者,把这些命令放在脚本的最后。

    3.2 sed 命令摘要

    sed 提供了以下命令。注意,每次读入文本行时都会自动删除行尾的换行符,而在输出时自动在行尾追加换行符。

    a\
    text
    a text
        (append) 在匹配到的行后追加一行指定的文本 'text'。
    
    c\
    text
    c text
        (change) 使用文本 'text' 替换或改变匹配的行。
    
    i\
    text
    i text
        (insert) 在当前行的前面插入一行指定文本 'text'。
    
    d
        (delete) 删除模式空间的内容,一般就是当前读入的行,立即开始下一轮循环。
    
    D
        如果模式空间中含有换行符,则删除该空间中的第一个换行符及其前面的文本,然后,在没有读取下一行文本
        的前提下开始下一轮循环,注意,此时模式空间中还留有未删除的内容。
    
        如果模式空间中没有换行符,则删除空间中的内容,并启动新一轮循环,这与执行 'd' 命令效果一样。
    
    e
        (execute) 发送到 shell 中执行在模式空间中找到的命令,并使用命令执行后的结果替换模式空间中的内容,
        且删除行尾的换行符。
    
    e command
        执行 'command' 命令,并将其执行结果发送至输出流。该命令可以跨多行执行,除了最后以反斜杠结尾的行例外。
    
    F
        (filename) 打印当前输入文件的名称(以一个换行符结尾)。
    
    g
        (goto) 使用保持空间的内容替换模式空间的内容。
    
    G
        先在模式空间中的内容后追加一个换行符,再追加保持空间的内容。
    
    h
        (hold space) 使用模式空间的内容替换保持空间的内容。
    
    H
        先在保持空间中追加一个换行符,再追加模式空间中的内容。
    
    l
        (list) 以可视的形式打印模式空间的内容。就是将不可打印的字符打印成可显示的 C 语言式样
        的字符,例如,相应的转义序列打印成 '\a''\b''\f''\r''\t''\v' 等,并在行尾添加一个 '$'。
    
    n
        (next) 如果没有禁用自动打印功能,则先打印模式空间的内容,然后,立即从输入文件中读取下一行文本,
        并替换掉模式空间。如果没有更多的输入,则 sed 直接退出。
    
    N
        先在模式空间中内容后面追加一个换行符,再追加从输入文件中读取的下一行文本。
        如果没有更多的输入,sed 则退出。
    
    p
        (print) 打印模式空间中的内容。
    
    P
        (大写) 打印模式空间中第一个换行符及其前面的内容。
    
    q[exit-code]
        (quit) 打印模式空间内容后,不执行更多的命令后退出。'exit-code' 是可以指定的退出状态码。
    
    Q[exit-code]
        该命令与 'q' 命令一样,但是不会打印模式空间的内容。
    
    r filename
        (read) 在匹配的行后读入以 "filename" 为文件名的文件内容。
    
    R filename
        把要读入的文件排成一队,并在当前循环结束后,或者在读取下一行文本前,
        把 'filename' 的内容插入到输出流中。
    
    w filename
        (write) 把模式空间中的内容写到以 "filename" 为文件名的文件中。
    
    W filename
        把模式空间中的第一个换行符及其前面的内容写到以 "filename" 为文件名的文件中。
    
    b label
        (branch) 表示程序无条件地跳转到标签 'label' 位置。如果省略了标签,将开始下一轮循环。
    
    #
        # 及其后面直至换行符之前的文本都是注释。
    
    { cmd; cmd ...}
        将多个命令组合在一起,形成命令集。
    
    =
        打印当前输入行的行号,并换行。
    
    t label
        (test) 只有在当前读入的行上执行上一个 's' 替换命令成功,或者,执行条件分支后,程序才会跳到
        标签 'label' 处。如果标签省略,则开始下一轮循环。
    
    T label
        只有在当前行上执行上一个 's' 替换命令失败后,或者,执行条件分支后,程序才会跳到标签 'label' 处。
        变迁如果省略,开始下一轮循环。
    
    : label
        为分支命令('b' 表示无条件分支,'t''T' 表示条件分支)指定跳转标签 'label' 的位置。
    
    x
        (exchange) 交换模式空间和保持空间中的内容。
    
    z
        (zap) 清空模式空间中的内容。
    
    y/src/dst/
        如果模式空间中的内容匹配 'src' 源字符集中的字符,则逐个转换为 'dst' 目标字符集中对应的字符。
        该命令的功能与 shell 中的 'tr' 命令类似。注意,其内部不能使用正则表达式。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115

    3.3 s 替换命令

    替换命令 s(substitute) 可能是 sed 中最重要的命令,它有许多选项。其使用语法是:

    's/regexp/replacement/flags'
    
    • 1

    该命令的基本概念不复杂:即试图使用正则表达式中的搜索模式 regexp 去匹配模式空间中的内容,如果匹配成功,则使用替换部分 replacement 替换匹配的部分。这个语法在许多命令中都有,例如 vimbash中。

    替换部分可以使用表达式 \n 反向引用正则表达式中的使用小括号进行分组匹配的内容,n 取值范围是 [1-9]。此外,替换部分可以使用未用反斜杠转义的 & 来引用正则表达式匹配的全部。

    替换命令中的三个斜杠 / 可以统一变更为其它任意单个字符。例如,s/foo/bar/ --> s@foo@bar@。在某些情况下,可以避免使用很多的反斜杠进行转义。

    最后,作为 GNU sed 的扩展,可以使用反斜杠和以下字符组成的序列进行大小写字符转换。具体含义如下:

    \L  (lowercase) 在遇到 '\U' 或者 '\E' 之前,把替换部分 'replacement' 全部字符转化成小写。
    
    \l  (lowercase) 把下一个字符转换成小写。
    
    \U  (uppercase) 在遇到 '\L' 或者 '\E' 之前,把替换部分 'replacement' 全部字符转化成大写。
    
    \u  (uppercase) 把下一个字符转换成大写。
    
    \E  (end) 终止有 '\L' 或者 '\U' 开始的大小写转换。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    当使用 g(global) 标志 (flag) 时,执行全局替换。例如,在模式空间中的字符串 a-b-,执行以下命令时:

    $ echo 'a-b-' | sed -E 's/(b?)-/x\u\1/g'
    axxB
    
    • 1
    • 2

    这里使用 -E,表示使用扩展正则表达式,可以少使用反斜杠进行转义,更简洁点。正则表达式中的 ? 表示前面的字符可以出现 0 次或者 1 次,小括号包围的字符串表示分组,后面可以使用 \1 来引用第一个分组。在以上正则表达式第一次进行匹配时会匹配到第一个 -,第二次匹配到 b-,因此,\1 分别引用两次匹配到的分组字符串 ''b

    如果没有使用 g 标志,则只会替换出现的第一个匹配项:

    $ echo 'a-b-' | sed -E 's/(b?)-/x\u\1/'
    axb-
    
    • 1
    • 2

    如果含有 \l\u 的替换部分中使用了反向引用表达式 \n,而反向引用的分组内容为空时,那么,\l\u 将影响反向引用后面的字符。例如,以下命令:

    $ echo 'a-b-' | sed 's/\(b\?\)-/\u\1x/g'
    aXBx
    
    • 1
    • 2

    由于第一次匹配到的分组内容为空,因此 \u 影响到 \1 后面的 x 变成大写的 ‘X’。第二次匹配到分组内容 b-\u 影响到字符 b 变成大写 B

    如果想在替换部分包含字面的 \& 或换行符,那么,请确保在这些字符前面使用反斜杠进行转义,即成 \\\&\n

    s 替换命令可以使用 0 个或 0 个以下的标志:

    g
        (global) 全局替换所有匹配正则表达式的文本,而不是默认的第一个匹配项。
    
    number
        只替换地 'number' 个匹配的 'regexp' 项。
        注意:如果同时使用 'g''number' 标志,对于 GNU sed 来说,会忽略第 'number' 个
        前面的匹配项,匹配和替换第 'number' 个及其后面的匹配项。
    
    p
        (print) 如果替换成功,则打印替换后的模式空间内容。
        注意:如果同时指定了标志 'p''e',那么,这两个标志的相对次序不同会产生不同
        的结果。一般而言,'ep' 是您想要的,即先执行再打印。但是,反过来操作对于调试很有用。
    
    w filename
        (write) 如果替换成功,则将替换后的结果写到以 'filename' 为文件名的文件中。作为
        GNU sed 扩展,如果没有使用选项 '-i',提供了两种特殊的文件名:'/dev/stderr',
        把结果打印到标准错误中;'/dev/stdout',把结果打印到标准输出中。
    
    e
        (execute) 此标志允许将 shell 命令通过管道输入到模式空间,如果替换命令成功,那么,
        执行在模式空间中找到的命令,并用命令执行的结果替换模式空间,删除尾随的换行符。
        该标志是 GNU sed 扩展。
    
    I
        (insensitive) 这是 GNU sed 的扩展,它使 sed 以不区分大小写的方式匹配正则表达式。
    
    M
        (multi-line) 这是 GNU sed 的扩展。它指示 sed 在多行模式下匹配正则表达式。
        其修饰符引起 '^''$' 分别匹配换行符及其后的空位置,行尾空位置及其后的换行符。
        而特殊字符序列 (\' 和 \') 一直分别匹配缓冲区的开始和结尾。
        另外,在多行模式下,'.' 字符不会匹配换行符。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    3.4 常用命令

    如果您使用 sed,很可能想要知道以下命令:

    • # [不允许有地址]

      字符 # 开始一个注释,一直到换行为止。

    • q[exit-code]

      不会执行更多的命令,或者再读入行,立即退出 sed。例如,在打印第 2 行后退出:

      $ sed 3 | sed 2q
      1
      2
      
      • 1
      • 2
      • 3

      该命令只能接受一个地址。注意,如果没有使用 -n 禁用自动打印功能,则会打印模式空间的内容。

    • d

      删除模式空间的内容,立即启动下一轮循环。例如,删除第 2 输入行:

      $ seq 3 | sed 2d
      1
      3
      
      • 1
      • 2
      • 3
    • p

      打印模式空间的内容至标准输出。此命令通常只与 -n 选项一起使用。例如,只打印第 2 行:

      $ seq 3 | sed -n 2p
      2
      
      • 1
      • 2
    • n

      如果没有禁用自动打印功能,则默认会打印模式空间的内容,然后,读入下一输入行,并替换模式空间。如果没有了更多的输入行,则退出 sed。

      此命令用于跳过一个输入行。例如,完成间隔两行后的替换(2n 跳过 2 行):

      $ seq 6 | sed 'n; n; s/./x/'
      1
      2
      x
      4
      5
      x
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7

      GNU sed 提供一个扩展的地址语法:‘开始~步长’ 起到相同的效果:

      $ seq 6 | sed '0~3s/./x/'
      
      • 1
    • { cmds }

      可以把一组命令组合成命令集。当您希望在匹配正则表达式时,一起执行几个命令时特别有用。例如,
      完成替换后打印第 2 行:

      $ seq 3 | sed -n '2{s/2/x/; p}'
      x
      
      • 1
      • 2

    3.5 使用频率较低的命令

    虽然以下命令使用频率相比较低,但是,使用它们可以构建一些小而实用的 sed 脚本。

    • y/source-chars/dest-chars/

      模式空间中的任何字符如果能够匹配源字符集 (source-chars) 中的字符,那么,逐个转换成目标字符集 (dest-chars) 中的相对应位置的字符。与 tr 命令功能相似。

      例如,转换 ‘a-j’ 至 ‘0-9’:

      $ echo hello world | sed ‘y/abcdefghij/0123456789/’
      74llo worl3
      
      • 1
      • 2
    • a text

      在匹配行后添加文本 ‘text’。例如,在第 2 后面追加单位 ‘hello’:

      $ seq 3 | sed '2a hello'
      1
      2
      hello
      3
      
      • 1
      • 2
      • 3
      • 4
      • 5
    • `a\

      text`

      在匹配行后添加文本 ‘text’。例如,在第 2 后面追加单位 ‘hello’:

      $ seq 3 | sed '2a\
      > hello'                  # 字符 '>' 是自动产生的,不是输入的
      1
      2
      hello
      3
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6

      以下命令接受两个地址,并在不带反斜杠的 ‘world’ 行后继续:

      seq 3 | sed '2a\
      > hello\
      > world
      > 3s/./x/'
      1
      2
      hello
      world
      x
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9

      作为 GNU sed 扩展,a 命令和文本 ‘text’ 可以分为两个 -e 参数,从而实现更简单的脚本编写:

      $ seq 3 | sed -e '2a\' -e hello
      
      $ sed -e '2a\' -e "$VAR"
      
      • 1
      • 2
      • 3
    • i text

      在当前行前插入文本 ‘text’。例如:在第二行前面插入单词 ‘hello’:

      $ seq 3 | sed ‘2i hello’
      1
      Hello
      2
      3
      
      • 1
      • 2
      • 3
      • 4
      • 5
    • c text

      使用文本 ‘text’ 替换一行或多行。例如,使用单词 ‘hello’ 替换第 2 行至第 9 行:

      $ seq 10 | sed '2, 9c hello'
      1
      hello
      10
      
      • 1
      • 2
      • 3
      • 4
    • =

      打印当前输入行的行号,且换行。例如:

      $ printf '%s\n' aaa bbb ccc | sed =
      1
      aaa
      2
      bbb
      3
      ccc
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
    • r filename

      在匹配行后面读入文件内容。例如:

      $ seq 3 | sed '2r/etc/hostname'
      1
      2
      Fencepost.gun.org
      3
      
      • 1
      • 2
      • 3
      • 4
      • 5
    • 其他几个命令

      l nw filenameDNPhHgGx

    3.6 多命令语法

    在 sed 程序中有几种方法可以指定多个命令。

    当使用 -f 选项来指定从脚本文件中读取并运行 sed 脚本时,使用换行来分隔命令是最自然的。而命令行上,所有的 sed 命令也可以使用换行来分隔,或者,可以把每个命令都作为 -e 选项的参数使用:

    $ seq 6 | sed '1d
    > 3d
    > 5d'
    2
    4
    6
    
    $ seq 6 | sed -e 1d -e 3d -e 5d
    2
    4
    6
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    可以使用分号来分隔简单的命令

    $ seq 6 | sed ‘1d; 3d; 5d’
    2
    4
    6
    
    • 1
    • 2
    • 3
    • 4

    {}btT 这些命令可以使用分号进行分隔,例如:

    $ seq 4 | sed '{1d;3d}'
    2
    4
    $ seq 6 | sed '{1d;3d};5d'
    2
    4
    6
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    btT 命令中使用标签:读取分号之前的命令,标签前后的空白都会忽略。在下面的示例中,标签是 x

    第一个示例在 GNU sed 中可以正常执行。第二个是可移植的等价物。

    $ seq 3 | sed '1/b x ; s/^/=/ ; :x ; 3d'
    1
    =2
    
    $ seq 3 | sed -e '1/bx' -e 's/^/=/' -e ':x' -e '3d'
    1
    =2
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    3.7 多个命令之间需要换行的命令

    以下命令不能使用分号分隔,需要换行:

    • a, c, i

      aci 命令后面的所有字符都当成是追加、更改、插入的文本。这时使用分号会导致您不希望的结果:

      $ seq 2 | sed '1aHello ; 2d'
      1
      Hello ; 2d
      2
      
      $ seq 2 | sed '1aHello
      > 2d'
      1
      Hello
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
    • # (comment)

      从 # 开始至换行为止之间的所有字符都当成注释。

      $ seq 3 | sed '# this is a comment ; 2d'
      1
      2
      3
      
      $ seq 3 | sed '# thist is a comment
      > 2d'
      1
      3
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
    • r, R, w, W

      rRwW 命令会把该命令之后的所有字符都解析成文件名。如果发现有空格、注释或分号包含在文件名中,将导致意外结果。

      $ seq 2 | sed '1w hello.txt ; 2d'
      1
      2
      $ ls -log
      total 4
      -rw-r--r--. 1 2 Nov 29 08:41 'hello.txt ; 2d'
      $ cat hello.txt\ \;\ 2d
      1
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
    • e

      e 命令会把该命令之后、行尾之前的所有字符都送到 shell 当成命令执行。如果发现空格、注释或分号包含在文件名中,会导致意外结果:

      $ echo a | sed '1e touch foo#bar'
      a
      $ ls -1
      foo#bar
      $ echo a | sed '1e touch foo ; s/a/b/'
      sh: s/a/b/: No such file or directory
      a
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
    • s///[we]

      在成功替换后,w 命令会把替换结果写到指定的文件中,而 e 命令会把替换结果当成 shell 命令执行。如果发现空格、注释或分号包含在文件名中,会导致意外结果:

      $ echo a | sed 's/a/b/w1.txt#foo'
      b
      $ls -1
      1.txt#foo
      
      • 1
      • 2
      • 3
      • 4

    4. 地址:选择行

    4.1 地址摘要

    地址条件决定了 sed 命令将会在哪些行上执行。例如,下面的命令只在第 144 行中出现的第一个 ‘hello’ 替换成 ‘world’:

    sed '144s/hello/world/' input.txt > output.txt
    
    • 1

    如果没有指定地址,那么替换命令将在所有行上执行。例如,下面的命令将对输入文件中的所有行中出现的第一个 ‘hello’ 替换成 ‘world’:

    sed 's/hello/world' input.txt > output.txt
    
    • 1

    地址可以使用正则表达式,以便根据行内容而不是行号来匹配行。例如,下面的命令只在包含 ‘apple’ 的行上出现的第一个 ‘hello’ 替换成 ‘world’:

    sed '/apple/s/hello/world/' input.txt > output.txt
    
    • 1

    地址范围由逗号分隔的两个地址指定。地址可以是数字(行号)、正则表达式或者两者的组合。例如,下面的命令只在第 4 行到第 17 行(含 4 和 7)之间的行上出现的第一个 ‘hello’ 替换成 ‘world’:

    sed '4,17s/hello/world/' input.txt > output.txt
    
    • 1

    在命令字符之后,地址声明之后添加一个 字符对匹配状态取反。那就是说,如果 字符跟在一个地址或者一个地址范围之后,那么,只有没有被匹配的地址行将会被选择。例如,下面的命令只在不含包 ‘apple’ 的行上出现的第一个 ‘hello’ 替换成 ‘world’:

    sed '/apple/!s/hello/world' input.txt > output.txt
    
    • 1

    4.2 通过数字选择行

    sed 脚本中的地址可以采用以下任何形式:

    • number

      指定行号,只会匹配输入的哪一行。注意:除非指定了 -i-s 选项,否则 sed 会对所有输入文件中的行进行连续计数。

    • $

      与输入的最后的文件的最后一行匹配,或者在指定了 -i-s 选项时,将与每个文件的最后一行匹配。

    • first~step

      (开始~步长) 这时 GNU 扩展。匹配从第 first 行开始的每个步长行。可以使用 0~2 匹配偶数行,使用 1~2 匹配奇数行。

      $ seq 10 | sed '0~4p'
      4
      8
      
      $ seq 10 | sed -n '1~3p'
      1
      4
      7
      10
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9

    4.3 通过文本匹配选择行

    GNU sed 支持以下正则表达式地址。

    • /regexp/

      选择所有匹配正则表达式 regexp 的行。如果正则表达式中包含任何斜杆 ( / ) 字符,其中的每一个斜杆必须使用反斜杠 ( \ ) 进行转义。

      下面的命令打印在 ‘/etc/passwd’ 文件中以 ‘bash’ 结尾的所有行:

      $ sed -n '/bash$/p' /etc/passwd
      
      • 1

      当然还有其他方法可以实现这个功能,例如:

      $ grep 'bash$' /etc/passwd
      
      $ awk -F: '$7 == "/bin/bash"' /etc/passwd
      
      • 1
      • 2
      • 3
    • \%regexp%

      其中的 % 字符可以由其他任意单个字符替代。如果正则表达式中包含大量的斜杆时,比较好,可以避免使用大量的反斜杠进行转义。

      以下命令时等价的。都是打印以 /home/alice/documents/ 开头的所有行:

      $ sed -n '/^\/home\/alice\/documents\//p'
      $ sed -n '\%^/home/alice/documents/%p'
      
      • 1
      • 2
    • /regexp/I, \%regexp%I

      这是 GNU 扩展,修饰符 I 使正则表达式能以不区分大小写的方式进行匹配。由于 i 已经用于插入命令,因此使用大写的 I

      在以下示例中,/B/I 是一个文本匹配地址,使用不区分大小写匹配,d 是删除命令:

      $ printf "%s\n" a B c | sed '/b/Id'
      a
      c
      
      • 1
      • 2
      • 3

      而使用小写 i 命令时,其功能时插入命令,而 ‘d’ 表示要插入的值。

      $ printf "%s\n" a b c | sed '/b/id'
      a
      d
      b
      c
      
      • 1
      • 2
      • 3
      • 4
      • 5
    • /regexp/M, \%regexp%M

      这时 GNU sed 的扩展。修饰符 M 指示 sed 可以在多行模式下匹配正则表达式。

      地址决定是否处理当前模式空间的内容,但是,如果模式空间内容发生了变化(例如,使用了替换命令),那么,后面的正则表达式将会匹配变化后的文本。例如,下面的示例,禁用了自动打印功能,s/2/x/ 替换命令改变了包含的 ‘2’ 到 ‘x’,而 /[0-9]/p 命令匹配包含数字的行,然后打印出来。

      $ seq 3 | sed -n 's/2/x/ ; /[0-9]/p'
      1
      3
      
      • 1
      • 2
      • 3

    4.4 范围地址

    通过逗号分隔两个地址来指定地址范围。地址范围匹配从第一个地址匹配的位置开始的行,直到第二个地址匹配为止(含前后两个地址):

    $ seq 10 | sed -n '4,6p'
    4
    5
    6
    
    • 1
    • 2
    • 3
    • 4

    如果第二个地址是正则表达式,那么检查结束匹配将从与第一个地址匹配行开始向后搜索,范围将始终跨越至少两行(除非输入流结束):

    $ seq 10 | sed -n '4, /[0-9]/p'
    4
    5
    
    • 1
    • 2
    • 3

    如果第二个地址小于或等于第一个地址匹配行的数字,则只匹配第一行:

    $ seq 10 | sed -n '4, 1p'
    4
    
    • 1
    • 2

    GNU sed 支持一些特殊的地址范围形式。

    • 0, /regexp/

      在地址规范中可以使用行号 0,例如 ‘0, /regexp/’,表示试图在第一行进行匹配。而 ‘/1, /regexp/’ 表示试图从第二行开始搜索。

      以下示例演示从地址 1 开始与从地址 0 开始的区别:

      $ seq 10 | sed -n '1, /[0-9]/p'
      1
      2
      
      $ seq 10 | sed -n '0, /[0-9]/p'
      1
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
    • addr1, +N

      匹配第 addr1 行及其后面的 N 行:

      $ seq 10 | sed -n '6, +2p'
      6
      7
      8
      
      • 1
      • 2
      • 3
      • 4
    • addr1, ~N

      匹配第 addr1 行及其后面的行,直到行号是 N 的倍数。例如,以下命令从第 6 行开始,直到是 4 的倍数行,即第 8 行:

      $ seq 10 | sed -n '6, ~4p'
      6
      7
      8
      
      $ seq 100 | sed -n '10, ~13p'
      10
      11
      12
      13
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10

    5. 正则表达式:选择文本行

    5.1 sed 正则表达式摘要

    正则表达式(regexp)许多程序都在使用,例如,grep,vim等。sed 中的正则表达式指定在两个斜杠之间。其威力来自于可以使用替代项和具有重复的能力。有些特殊字符在模式中并不代表自身,而是有其它含义。

    例如,正则表达式中的字符 ^ 与行首位置匹配。字符 . 匹配任意单个字符。

    $ printf "%s\n" abode bad bed bit bid byte body | sed -n '/^b.d/p'
    bad
    bed
    bid
    body
    
    • 1
    • 2
    • 3
    • 4
    • 5

    5.2 基础正则表达式和扩展正则表达式

    基础正则表达式(BRE)和扩展正则表达式(ERE)是模式的两种语法变体。sed 默认使用的 BRE,与 grep 类似。使用选项 -E, -r, --regexp-extended 其中之一启用 ERE 语法。

    在 GNU sed 中,两种语法之间的唯一区别是以下特殊字符的行为不同:?+(){}|

    对于 BRE 语法,这些字符没有特殊意义,除非有前缀的反斜杠才具有特殊意义;而对于 ERE 语法,这些字符刚好相反,即它们是特殊的,除非有前缀的反斜杠,才变成其字面意义上的字符本身。

    5.3 BRE 语法摘要

    下面是 sed 中使用的正则表达式语法简要描述。

    字符
    说明
    char单个普通字符只能匹配自己。
    *匹配 * 前面零个或多个匹配实例的序列,匹配实例必须是普通单个字符、反斜杠转义的特殊字符、.、分组、或者方括号表达式。
    .匹配任何单个字符,包括换行符。但是在多行模式下不匹配换行符!
    ^匹配模式空间中最开始的位置。
    $^ 类似,它引用模式空间的结尾位置。
    [list], [^list][list] 表示列表中的任意单个字符。例如,[aeiou] 匹配所有元音。而 [^list] 则相反。
    \+表示一个或多个前面的实例序列,等价于 \{1, \}
    \?表示零个或者一个前面实例序列,等价于 \{0, 1\}
    \{N\}表示刚好 N 个前面实例序列。
    \{N, M\}表示 N 个及其以上至 M 个前面实例序列。
    \{N, \}表示 N 个及其以上各前面实例序列。
    regexp" role="presentation">regexp小括号包围的表达式作为子表达式进行分组。可以应用于后缀操作,例如,\(abcd\)* 表示零次或多次重复的 ‘abcd’,如 ‘abcdabcd’;也可用于反向引用。
    regexp1|regexp2表示要么匹配 regexp1,要么匹配 regexp2
    \digit表示引用第 digit 个前面使用小括号包围的子表达式所形成的分组,称为反向引用。
    \n匹配换行符。
    \char如果 char 单个字符本身是特殊字符,那么,这种转义后代表字面意义字符自身。注意,\t 在大多数 sed 实现版本中并不代表制表符。

    注意:正则表达式的匹配时是贪婪的,即尽可能长地匹配长度。

    5.4 字符类和方括号表达式

    方括号表达式使用方括号包围的列表。表示可以匹配该列表中的任意单个字符;如果列表中的第一个字符是 ^,则表示相反的含义,即会匹配不在该列表中的任何字符。例如,以下示例,把 ‘gray’ 或 ‘grey’ 替换成 ‘blue’:

    $ sed 's/gr[ae]y/blue/'
    
    • 1

    在方括号中使用连字符 (-) 分隔两个字符组成范围表达式。例如,[a-d] 等同于 [abcd]

    最后,sed 已经使用方括号预定义了一些字符类。例如:

    $ echo 1 | sed 's/[[:digit:]]/x/'
    x
    
    • 1
    • 2

    以下是预定义的字符类:

    字符类
    说 明
    [:alnum:](alphanumeric) 字符数字类:[:alpha:] 和 [:digit:]。
    [:alpha:](alphabetic) 字符类:[:lower:] 和 [:upper:]。
    [:blank:]空字符:空格和 tab 制表符。
    [:cntrl:](control) 控制字符类。
    [:digit:]数字类,等价于 [0-9]。
    [:graph:]图形化字符类:[:alpha:] 和 [:punct:]。
    [:lower:]小写字符,等价于 [a-z]。
    [:print:]可打印的字符:[:alnum:], [:punct:] 和 空白字符。
    [:punct:](punctuation) 标点符号。
    [:space:]空白字符:在 C 语言环境中,tab 制表符、换行符、垂直 tab 制表符、换页、回车和空格。包含 [:blank:]。
    [:upper:]大写字符:[A-Z]。
    [:xdigit:](hexadecimal) 十六进制数字。

    还有一些细节与 bash 中一致。例如,]-^ 在方括号中不同位置具有不同的含义。在方括号中,$[\ 不再具有特殊含义。

    5.5 正则表达式扩展

    以下字符序列具有特殊的含义,在基础和扩展正则表达式中语法一致。

    \w    匹配任意组成“单词”的字符。组成“单词”的字符是任意字母、数字或下划线(_)。
          $ echo "abc %-= def." | sed 's/\w/X/g'
          XXX %-= XXX.
    
    \W    匹配任意“非单词”字符。
          $ echo "abc %-= def." | sed 's/\W/X/g'
          abcXXXXXdefX
    
    \b    匹配单词边界。
          $ echo "abc %-= def." | sed 's/\b/X/g'
          XabcX %-= XdefX.
    
    \B    匹配单词边界以外的任何位置。
          echo "abc %-= def." | sed 's/\B/X/g'
          aXbXc X%X-X=X dXeXf.X
    
    \s    匹配空白字符(空格和各种tab制表符)。内嵌在模式空间和保持空间的换行符也会匹配。
          echo "abc %-= def." | sed 's/\s/X/g'
          abcX%-=Xdef.
    
    \S    匹配非空白字符。
          echo "abc %-= def." | sed 's/\S/X/g'
          XXX XXX XXXX
    
    \<    匹配一个单词的开始位置。
          echo "abc %-= def." | sed 's/\
          Xabc %-= Xdef.
    
    \>    匹配一个单词的结束位置。
          echo "abc %-= def." | sed 's/\>/X/g'
          abcX %-= defX.
    
    \‘    只匹配模式空间的开始位置。这与多行模式的 '^' 不同。
    
          比较以下两个例子:
          printf "a\nb\nc\n" | sed 'N;N;s/^/X/gm'
          Xa
          Xb
          Xc
    
          # 译者:反斜杠后面的字符是通过按键 1 左边的那个键实现的。
          printf "a\nb\nc\n" | sed 'N;N;s/\`/X/gm'
          Xa
          b
          c
    
    \’    只匹配模式空间的结束位置。这与多行模式的 '$' 不同。这个字符不知道是哪个。
          printf "a\nb\nc\n" | sed 'N;N;s/$/X/gm'
          aX
          bX
          cX
    
          # 实验时发现有趣的现象
          printf "aAAA\nb\nc\n" | sed 'N;N;s/\|/X/gm'
          (printf "aAAA\nb\nc\n" | sed -E 'N;N;s/|/X/gm')一样的结果:
          XaXAXAXAX
          XbX
          XcX
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
  • 相关阅读:
    中南林业科技大学javaweb实验报告
    [elastic 8.x]java客户端连接elasticsearch与操作索引与文档
    面试经典150题——Day21
    【JS 逆向百例】猿人学系列 web 比赛第二题:js 混淆 - 动态 cookie,详细剖析
    Android进阶之旅(第八天:尝试实现BottomDialog)
    vim编辑器
    面试题:Hash 碰撞是什么?如何解决?
    Spring 事务和事务传播机制
    Ask Milvus Anything!聊聊被社区反复@的那些事儿ⅠⅠ
    python中的命名空间和变量作用域介绍
  • 原文地址:https://blog.csdn.net/qq_39785418/article/details/128070232