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
}
答案 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” 打印新行。