在POSIX.2中将文本从最后一个标记获取到EOF


8

我有一个带有标记线的文本,例如:

aaa
---
bbb
---
ccc

我需要从最后一个标记(不包括)到EOF获得文本。在这种情况下

ccc

POSIX.2中是否有一种优雅的方式?现在,我用两个运行:先用nlgrep过去发生与各自的行号。然后,我提取行号并用于sed提取有问题的块。

文本段可能会很大,因此恐怕要使用一些添加文本的方法,例如将文本添加到缓冲区中,如果遇到标记,则会清空缓冲区,因此在EOF处,最后一个块位于缓冲。

Answers:


6

除非您的网段确实很大(例如:您实际上不能保留那么多的RAM,大概是因为这是一个控制大型文件系统的微型嵌入式系统),否则,单遍确实是更好的方法。不仅因为它会更快,而且最重要的是因为它使源成为一个流,从中读取或保存的所有数据都将丢失。尽管sed也可以做到,但这确实是awk的工作。

sed -n -e 's/^---$//' -e 't a' \
       -e 'H' -e '$g' -e '$s/^\n//' -e '$p' -e 'b' \
       -e ':a' -e 'h'              # you are not expected to understand this
awk '{if (/^---$/) {chunk=""}      # separator ==> start new chunk
      else {chunk=chunk $0 RS}}    # append line to chunk
     END {printf "%s", chunk}'     # print last chunk (without adding a newline)

如果必须使用两次通过方法,请确定最后一个分隔符的行偏移量并从中进行打印。或者确定字节偏移量并从中打印。

</input/file tail -n +$((1 + $(</input/file         # print the last N lines, where N=…
                               grep -n -e '---' |   # list separator line numbers
                               tail -n 1 |          # take the last one
                               cut -d ':' -f 1) ))  # retain only line number
