将标准输入的最后一行添加到整个标准输入

将标准输入的最后一行添加到整个标准输入

考虑这个脚本:

tmpfile=$(mktemp)

cat <<EOS > "$tmpfile"
line 1
line 2
line 3
EOS

cat <(tail -1 "$tmpfile") "$tmpfile"

这有效并输出:

line 3
line 1
line 2
line 3

假设我们的输入源不是实际的文件,而是标准输入:

cat <<EOS | # what goes here now?
line 1
line 2
line 3
EOS

我们如何修改命令:

cat <(tail -1 "$tmpfile") "$tmpfile"

那么在不同的上下文中它仍然会产生相同的输出吗?

笔记:我正在使用的特定 Heredoc 以及 Heredoc 本身的使用只是说明性的。任何可接受的答案都应该假设它是通过 stdin 接收任意数据

答案1

尝试:

awk '{x=x $0 ORS}; END{printf "%s", $0 ORS x}'

例子

用我们的输入定义一个变量:

$ input="line 1
> line 2
> line 3"

运行我们的命令:

$ echo "$input" | awk '{x=x $0 ORS}; END{printf "%s", $0 ORS x}'
line 3
line 1
line 2
line 3

当然,我们也可以使用here-doc:

$ cat <<EOS | awk '{x=x $0 ORS}; END{printf "%s", $0 ORS x}'
line 1
line 2
line 3
EOS
line 3
line 1
line 2
line 3

怎么运行的

  • x=x $0 ORS

    这会将每行输入附加到变量中x

    在 awk 中,ORS输出记录分隔符。默认情况下,它是换行符。

  • END{printf "%s", $0 ORS x}

    在我们读入整个文件后,这将打印最后一行 ,$0然后是整个文件的内容x

由于这会将整个输入读取到内存中,因此它不适合大(例如千兆字节)输入。

答案2

如果 stdin 指向一个可查找文件(就像 bash(但不是所有其他 shell)的此处文档是用临时文件实现的),您可以获取尾部,然后在读取完整内容之前向后查找:

寻找zsh运算符在或shell 或 tcl/perl/python 等脚本语言中可用ksh93,但在bash.但bash如果您必须使用,您可以随时调用那些更高级的解释器bash

ksh93 -c 'tail -n1; cat <#((0))' <<...

或者

zsh -c 'zmodload zsh/system; tail -n1; sysseek 0; cat' <<...

现在,当 stdin 指向不可查找的文件(例如管道或套接字)时,这将不起作用。然后,唯一的选择是读取并存储(在内存中或临时文件中......)整个输入。

已经给出了一些在内存中存储的解决方案。

使用临时文件,使用zsh,您可以这样做:

seq 10 | zsh -c '{ cat =(sed \$w/dev/fd/3); } 3>&1'

如果在 Linux 上,使用bashzsh或任何使用此处文档临时文件的 shell,您实际上可以使用此处文档创建的临时文件来存储输出:

seq 10 | {
  chmod u+w /dev/fd/3 # only needed in bash5+
  cat > /dev/fd/3
  tail -n1 /dev/fd/3
  cat <&3
} 3<<EOF
EOF

答案3

cat <<EOS | sed -ne '1{h;d;}' -e 'H;${G;p;}'
line 1
line 2
line 3
EOS

将其转换为使用的内容的问题tailtail需要读取整个文件才能找到它的结尾。要在您的管道中使用它,您需要

  1. 向 提供文档的完整内容tail
  2. 提供它再次cat
  3. 以该顺序。

棘手的一点不是复制文档的内容(这样做),而是在输出文档的其余部分之前tee获取输出,而不使用中间临时文件。tail

使用sed(或awk, 作为约翰 1024 确实)通过将数据存储在内存中,摆脱了数据的双重解析和排序问题。

我建议的解决方案sed

  1. 1{h;d;},将第一行按原样存储在保留空间中,然后跳到下一行。
  2. H,将彼此的行附加到带有嵌入换行符的保留空间。
  3. ${G;p;},将保留空间附加到带有嵌入换行符的最后一行并打印结果数据。

这是 John1024 的解决方案的字面翻译sed,但需要注意的是 POSIX 标准仅保证保留空间至少为 8192 字节(8 KiB;但它推荐该缓冲区是根据需要动态分配和扩展的,GNUsed和 BSD都sed在这样做)。


如果您允许自己使用命名管道:

mkfifo mypipe
cat <<EOS | tee mypipe | cat <( tail -n 1 mypipe ) -
line 1
line 2
line 3
EOS
rm -f mypipe

这用于tee将数据向下发送mypipe并同时发送到cat。该cat实用程序将首先读取 的输出tail(从 读取mypipetee正在写入),然后附加直接来自 的文档副本tee

但这有一个严重的缺陷,如果该文档是太大了(大于管道的缓冲区大小),tee写入mypipecat在等待(未命名)管道清空时会阻塞。在cat读取之前它不会被清空。在完成cat之前不会阅读它。tail并且在完成tail之前不会完成。tee这是典型的僵局情况。

变化

tee >( tail -n 1 >mypipe ) | cat mypipe -

有同样的问题。

答案4

如果你不关心顺序的话。那么这就会起作用cat lines | tee >(tail -1)。正如其他人所说。您需要读取文件两次,或缓冲整个文件,以按照您要求的顺序执行操作。

相关内容