考虑这个脚本:
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 上,使用bash
或zsh
或任何使用此处文档临时文件的 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
将其转换为使用的内容的问题tail
是tail
需要读取整个文件才能找到它的结尾。要在您的管道中使用它,您需要
- 向 提供文档的完整内容
tail
。 - 提供它再次到
cat
。 - 以该顺序。
棘手的一点不是复制文档的内容(这样做),而是在输出文档的其余部分之前tee
获取输出,而不使用中间临时文件。tail
使用sed
(或awk
, 作为约翰 1024 确实)通过将数据存储在内存中,摆脱了数据的双重解析和排序问题。
我建议的解决方案sed
是
1{h;d;}
,将第一行按原样存储在保留空间中,然后跳到下一行。H
,将彼此的行附加到带有嵌入换行符的保留空间。${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
(从 读取mypipe
,tee
正在写入),然后附加直接来自 的文档副本tee
。
但这有一个严重的缺陷,如果该文档是太大了(大于管道的缓冲区大小),tee
写入mypipe
并cat
在等待(未命名)管道清空时会阻塞。在cat
读取之前它不会被清空。在完成cat
之前不会阅读它。tail
并且在完成tail
之前不会完成。tee
这是典型的僵局情况。
变化
tee >( tail -n 1 >mypipe ) | cat mypipe -
有同样的问题。
答案4
如果你不关心顺序的话。那么这就会起作用cat lines | tee >(tail -1)
。正如其他人所说。您需要读取文件两次,或缓冲整个文件,以按照您要求的顺序执行操作。