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
}
Run Code Online (Sandbox Code Playgroud)
好的,你已经有了一些故事.回想一下,为每个输入行执行sed表达式.因此,G在开头将保留空间的内容附加到当前行(中间有换行符).保持空间的内容最初为空,但h在每个输入周期结束时由命令扩展.
然后仅s/\n/&&/复制第一个换行符,即当前行与从保留空间中抓取的换行符之间的换行符.这是为下一个命令做准备./^\([ -~]*\n\).*\n\1/如果当前行与保留空间
^\([ -~]*\n\)中的行相同,则确实匹配:匹配缓冲区开头的行¹
请注意,仅当行仅包含可打印的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
Run Code Online (Sandbox Code Playgroud)
哦,你想要清晰简洁地做到这一点?只需使用awk.
<"$TMP_FILE" awk '!seen[$0] {++seen[$0]; print}'
Run Code Online (Sandbox Code Playgroud)
如果尚未显示当前行,请将其标记为已显示,然后将其打印出来.
请注意,与sed方法一样,awk方法实质上将整个文件存储在内存中.上面使用的方法sort具有以下优点:一次只sort需要保留多行输入,并且它是为此而设计的.
当然,如果你不关心线的顺序,那就简单了sort -u.