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

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

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

aaa
---
bbb
---
ccc

我需要获取从最后一个标记(不包括)到 EOF 的文本。在这种情况下,它将是

ccc

POSIX.2 中有一种优雅的方式吗?现在我使用两次运行:第一次运行nlgrep最后一次运行分别使用相应的行号。然后我提取行号并用于sed提取有问题的块。

文本段可能非常大,所以我担心使用一些文本添加方法,就像我们将文本添加到缓冲区中一样,如果遇到标记,我们会清空缓冲区,这样在 EOF 处,我们就有了最后一个块缓冲。

答案1

除非您的段真的很大(例如:您真的无法腾出那么多 RAM,大概是因为这是一个控制大型文件系统的小型嵌入式系统),否则单遍确实是更好的方法。不仅因为它会更快,而且最重要的是因为它允许源成为流,从中读取但未保存的任何数据都会丢失。这确实是 awk 的工作,尽管 sed 也可以做到。

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}'

答案2

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

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

更新 (修改了吉尔斯方法的信息)

好吧,我想知道如何格伦·杰克曼的 tac会执行,所以我对三个答案进行了时间测试(在撰写本文时)...每个测试文件包含 100 万行(各自的行号)。
所有答案都符合预期......

这里有时间..


吉尔斯 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

吉尔斯'gawk'(RT 方法)...非常快,但不是 POSIX。

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

格伦·杰克曼...非常快,但不是 POSIX。

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

弗雷德熊

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

麦基·梅塞尔

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

答案3

两次通过策略似乎是正确的事情。我会使用 sed 而不是awk(1).两个通道可能如下所示:

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

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

$ awk "NR>$LINE" file

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

答案4

你可以只使用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覆盖旧缓冲区,当更改缓冲区(和自动打印) 时删除除 la t之外的所有行。hd$x

相关内容