</input/file tail -n +$(</input/file awk '/^---$/ {n=NR+1} END {print n}')
</input/file tail -c +$(</input/file LC_CTYPE=C awk '
    {pos+=length($0 RS)}        # pos contains the current byte offset in the file
    /^---$/ {last=pos}          # last contains the byte offset after the last separator
    END {print last+1}          # print characters from last (+1 because tail counts from 1)
')

附录:如果您拥有的不仅仅是POSIX,这是一个简单的单遍版本,它依赖于awk的通用扩展名,该扩展名允许记录分隔符RS为正则表达式(POSIX仅允许单个字符)。这不是完全正确的:如果文件以记录分隔符结尾,它将在最后一个记录分隔符之前打印块,而不是空记录。第二个版本使用RT避免了该缺陷,但是RT特定于GNU awk。

awk -vRS='(^|\n)---+($|\n)' 'END{printf $0}'
gawk -vRS='(^|\n)---+($|\n)' 'END{if (RT == "") printf $0}'

@Gilles:sed工作正常,但是我无法awk运行示例;它挂起...我得到第三个例子一个错误: cut -f ':' -t 1 ...斩:无效选项- “T”
Peter.O

@ fred.bear:我不知道这是怎么发生的—我测试了所有片段,但是不知何故弄乱了cut示例中的复制粘贴后编辑。我认为该awk示例没有错,您使用的是什么版本的awk,您的测试输入是什么?
吉尔(Gilles)“所以,别再邪恶了”,

...实际上,该awk版本正在运行..它在一个大文件上占用了长时间。.该sed版本在0.470s内处理了同一文件..我的测试数据非常加权...只有两个块,其中一个'-'从一百万行的末尾开始三行...
Peter.O 2011年

@Gilles ..((我认为我应该在凌晨3点停止测试。我不知何故将“两个通过” awk的所有三个都作为一个单元进行了测试:( ... ...我现在已经分别进行了每个测试,第二个测试速度非常快0.204秒 ...但是,第一个“两次通过” awk仅输出:“ (标准输入) ”(-l似乎是罪魁祸首)...什么都不输出...但是第二个“两次通过”是所有现有方法中最快的(POSIX或其他方法)...
Peter.O 2011年

@ fred.bear:已修复,已修复。对于这些简短的片段,我的质量检查不是很好-我通常从命令行复制,粘贴格式,然后发现错误,然后尝试内联而不是重新格式化。我很好奇,看看计数字符是否比计数行(第二对第三遍和第三遍方法)更有效。
吉尔(Gilles)'所以

3

两次通过策略似乎是正确的选择。我会使用sed而不是sed awk(1)。两遍可能看起来像这样:

$ LINE=`awk '/^---$/{n=NR}END{print n}' file`

获取行号。然后回显从该行号开始的所有文本:

$ awk "NR>$LINE" file

这应该不需要过多的缓冲。


然后可以将它们组合:awk -v line=$(awk '/^---$/{n=NR}END{print n}' file) 'NR>line' file
格伦·杰克曼

看到我一直在测试其他提交的内容,现在我还测试了摘录上方的“ glen jackman's”。它需要0.352秒(使用我的答案中提到的相同数据文件)...我开始收到消息,说awk可以比我原先认为的快(我认为sed差不多就可以了,但是它似乎是“课程马”的案例)...
Peter.O 2011年

看到所有这些基准脚本非常有趣。很好,弗雷德。
Mackie Messer

最快的解决方案使用tactail,它们实际上是向后读取输入文件的。现在,如果只有awk可以向后读取输入文件……
Mackie Messer

3
lnum=$(($(sed -n '/^---$/=' file | sed '$!d') +1)); sed -n "${lnum},$ p" file 

第一个sed输出“ ---”行的行号...
第二个sed从第一个sed的输出中提取最后一个数字...
在该数字上加1以获取“ ccc”块的开始...
第三个从“ ccc”块的开始到EOF的“ sed”输出

更新 (使用Gilles方法的改进信息)

好吧,我想知道格伦·杰克曼的 tac表现如何,所以我对这三个答案进行了时间测试(在撰写本文时)……测试文件每个都包含一百万行(各自的行号)。
所有的答案都达到了预期的效果...

这是时间。


吉尔斯 sed(单次通过)

# real    0m0.470s
# user    0m0.448s
# sys     0m0.020s

吉尔斯 awk(单次通过)

# very slow, but my data had a very large data block which awk needed to cache.

吉尔斯 “两次通过”(第一种方法)

# real    0m0.048s
# user    0m0.052s
# sys     0m0.008s

吉尔斯 “两次通过”(第二种方法)... 非常快

# real    0m0.204s
# user    0m0.196s
# sys     0m0.008s

吉尔斯 “两次通过”(第三种方法)

# real    0m0.774s
# user    0m0.688s
# sys     0m0.012s

吉尔 '徒劳无功(RT法)...... 非常快,但不是POSIX。

# real    0m0.221s
# user    0m0.200s
# sys     0m0.020s

glenn jackman ... 非常快,但不是POSIX。

# real    0m0.022s
# user    0m0.000s
# sys     0m0.036s

# real    0m0.464s
# user    0m0.432s
# sys     0m0.052s

麦基·梅塞尔(Mackie Messer)

# real    0m0.856s
# user    0m0.832s
# sys     0m0.028s

出于好奇,您测试了我的两次通过版本中的哪个版本,以及您使用了哪个版本的awk?
吉尔斯(Gilles)“所以,别再邪恶了”,

@Gilles:我使用了GNU Awk 3.1.6(在具有4 GB RAM的Ubuntu 10.04中)。所有测试的第一个“大块”中都有100万行,然后是“标记”,其后是2条“数据”行...处理一个100,000行的较小文件花了15.540秒,但是对于1,000,000行,我是现在运行它,到目前为止已经超过25分钟了。它正在使用100%的内核...现在将其杀死...这是一些更多的增量测试:lines = 100000(0m16.026s)-lines = 200000(2m29.990s)-lines = 300000(5m23。 393s)-行= 400000(11m9.938s)
Peter.O 2011年

糟糕,在上述评论中,我错过了您的“两次通过” awk参考。上面的细节是针对“单次通过” awk的... awk版本是正确的...我对您回答下的不同“两次通过”版本作了进一步的评论(修改了上面的时间结果)
Peter.O 2011年


0

你可以用 ed

ed -s infile <<\IN
.t.
1,?===?d
$d
,p
q
IN

工作原理:t复制当前(.)行-在ed开始时始终是最后一行(以防最后一行出现定界符),1,?===?d删除直到并包括前一个匹配项的所有行(ed仍在最后一行) ),然后$d删除(重复的)最后一行,,p打印文本缓冲区(替换w为在适当位置编辑文件),最后q退出ed


如果您知道输入中至少有一个定界符(并且不在乎是否也已打印出来),则

sed 'H;/===/h;$!d;x' infile

会更短。
工作原理:将所有行添加到H旧缓冲区,h遇到匹配项时覆盖旧缓冲区,当更改缓冲区(和自动打印)时,将d删除除最后一行以外的所有行。 $x

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.