请选择 进入手机版 | 继续访问电脑版
    查看: 241|回复: 4

    [经验分享] 基于sed常用命令的介绍(2)

    [复制链接]

    签到天数: 178 天

    [LV.7]化身百千

    发表于 2018-9-30 16:37:11 | 显示全部楼层 |阅读模式
    承接上一篇帖子 ,继续介绍几个sed常用的命令。
    (6).手动读取下一行命令"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;
    };
    但事实证明并非如此,证明过程在本文结尾。此处暂不讨论这些复杂的东西,先看看"n"命令的示例。

    例如,搜索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"被输出了两次。

    (7).替换命令"s"。

    这是sed用的最多的命令。两个字就能概括其功能:替换。将匹配到的内容替换成指定的内容。

    "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.sh  
    这里使用了"%"代替"/",且在REPLACEMENT部分对"&"进行了转义,因为该符号在REPLACEMENT中时表示的是引用REGEXP所匹配的所有内容。

    (8).追加、插入和修改命令"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
    其实"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"命令之后,再回头看这个示例,应该能知道为什么。在sed修炼系列(四):sed中的疑难杂症中给出了解释。

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

    在前面已经解释了"n"、"d"和"p"命令,sed还支持它们的大写命令"N"、"D"和"P"。

    "N"命令:读取下一行内容追加到模式空间的尾部。其和"n"命令不同之处在于:"n"命令会输出模式空间的内容(除非使用了"-n"选项)并清空模式空间,然后才读取下一行到模式空间,也就是说"n"命令虽然读取了下一行到模式空间,但模式空间仍然是单行数据。而"N"命令在读取下一行前,虽然也有自动输出和清空模式空间的动作,但该命令会把当前模式空间的内容锁住,使得自动输出的内容为空,也无法清空模式空间,然后读取下一行追加到当前模式空间中的尾部。追加时,原有内容和新读取内容使用换行符"\n"分隔,这样在模式空间中就实现了多行数据。即所谓的"多行模式"。 另外,当无法读取到下一行时(到了文件尾部),将直接退出sed程序,使得"N"命令后的命令不会再执行,这和"n"命令是一样的。

    "D"命令:删除模式空间中第一个换行符"\n"之前的内容,然后立即回到SCRIPT循环的顶端,即进入下一个SCRIPT循环。如果"D"删除后,模式空间中已经没有内容了,则SCRIPT循环自动退出进入下一个sed循环;如果模式空间还有剩余内容,则继续从头执行SCRIPT循环。也就是说,"D"命令后的命令不会被执行。

    "P"命令:输出模式空间中第一个换行符"\n"之前的内容。

    "N"、"D"和"P"命令作用非常大,它们是绝佳的组合命令,因为借助它们能实现"窗口滑动"技术,这对于复杂的文本行操作来说大有裨益。但显然,这不是本文的内容,在sed修炼系列(三):sed高级应用之实现窗口滑动技术中详细说明了这3个命令的功能。

    此处按照惯例,还是给出它们的大致循环结构:其中"N"命令的if判断和前文的"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
    (10).buffer空间数据交换命令"h"、"H"、"g"、"G"、"x"简单说明。

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

    绝大多数时候,sed仅依靠模式空间就能达到目的,但有些复杂的数据操作则只能借助保持空间来实现。之所以称之为保持空间,是因为它是暂存数据用的,除了仅有的这几个命令外,没有任何其他命令可以操作该空间,因此借助它能实现数据的持久性。

    保持空间的作用很大,它和模式空间之间的数据交换能实现很多看上去不能实现的功能,是实现sed高级功能所必须的,例如"窗口滑动"。同样,这不是本文的内容。所以只简单解释这几个命令的作用:

    "h"命令:将当前模式空间中的内容覆盖到保持空间。

    "H"命令:在保持空间的尾部加上一个换行符"\n",并将当前模式空间的内容追加到保持空间的尾部。

    "g"命令:将保持空间的内容覆盖到当前模式空间。

    "G"命令:在模式空间的尾部加上一个换行符"\n",并将当前保持空间的内容追加到模式空间的尾部。

    "x"命令:交换模式空间和保持空间的内容。

    注意,无论是交换、追加还是覆盖,原空间的内容都不会被删除。

    签到天数: 693 天

    [LV.9]元老将成

    发表于 2018-10-8 08:31:34 | 显示全部楼层
    不错的资料
    回复 支持 反对

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    关闭

    站长推荐上一条 /1 下一条

    返回顶部