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


9

考虑以下脚本:

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

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

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

我们如何修改命令:

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

这样在不同的上下文中它仍会产生相同的输出?

注意:我要介绍的特定Heredoc以及Heredoc本身的使用仅是说明性的。任何可接受的答案都应假定它正在通过stdin接收任意数据


1
stdin 始终是一个“实际文件”(fifo / socket / etc也是一个文件;并非所有文件都可搜索)。您的问题的答案是琐碎的“使用临时文件”或某种使整个文件加载到内存中的恐怖。“如何在不将数据存储到任何地方的情况下从流中检索旧数据?” 没有一个好的答案。
mosvy

1
@mosvy如果您要添加,那是一个完全可以接受的答案。
乔纳

2
正如乔纳所说,@ mosvy应该将答案张贴在答案框中。我知道目前无法阅读任何网站,但请忽略缓慢滴落在您的视野中的红色,并使用下部的文本区域。
wizzwizz4

Answers:


7

尝试:

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

由于这会将整个输入读入内存,因此不适用于大型(例如,千兆字节)输入。


谢谢约翰。因此,是否不可能以类似于OP中命名文件示例的方式执行此操作?我正在想象以某种方式复制标准输入...有点类似tee,但是对于标准输入和文件,我们会将同一个标准输入传递到两个不同的进程替换中。或任何大致等同于此的东西?
乔纳

5

如果stdin指向可搜索的文件(例如bash的文件(但不是所有其他shell的文件),此处是通过临时文件实现的文件),则可以获取尾部,然后在阅读全部内容之前先进行查找:

seek运算符可在zshksh93Shell中使用,也可以在脚本语言(如tcl / perl / python)中使用,但不能在中使用bash。但是,bash如果必须使用,您总是可以从中调用那些更高级的口译员bash

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

要么

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

现在,当stdin指向管道或套接字之类的不可搜索文件时,它将不起作用。然后,唯一的选择是读取和存储(在内存中或在临时文件中)整个输入。

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

通过使用tempfile,zsh可以使用:

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

如果在Linux上,bash或带有zsh将临时文件用于此处文档的任何外壳程序,则实际上可以使用由此处文档创建的临时文件来存储输出:

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

4
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(或awkJohn1024一样)通过将数据存储在内存中来消除数据的双重解析和排序问题。

sed我建议的解决方案是

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

这完全是John1024解决方案的字面翻译sed,但需要注意的是POSIX标准仅保证保留空间至少为8192字节(8 KiB;但是建议根据需要动态分配和扩展此缓冲区,两者均由GNU sed和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(其从读取mypipe,其tee被写入),然后追加直接来自的文档的拷贝tee

但是,这有一个严重的缺陷,即如果文档太大(大于管道的缓冲区大小),则在等待(未命名)管道为空时,将tee写入mypipecat阻塞。除非将其cat读取,否则不会清空。在完成cat之前不会从中读取tail。并且tail直到完成才tee完成。这是典型的僵局情况。

变化

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

有同样的问题。


2
sed如果输入只有一个行(也许一个不工作sed '1h;1!H;$!d;G')。还要注意,一些sed实现对它们的模式和保持空间的大小有较低的限制。
斯特凡Chazelas

命名管道解决方案是我一直在寻找的那种东西。限制是可耻的。我理解您的解释,除了“发球要等到发球结束后才能完成”,您能否详细说明为什么会这样?
乔纳

2

pee在一个命令行实用程序集合中有一个工具,该工具通常以“ moreutils”的名称打包(或者可以从其主页获取)。

如果您可以在系统上使用它,则示例的等效内容将如下所示:

cat <<EOS | pee 'tail -1' cat 
line 1
line 2
line 3
EOS

运行命令pee的顺序很重要,因为它们按提供的顺序执行。


1

尝试:

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

由于整个内容都是文字数据(“此处是文档”),并且它与所需输出之间的差异很小,因此只需在此处按摩该文字数据以匹配输出即可。

现在假设line 3来自某个地方,并存储在一个名为的变量中lastline

cat <<EOS # | what goes here now? Nothing!
$lastline
line 1
line 2
$lastline
EOS

在本文中,我们可以通过替换变量来生成文本。不仅如此,我们还可以使用命令替换来计算文本:

cat <<EOS
this is template text
here we have a hex conversion: $(printf "%x" 42)
EOS

我们可以插值多行:

cat <<EOS
multi line
preamble
$(for x in 3 1 2 3; do echo line $x ; done)
epilog
EOS

通常,避免对此处的doc模板进行文本处理;尝试使用内插代码生成它。


1
老实说,我不知道这是不是在开玩笑。将cat <<EOS...在OP只是为了“catting一个任意文件,”使特定后,问题清楚的例子替身。这对您来说真的不明显吗,还是您只是认为按字面意义解释这个问题会很聪明?
乔纳

@Jonah这个问题清楚地表明“例如,我们的输入源不是实际的文件,而是stdin:”。没有关于“任意文件”的内容;这是关于这里的文档。这里的文档不是任意的。它不是程序的输入,而是程序员选择的一部分语法。
卡兹(Kaz)

1
我认为上下文和现有答案清楚地表明了这种情况,仅是因为为了使您的解释正确无误,您必须假设我本人或其他答复者都没有意识到可以复制并粘贴内容。代码行。不过,我将编辑问题以使其明确。
乔纳

1
卡兹(Kaz),谢谢您的答复,但请注意,即使您进行了编辑,也仍然没有回答这个问题。您正在通过管道接收任意多行输入。您不知道它将是什么。您的任务是输出最后一行输入,然后输出整个输入。
乔纳

1
哈兹(Kaz),输入仅作为示例。包括我自己在内的大多数人发现,提供真实输入和预期输出的示例而不是仅是抽象问题会有所帮助。您是唯一对此感到困惑的人。
乔纳

0

如果您不在乎订单。然后这将起作用cat lines | tee >(tail -1)。正如其他人所说。您需要读取文件两次,或缓冲整个文件,然后按要求的顺序进行操作。

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.