美文网首页
学习《玩透sed》基础篇

学习《玩透sed》基础篇

作者: 一萍之春 | 来源:发表于2021-07-20 15:26 被阅读0次

    通过学习《玩透sed》整理

    作者: 骏马金龙
    学习链接: https://www.junmajinlong.com/how_to_nav_posts/
    学习来源: 骏马金龙

    整体的思维导图查看

    sed入门

    sed的基本概率

    sed是一个流式的编辑程序,它读取输入流(可以是文件、标准输入)的每一行放进模式空间 (pattern space),
    同时将此行行号通过 sed 行号计数器记录在内存中,然后对模式空间中 的行进行模式匹配,如果能匹配上则使用 sed 程序内部的命令进行处理,
    处理结束后,从模式空间中输出(默认)出去,并清空模式空间,
    随后再从输入流中读取下一行到模式空间 中进行相同的操作,
    直到输入流中的所有行都处理完成。由此可见,sed 是一个循环一个 循环处理内容的。

    一次sed处理的过程:
    1. 读取输入流的一行到模式空间。
    2. 对模式空间中的内容进行匹配和处理。 
    3. 自动输出模式空间内容。
    4. 清空模式空间内容。
    5. 读取输入流的下一行到模式空间。
    PS:如果是文件会一次性加载一定量的到os buffer之中然后再一行行的读取,如果使用的是管道或者其他的输入流,就是直接从对应的缓存之中再一行行的读取。
    

    我们在这个过程中可以通过我们命令所改变的是第二步,其他步骤都是固定的模式。
    但是sed的3,4步可以通过sed的几个命令以及选项进行改变,使其输出空或者无法清空模式空间。

    语法格式:
    sed OPTIONS SCRIPT INPUT_STREAM
    

    SCPIPT部分就是sed脚本,它是 sed 内部命令的集合,sed中的命令有些奇 特,它包含行匹配以及要执行的命令。
    格式为 ADDR1[,ADDR2]cmd_list。例如,要对第2行执行删除命令,其命令为 sed 2d filename,只输出第 4 行到 6 行,其命令为 sed -n 4,6p。

    因为SCRIPT是命令的集合所以循环可以修改为:
    1. 读取输入流的一行到模式空间。
    2. 对模式空间中内容执行SCRIPT。(包括上面示例中的"2d"和"4,6p") 
    3. 读取输入流的下一行到模式空间。
    4. 对模式空间中内容执行SCRIPT。
    

    SCRIPT部分除了包含了 sed 命令行中的内部命令,还包括两个特殊动作:自动输出和清 空模式空间内容。这两个动作是一定会执行的,但是有些命令也可以改变让这两个命令不进行执行。

    用shell程序实现过程就是这样:
    for ((line=1;line<=last_line_num;++line))
    do    
        read $line to pattern_space; while pattern_space is not null
        do
            execute cmd1 in SCRIPT; 
            execute cmd2 in SCRIPT; 
            execute cmd3 in SCRIPT; 
            ......
            auto_print; 
            remove_pattern_space;
        done
    done
    

    在for循环中读取下一行和执行 SCRIPT 循环。而在SCRIPT 循环中就是script语句的执行以及对模式空间的清除。

    sed的命令行书写也就是script部分的书写。
    # 一行式。多个命令使用分号分隔 sed Address{cmd1;cmd2;cmd3...}
    # 多个表达式时,可以使用"-e"选项,也可以不用,但使用分号分隔
    sed Address1{cmd1;cmd2;cmd3};Address2{cmd1;cmd2;cmd3}...
    sed -e 'Address1{cmd1;cmd2;cmd3}' -e 'Address2{cmd1;cmd2;cmd3}' ...
    # 分行写时
    sed Address1{
        cmd1
        cmd2
        cmd3
    } Address2{
        cmd1
        cmd2
        cmd3
    }
    如果写在文件中即sed本
    #!/usr/bin/sed -f 
    #注释行 
    Address1{cmd1;cmd2...} 
    Address2{cmd1;cmd2...} 
    ......
    

    其中 cmd 部分还可以进行模式匹配,也即类似于 Address{{pattern1}cmd1;{pattern2}cmd2}的写法。例如,/^abc/{2d;p}。

    sed 选项

    可能用到的几个选项:
    '-n'
    默认情况下,sed 将在每轮 script 循环结束时自动输出模式空间中的内容。使用该选项 后可以使得这次自动输出动作输出空内容,而不是当前模式空间中的内容。
    注意,"-n"是 输出空内容而不是禁用输出动作,虽然两者的结果都是不输出任何内容,但在有些依赖于 输出动作和输出流的地方,
    它们的区别是很大的,前者有输出流,只是输出空流,后者则 没有输出流。
    '-e SCRIPT'
    前文说了,SCRIPT 中包含的是命令的集合,"-e"选项就是向 SCRIPT 中添加命令的。可 以省略"-e"选项,但如果命令行容易产生歧义,则使用"-e"选项可明确说明这部分是 SCRIPT 中的命令。
    另外,如果一个"-e"选项不方便描述所需命令集合时,可以指定多个"- e"选项。
    '-f SCRIPT-FILE'
    指定包含命令集合的 SCRIPT 文件,让 sed 根据 SCRIPT 文件中的命令集处理输入流。
    '-i[SUFFIX]'
    该选项指定要将 sed 的输出结果保存(覆盖的方式)到当前编辑的文件中。
    GNU sed 是通过创建一个临时文件并将输入写入到该临时文件,然后重命名为源文件来实现的。
    当当前输入流处理结束后,临时文件被重命名为源文件的名称。如果还提供了 SUFFIX, 则在重命名临时文件之前,
    先使用该 SUFFIX修改源文件名,从而生成一个源文件的备份 文件
    临时文件总是会被重命名为源文件名称,也就是说输入流处理结束后,仍使用源文件名的文件是sed 修改后的文件。文件名中包含了 SUFFIX 的文件则是最原始文件的备份。
    例如:源文件为 a.txt,sed -i'.log' SCRIPT a.txt 将生成两个文件:a.txt 和 a.txt.log,前者 是 sed 修改后的文件,a.txt.log 是源 a.txt 的备份文件。
    
    重命名的规则如下:如果扩展名不包含符号"*",将 SUFFIX添加到原文件名的后面当作文 件后缀;如果 SUFFIX中包含了一个或多个字符"*",则每个"*"都替换为原文件名。这使得 你可以为备份文件添加一个前缀,而不是后缀。如果没有提供 SUFFIX,源文件被覆盖, 且不会生成备份文件。
    该选项隐含了"-s"选项。
    '-r'
    使用扩展正则表达式,而不是使用默认的基础正则表达式。sed 所支持的扩展正则表达式 和 egrep 一样。使用扩展正则表达式显得更简洁,因为有些元字符不用再使用反斜线"\"。 正则表达式见 grep 命令中文手册。
    '-s'
    默认情况下,如果为 sed 指定了多个输入文件,如 sed OPTIONS SCRIPT file1 file2 file3,
    则多个文件会被 sed 当作一个长的输入流,也就是说所有文件被当成一个大文 件。
    指定该选项后,sed将认为命令行中给定的每个文件都是独立的输入流。
    既然是独立的输入流,范围定址(如/abc/,/def/)就无法跨越多个文件进行匹配,行号也会在处理每个文件时重置,
    "$"代表的也将是每个文件的最后一行。这也意味着,如果不使 用该选项,则这几个行为都是可以完成的。
    

    语句练习

    1.只输出 a.txt 中的第 5 行。
    sed -n 5p a.txt
    sed -n -e '5p' a.txt
    
    2.输出 a.txt,并输出每行的行号。
    sed '=' a.txt
    3.分别输出a.txt b.txt并分别保存到".bak"后缀的文件中。
    sed -i'*.bak' -n '5p' a.txt b.txt
    此处需要使用-s 但是-i里面已经包含了-s 如果不使用-s会是他们两者合并的第5行文件 
    4.使用扩展正则表达式,输出 a.txt 和 b.txt 中能包含 "zip"的行。
    sed -r -n '/zip+/p' a.txt b.txt
    

    定址表达式

    当 sed 将输入流中的行读取到模式空间后,就需要对模式空间中的内容进行匹配,如果能 匹配就能执行对应的命令,
    如果不能匹配就直接输出、清空模式空间并进入下一个 sed 循 环读取下一行。
    匹配的过程称为定址。定址表达式有多种,但总的来说,其格式为[ADDR1][ADDR2]。

    定址表达式的三种方式:
    1. ADDR1 和 ADDR2 都省略时,表示所有行都能被匹配上。
    2. 省略ADDR2时,表示只有被ADDR1表达式匹配上的行才符合条件。
    3. 不省略ADDR2时,是范围地址。表示从ADDR1匹配成功的行开始,到ADDR2匹配成功的行结束
    

    无论是 ADDR1 还是 ADDR2,都可以使用两种方式进行匹配:行号和正则表达式。如下:

    'N'
    指定一个行号,sed 将只匹配该行。(需要注意,除非使用了"-s"或"-i"选项,sed 将对所有 输入文件的行连续计数。)\
    'FIRST~STEP'
    表示从第 FIRST 行开始,每隔 STEP 行就再取一次。也就是取行号满足 FIRST+(N*STEP) (其中 N>=0)的行。因此,要选择所有奇数行,使用"1~2";\
    要从第 2 行开始每隔 3 行取一 次,使用"2~3";要从第 10 行开始每隔 5 行取一次,使用"10~5";而"50~0"则表示只取第 50 行。
    '$'
    默认该符号匹配的是最后一个文件的最后一行,如果指定了"-i"或"-s",则匹配的是每个文 件的最后一行。\
    总之,"$"匹配的是每个输入流的最后一行
    se d 采用行号计数器来临时记录当前行的行号,因此 sed 在读取到最后一行前即使是倒数第二行的时候,完全不知道最后一行是第几行,
    所以代表最后一行的"$"无法进行任何数学运算,例如倒数第二行使用"$-1"表示是错误的。
    而且,"$"只是一个额外的 标记符号,当 sed读取到输入流的最后一行时,发现这就是最后一行,于是为此行打上 "$"记号,并读取到模式空间中。
    
    '/REGEXP/'
    将选择能被正则表达式 REGEXP 匹配的所有行。如果 REGEXP 中自身包含了字符"/",则 必须使用反斜线转义,即"\/"。
    '/REGEXP/I'
    和"/REGEXP/"是一样的,只不过匹配的时候不区分大小写。
    '\%REGEXP%'
    ('%'可以使用其他任意单个字符替换。) 这和上一个定址表达式的作用是一样的,只不过是 使用符号"%"替换了符号"/"。
    当 REGEXP 中包含"/"符号时,使用该定址表达式就无需对"/" 使用反斜线"\"转义。
    但如果此时 REGEXP 中包含了"%"符号时,该符号需要使用"\"转义。 
    总之,定址表达式中使用的分隔符在 REGEXP 中出现时,都需要使用反斜线转义。
    'ADDR1,+N'
    匹配 ADDR1 和其后的 N 行。
    
    'ADDR1,~N'
    匹配 ADDR1 和其后的行直到出现 N 的倍数行。倍数可为随意整数倍,只要 N 的倍数是最 接近且大于 ADDR1 的即可。
    如 ADDR1=1,N=3 匹配 1-3 行,ADDR1=5,N=4 匹配 5-8 行。 而"1,+3"匹配的是第一行和其后的 3 行即 1-4 行。
    另外,在定址表达式的后面加"! "符号表示反转匹配的含义。也就是说那些匹配的行将不被 选择,而是不匹配的行被选择。
    
    定址的示例
    sed -n '3p' INPUTFILE
    sed -n '3,5!p' INPUTFILE
    sed -n '3,/^# .*/! p' INPUTFILE
    sed -n '/abc/,/xyz/p' INPUTFILE
    sed -n '!p' INPUTFILE # 这个有悖常理,但确实是允许的
    

    常用命令

    强制输出命令"p"

    for ((line=1;line<=last_line_num;++line))
    do
    done done
        read $line to pattern_space; 
        while pattern_space is not null
        do
            execute cmd1 in SCRIPT; 
            execute cmd2 in SCRIPT; 
            ADDR1,ADDR2{print}; #"P"command
            ......
            auto_print; # '-n'影响的
            remove_pattern_space;
        done
    done
    

    在处理过程中这个是两个动作一个是p命令的输出一个是auto_print的输出,使用'-n'就可以不让 auto_print进行输出。\

    仅输出标准输入的第 2 行内容。
    [root@VM-0-6-centos script]# echo -e 'abc\nxyz' | sed -n 2p
    xyz
    如果不用 -n 这里 auto_print会自动输出一遍所以可以输出两遍
    [root@VM-0-6-centos script]# echo -e 'abc\nxyz' | sed  2p
    abc
    xyz
    xyz
    

    删除命令"d"

    for ((line=1;line<=last_line_num;++line))
    do
    done done
        read $line to pattern_space; 
        while pattern_space is not null
        do
            execute cmd1 in SCRIPT; 
            execute cmd2 in SCRIPT; 
            ADDR1,ADDR2{delete;break}; #"D"command
            ......
            auto_print; # '-n'影响的
            remove_pattern_space;
        done
    done
    

    注意这里使用'd'命令时,会直接跳出script循环如果delete命令后还有其他命令时都不会被执行了。
    例如:删除a.txt 第五行并保存原文件之中。

    sed -i '5d' a.
    这里不能使用重定向的方式保存,因为重定向是在 sed 命令执行前被 shell 执行的,所以 会截断 a.txt,使得 sed 读取的输入流为空,\
    或者结果出乎意料之外。而"-i"选项则不会操作原文件,而是生成临时文件并在结束时重命名为原文件名。
    

    删除 a.sh 中包含"#"开头的注释行,但第一行的#!/bin/bash 不删除。

    sed '/^#/{1!d}' a.sh
    

    如果"d"后面还有命令,在删除模式空间后,这些命令不会执行,因为会立即退出当前 SCRIPT 循环。例如:

    echo -e 'abc\nxyz' | sed '{/abc/d;=}' 
    2
    xyz
    

    其中"="这个命令用于输出行号,但是结果并没有输出被"abc"匹配的行的行号。

    退出 sed 程序命令"q"和"Q"

    使用"q"和"Q"命令的作用是立即退出当前 sed 程序,使其不再执行后面的命令,也不再读 取后面的行。

    # "q"命令
    for ((line=1;line<=last_line_num;++line))
    do
        read $line to pattern_space; 
        while pattern_space is not null
        do
            execute cmd1 in SCRIPT; 
            execute cmd2 in SCRIPT; 
            ADDR1,ADDR2{auto_print;exit}; # "q" command
            ......
            auto_print;
            remove_pattern_space;
    
        done 
    done
    # "Q"命令
    for ((line=1;line<=last_line_num;++line))
    do
        read $line to pattern_space; 
        while pattern_space is not null
        do
            execute cmd1 in SCRIPT; 
            execute cmd2 in SCRIPT; 
            ADDR1,ADDR2{exit}; # "Q" command
            ......
            auto_print; 
            remove_pattern_space;
    
        done 
    done
    

    例如,搜索脚本 a.sh,当搜索到使用了"."或"source"命令加载环境配置脚本时就输出并立 即退出。

    sed -n -r '/^[ \t]*(\.|source) /{p;q}' a.sh
    

    输出行号命令

    "="命令用于输出最近被读取行的行号。在 sed 内部,使用行号计数器进行行号计数,每读 取一行,行号计数器加 1。
    计数器的值存储在内存中,在要求输出行号时,直接插入在输 出流中的指定位置。由于值是存在于内存中,而非模式空间中,因此不受"-n"选项的影 响。
    这是一个依赖于输出流的命令,只要有输出动作就会追加在该输出流的尾部。
    例如,搜索出 httpd.conf 中"DocumentRoot"开头的行的行号,允许有前导空白字符。

    sed -n '/^[ \t]*DocumentRoot/{p;=}' 
    httpd.conf DocumentRoot "/var/www/html"
    119
    

    字符一一对应替换命令"y"

    该命令与tr命令映射功能一样都是字符进行一一替换
    例如,将 a.txt 中包含大写字母的 YES、Yes 等替换成小写的 yes。

    sed 'y/YES/yes/' a.txt
    

    手动读取下一行命令"n

    在 sed 的循环过程中,每个 sed 循环的第一步都是读取输入流的下一行到模式空间中,这 是我们无法控制的动作。但 sed 有读取下一行的命令"n"。
    由于是读取下一行,所以它会触发自动输出的动作,于是就有了输出流。不仅如此,还应
    该记住的是:只要有读取下一行的行为,在其真正开始读取之前一定有隐式自动输出的行

    但需注意,当没有下一行可供"n"读取时(例如文件的最后一行已经被读取过了),将输出模 式空间内容后直接退出 sed 程序,使得"n"命令后的所有命令都不会执行,即使是那两个隐 含动作。

    for ((line=1;line<=last_line_num;++line))
    do
        read $line to pattern_space; 
        while pattern_space is not null
        do
            execute cmd1 in SCRIPT;
            execute cmd2 in SCRIPT;
            ADDR1,ADDR2{ # "n" command
                if [ "$line" -ne "$last_line_num" ];then 
                    auto_print;
                    remove_pattern_space;
                    read next_line to pattern_space;
                else
                    auto_print; 
                    remove_pattern_space; 
                    exit;
                fi
            };
            ......
            auto_print; 
            remove_pattern_space;
        done 
    done
    

    注意,是先判断是否有下一行可读取,再输出和清空 pattern space 中的内容,所以 then 和 else 语句中都有这两个动作。 也许感觉上似乎更应该像下面这样的优化形式:

    ADDR1,ADDR2{ # "n" command 
        auto_print;
        remove_pattern_space;
        [ "$line" -ne "$last_line_num" ] && read next_line to pattern_space || exit;
    };
    

    事实并非如上的代码
    例如,搜索 a.txt 中包含"redirect"字符串的行以及其下一行,并输出。

    sed -n '/redirect/{p;n;p}' a.txt
    
    echo -e "abc\ndef\nxyz" | sed '/abc/{n;=;p}'
    abc
    2
    def
    def xyz
    从结果中可以分析出,"n"读取下一行前输出了"abc",然后立即读入了下一行,所以输出 的行号是 2 而不是 1,
    因为这时候行号计数器已经读取了下一行,随后命令"p"输出了该模 式空间的内容,输出后还有一次自动输出的隐含动作,所以"def"被输出了两次
    

    替换命令"s"

    将匹配到的内容替换成指定的内容。

    "s"命令的语法格式为:其中"/"可以替换成任意其他单个字符。
    s/REGEXP/REPLACEMENT/FLAGS
    它使用 REGEXP 去匹配行,将匹配到的那部分字符替换成 REPLACEMENT。FLAGS 是 "s"命令的修饰符,常见的有"g"、"p"和"i"或"I"。
        • "g":表示替换行中所有能被 REGEXP 匹配的部分。不使用 g 时,默认只替换行中 的第一个匹配内容。此外,"g"还可以替换成一个数值 N,表示只替换行中第 N 个 被匹配的内容。
        • "p":输出替换后模式空间中的内容。
        • "i"或"I":REGEXP 匹配时不区分大小写。
    REPLACEMENT 中可以使用"\N"(N 是从 1 到 9 的整数)进行后向引用,所代表的是 REGEXP第N个括号(...)中匹配的内容。另外,REPLACEMENT中可以包含未转义的"&"
    符号,这表示引用 pattern space 中被匹配的整个内容。需要注意,"&"是引用 pattern space 中的所有匹配,不仅仅只是括号的分组匹配。
    

    例如,删除 a.sh 中所有"#"开头(可以包括前导空白)的注释符号"#",但第一行"#! /bin/bash" 不处理。

    sed -i '2,$s/^[ \t]*#//' a.sh
    

    为 a.sh 文件中的第 5 行到最后一行的行首加上注释符号"#"。

    sed '5,$s/^/#/' a.sh
    

    将 a.sh 中所有的"int"单词替换成"SIGINT"

    sed 's/\bint\b/SIGINT/g' a.sh
    

    将 a.sh 中"cmd1 && cmd2 || cmd3"的 cmd2 和 cmd3 命令对调个位置。

    sed 's%&&\(.*\) ||\(.*\)%\&\&\2 ||\1%' a.
    这里使用了"%"代替"/",且在 REPLACEMENT 部分对"&"进行了转义,因为该符号在 REPLACEMENT 中时表示的是引用 REGEXP 所匹配的所有内容。
    

    追加、插入和修改命令"a"、"i"、"c"。

    这 3 个命令的格式是"[a|i|c] TEXT",表示将 TEXT 内容队列化到内存中,当有输出流或者 说有输出动作的时候,半路追上输出流,分别追加、插入和替换到该输出流然后输出。
    追 加是指追加在输出流的尾部,插入是指插入在输出流的首部,替换是指将整个输出流替换掉。
    "c"命令和"a"、"i"命令有一丝不同,它替换结束后立即退出当前 SCRIPT 循环,并进入下一个sed 循环,因此"c"命令后的命令都不会被执行。
    

    例如:

    echo -e "abc\ndef" | sed '/abc/a xyz' 
    abc
    xyz
    def
    echo -e "abc\ndef" | sed '/abc/i xyz' 
    xyz
    abc
    def
    echo -e "abc\ndef" | sed '/def/i xyz' 
    abc
    xyz
    def
    echo -e "abc\ndef" | sed '/def/c xyz' 
    abc
    xyz
    echo -e "abc\ndef" | sed '/abc/c xyz' 
    xyz
    def
    

    其实"a"、"i"和"c"命令的 TEXT 部分写法是比较复杂的,如果 TEXT 只是几个简单字符,如 上即可。
    但如果要 TEXT 是分行文本,或者包含了引号,或者这几个命令是写在"{}"中的,则上面的写法就无法实现。
    需要使用符号""来转义行尾符号,这表示开启一个新行,此后 输入的内容都是 TEXT,直到遇到引号或者";"开头的行时\

    例如,在 a.sh 的#!/bin/bash 行后添加一个注释行"# Script filename: a.sh"以及一个空 行。由于是追加在尾部,所以使用"a"命令。

    sed '\%#!/bin/bash%a\# Script filename: a.sh\n' a.sh
    

    "a"命令后的第一个反斜线用于标记 TEXT 的开始,"\n"用于添加空白行。如果分行写,或
    者"a"命令写在大括号"{}"中,则格式如下:

    sed '\%#!/bin/bash%a\
    # Script filename: a.sh\n ' a.sh
    
    sed '\%#!/bin/bash%{p;a\ # Script filename: a.sh\n ;p}' a.sh
    最后需要说的是,这 3 个命令的 TEXT 是存放在内存中的,不会进入模式空间,因此不受 "-n"选项或某些命令的影响。
    此外,这 3 个命令依赖于输出流,只要有输出动作,不管是 空输出流还是非空的输出流,只要有输出,这几个命令就会半路"劫杀"。
    如果不理解这两句话,这 3个命令的结果有时可能会比较疑惑。
    

    例如,"a"命令是追加在当前匹配行行尾的,但为什么下面的"haha"却插入到匹配行"def"的 前面去了呢?

    echo -e "abc\ndef\nxyz" | sed '/def/{a\ haha
    ;N}'
    abc
    haha
    def
    xyz
    

    多行模式命令"N"、"D"、"P"简单说明。

    "N"命令:读取下一行内容追加到模式空间的尾部。其和"n"命令不同之处在于:"n" 命令会输出模式空间的内容(除非使用了"-n"选项)并清空模式空间,
    然后才读取下 一行到模式空间,也就是说"n"命令虽然读取了下一行到模式空间,但模式空间仍 然是单行数据。
    而"N"命令在读取下一行前,虽然也有自动输出和清空模式空间的 动作,但该命令会把当前模式空间的内容锁住,使得自动输出的内容为空,
    也无法 清空模式空间,然后读取下一行追加到当前模式空间中的尾部。追加时,原有内容 和新读取内容使用换行符"\n"分隔,
    这样在模式空间中就实现了多行数据。即所谓 的"多行模式"。另外,当无法读取到下一行时(到了文件尾部),
    将直接退出sed 程序,使得"N"命令后的命令不会再执行,这和"n"命令是一样的
    例如:
    echo -e "abc\ndef\nxyz" | sed '/def/{a\ haha;N}'
    abc
     haha
    def
    xyz
    echo -e "abc\ndef\nxyz" | sed '/def/{a\ haha;n}'
    abc
    def
     haha
    xyz
    "D"命令:删除模式空间中第一个换行符"\n"之前的内容,然后立即回到 SCRIPT 循 环的顶端,即进入下一个 SCRIPT 循环。如果"D"删除后,模式空间中已经没有内 容了,则 SCRIPT 循环自动退出进入下一个 sed 循环;如果模式空间还有剩余内 容,则继续从头执行 SCRIPT 循环。也就是说,"D"命令后的命令不会被执行。
    "P"命令:输出模式空间中第一个换行符"\n"之前的内容。
    
    # "N"命令的大致循环结构
    for ((line=1;line<=last_line_num;++line))
    do
        read $line to pattern_space; 
        while pattern_space is not null
        do
            execute cmd1 in SCRIPT;
            execute cmd2 in SCRIPT; 
            ADDR1,ADDR2{ # "N" command
            if [ "$line" -ne "$last_line_num" ];then 
                lock pattern_space;
                auto_print;
                remove_pattern_space;
                unlock pattern_space;
                append "\n" to pattern_space; 
                read next_line to pattern_space;
            else
                auto_print;
                remove_pattern_space; 
                exit;
            fi
            };
            ......
            auto_print; 
            remove_pattern_space;
        done 
    done
    # "D"命令的大致循环结构
    for ((line=1;line<=last_line_num;++line))
    do
        
        read $line to pattern_space; 
        while pattern_space is not null
        do
            execute cmd1 in SCRIPT;
            execute cmd2 in SCRIPT;
            ADDR1,ADDR2{ # "D" command
                delete first line in pattern_space;
                continue; 
            };
            ......
            auto_print;
            remove_pattern_space;
        done 
    done
    # "P"命令的大致循环结构
    for ((line=1;line<=last_line_num;++line))
    do
        read $line to pattern_space;
        while pattern_space is not null
        do
            execute cmd1 in SCRIPT;
            execute cmd2 in SCRIPT;
            ADDR1,ADDR2{ # "P" command
            print first line in pattern_space; };
            ......
            auto_print; remove_pattern_space;
        done
    done
    

    buffer 空间数据交换命令"h"、"H"、"g"、"G"、"x"简单说明。

    ed 除了维护模式空间(pattern space),还维护另一个 buffer 空间:保持空间(hold space)。这两个空间初始状态都是空的

    • "h"命令:将当前模式空间中的内容覆盖到保持空间。
    • "H"命令:在保持空间的尾部加上一个换行符"\n",并将当前模式空间的内容追加到
    保持空间的尾部。
    • "g"命令:将保持空间的内容覆盖到当前模式空间。
    • "G"命令:在模式空间的尾部加上一个换行符"\n",并将当前保持空间的内容追加到
    模式空间的尾部。
    • "x"命令:交换模式空间和保持空间的内容。
    

    总结

    N n的证明

    echo -e "abc\ndef\nxyz" | sed '/def/{a\
    > haha
    > ;N}'
    abc
    haha
    def
    xyz
    echo -e "abc\ndef" | sed '/def/{a\         
    haha
    ;N}'
    abc
    def
    haha
    

    解释第一个命令为何"haha"会出现在匹配行"def"的前面。当 sed 读取的行能匹配 "def"时,
    将队列化"haha"到内存中,并在有输出流的时候追加到输出流尾部。由于这里的 输出流来自于"a"命令后的"N"命令,
    该命令将模式空间锁住,使得隐含动作自动输出的内 容为空,但队列化的内容还是发现了这个空输出流,
    于是追加在这个空流的尾部。再之 后,"N"将下一行读取到模式空间中,到了 SCRIPT 循环的结尾,再次自动输出,此时模式空间有两行:"def" 和 "xyz",这两行同时被输出。
    显然,在"def"被输出之前,队列化的 内容已经随着空输出流而输出了。
    再解释为何第二个命令的结果中"haha"在"def"之后,这也是待证明的疑问。第二个命令 中,由于"def"已经是输入流的最后一行,
    "N"已经无法再读取下一行,于是输出当前模式空 间内容并退出 sed 程序。
    假设,"n"或"N"命令是先自动输出、清空模式空间内容,再判断 是否有下一行可读取的,那么在判断之前自动输出时,
    "N"不知道是否还有下一行,于是队 列化的内容应该同第一个命令一样,插入在"def"之前。但结果却并非如此。
    如果先判断是 否有下一行可供读取,再输出、清空模式空间,则队列化内容是跟随着"N"退出 sed 程序前 输出的,这正符合第二个命令的结果。

    相关文章

      网友评论

          本文标题:学习《玩透sed》基础篇

          本文链接:https://www.haomeiwen.com/subject/cgfrmltx.html