这个来自todo.sh的sed表达式是做什么的?

时间:2011-05-08 20:42:25

标签: regex shell sed

sed表达式:G; s/\n/&&/; /^\([ ~-]*\n\).*\n\1/d; s/\n//; h; P做了什么?它究竟匹配什么以及它与之匹配?

来自todo.sh。在上下文中:

archive()
{
    #defragment blank lines
    sed -i.bak -e '/./!d' "$TODO_FILE"                     ## delete all empty lines
    [ $TODOTXT_VERBOSE -gt 0 ] && grep "^x " "$TODO_FILE"  ## if verbose mode print completed tasks..
    grep "^x " "$TODO_FILE" >> "$DONE_FILE"                ## append completed tasks to $DONE_FILE
    sed -i.bak '/^x /d' "$TODO_FILE"                       ## delete completed tasks
    cp "$TODO_FILE" "$TMP_FILE"


    sed -n 'G; s/\n/&&/; /^\([ ~-]*\n\).*\n\1/d; s/\n//; h; P' "$TMP_FILE" > "$TODO_FILE"


    ## G;                       Add a newline
    ## s/\n/&&/;                Substitute newline with && (two newlines?)
    ## /^\([ ~-]*\n\).*\n\1/d;  Delete duplicate lines???
    ## s/\n//                   Remove newlines
    ## h                        Hold: copy pattern space to buffer
    ## P                        Print first line of pattern space
    if [ $TODOTXT_VERBOSE -gt 0 ]; then
    echo "TODO: $TODO_FILE archived."
    fi
}

2 个答案:

答案 0 :(得分:7)

好的,你已经有了一些故事。回想一下,为每个输入行执行sed表达式。因此,开头的G会将保留空间的内容附加到当前行(中间有换行符)。保留空间的内容最初为空,但在每个输入周期结束时由h命令扩展。

然后s/\n/&&/仅复制第一个换行符,即当前行与从保留空间抓取的内容之间的换行符。这是为下一个命令做准备。如果当前行与保留空间中的一行相同,则/^\([ -~]*\n\).*\n\1/确实匹配:
^\([ -~]*\n\)匹配缓冲区开头的一行1 请注意,仅当该行仅包含可打印的ASCII字符时才匹配。
如果您的系统支持区域设置,^\([[:print:]]*\n\)会更好。
.*\n匹配至少一个后续行
\1匹配与第一行相同的行 前一个s命令添加的额外换行符可以处理重复项是保留空间的第一行的情况。 \n\1的要点是在一行的开头“锚定”副本,否则bar将被视为foobar的副本。如果当前行是重复行,则d命令将丢弃它并执行分支到下一行。

如果当前行不重复,s/\n//会丢弃该额外换行符(同样,没有g修饰符,因此只删除第一个换行符)。然后h命令导致保留空间包含之前包含的内容,前面加上当前行。最后P打印当前输入行。

好的,现在保留空间包含什么?它开始为空,然后将每个连续的行前置,除非它是重复的。因此,保持空间包含输入行,按相反顺序减去重复项。

¹呃,我不知道你是怎么做到的,但那应该是[ -~],而不是[ ~-]这没有任何意义。


如果你有一套符合POSIX标准的工具(Single Unix v2足够好),这是另一种方法。

<"$TMP_FILE" \
nl -s: |              # add line numbers
sort -t: -k2 -u |     # sort, ignoring the line numbers, and remove duplicates
sort -t: -k1 -n |     # sort by line number
cut -d: -f2-          # cut out the line numbers

哦,你想要清晰简洁地做到这一点?只需使用awk。

<"$TMP_FILE" awk '!seen[$0] {++seen[$0]; print}'

如果还没有看到当前行,请将其标记为已显示,然后将其打印出来。

请注意,与sed方法一样,awk方法实际上将整个文件存储在内存中。上面使用sort的方法的优点是,只有sort一次需要保留多行输入,并且它是为此而设计的。

当然,如果你不关心线的顺序,它就像sort -u一样简单。

答案 1 :(得分:2)

在吉尔斯提出他的出色答案之后,我找到Famous Sed One-Liners Explained,其中包括这个确切的sed表达;在此添加以供参考:

  

<强> 70。从文件中删除重复的非连续行。

sed -n 'G; s/\n/&&/; /^\([ -~]*\n\).*\n\1/d; s/\n//; h; P'
  

这是一个非常棘手的单线。它   将唯一行存储在保持缓冲区中   并在每个新读取的行,测试是否   新线已经处于暂停状态   缓冲。如果是,则新行是   清除。如果不是,那就保存了   在保持缓冲区中用于将来的测试和   打印。

     

更详细的描述 - 每一个   这条单线附加线   保持缓冲区的内容到模式   空格“G”命令。附后   字符串与...分开   模式空间的现有内容   “\ n”字符。接下来,替换   取而代之的是“\ n”   字符有两个“\ n \ n”。该   替换命令“s / \ n /&amp;&amp; /”   那。 “&amp;”意味着匹配   串。由于匹配的字符串是   “\ n”,然后是“&amp;&amp;”是它的两个副本   “\ n \ n”。接下来,测试“/ ^([    - 〜] \ n)。 \ n \ 1 /“完成以查看组捕获组1的内容是否为   重复。捕获组1全部   从空间“”到“〜”的人物   (包括所有可打印的字符)。   “[ - 〜] ”与之匹配。更换   一个“\ n”有两个是关键的想法   这里。因为“([ - 〜] \ n)”是贪婪的   (尽可能匹配),   双换行确保它   匹配尽可能少的文本。如果   测试是成功的,目前   已经看到输入线和“d”   清除整个模式空间和   从中启动脚本执行   开始。如果测试不是   成功,加倍“\ n \ n”得到   替换为单个“\ n”by   “s / \ n //”命令。然后“h”复制   整个字符串保存缓冲区,“P”   打印新行。