如何将标准输入压缩成文件并同时将标准输入输出到标准输出?

36

我想要执行一个命令,让该命令的输出被动态压缩为gzip格式,并同时打印/保存该命令的输出。

例如:

echo "hey hey, we're the monkees" | gzip --stdout > my_log.gz

除了执行这一行代码之外,我希望在标准输出中看到这个:

hey hey, we're the monkees
4个回答

65

另一种方法(假设使用像bashzsh这样的shell):

echo "hey hey, we're the monkees" | tee >(gzip --stdout > my_log.gz)

这个有点奇怪的 >() 语法基本上是做以下几件事情:

  • 创建一个新的 FIFO(通常在 /tmp/ 中)
  • () 中执行命令,并将该 FIFO 绑定到该子命令的 stdin 上
  • 将 FIFO 文件名返回到命令行。

然后,tee 最终看到的是这样的东西:

tee /tmp/arjhaiX4

所有gzip看到的都是它的标准输入。

对于Bash,请参见man bash了解详情。它在重定向部分中。对于Zsh,请参见man zshexpn,在“进程替换”标题下。

据我所知,Korn Shell、经典Bourne Shell的变体(包括ash和dash)以及C Shell都不支持此语法。


很酷,我自己正在学习。你能详细说明一下那里发生了什么吗? - Paul Dixon
如果我理解正确的话,你不是将文件给予tee,而是将其作为输入发送到括号表达式,该表达式将gzip输出写入另一个文件。未压缩的数据像往常一样从stdout离开tee。 - Paul Dixon
1
当我第一次学习它时,我感到震惊。但是它可以让你逃避非常复杂的重定向,同时有几个程序在同一输入上运行。当然,如果需要,您也可以手动执行mkfifo并在不同的控制台中运行所有这些命令。 - greyfade
2
我正在使用这个来记录日志。我发现gzip缓冲区很多,当我按ctrl-c时会丢失所有内容。通过>(trap''INT; gzip --stdout > my_log.gz)忽略SIGINT似乎可以保存它。 - jozxyqk
@jozxyqk 不错!确实看起来gzip在写入之前会缓冲16KB的压缩数据。 - Francois
显示剩余2条评论

46
echo "hey hey, we're the monkees" | tee /dev/tty | gzip --stdout > my_log.gz

如评论所指出的,/dev/stdout在某些情况下可能比/dev/tty更有效。


3
/dev/tty在这里是做什么的?原来的问题想要在标准输出上获得输出,不一定要在终端上。 - Gareth Rees
4
/dev/tty 是当前终端的同义词。问题提问者通常使用“standard out”表示当前终端,而不是更严格定义该术语的含义。 - Paul Tomblin
3
如果真的惯用“标准输出”表示“当前终端”,那么这个习惯可能会导致很多混淆!对于这个问题,bash有/dev/stdout。 - Gareth Rees
6
当标准输出被重定向到文件而不是终端时。 - Gareth Rees
1
@GarethRees,你说得没错,但如果你使用python -m,实用性就胜过纯粹性。你可以在回答中指出这一点来服务公众,而不是编辑问题,后者会隐藏惯例是普遍的事实。 - n611x007
显示剩余4条评论

20

来一杯美味的tee吧!

tee命令将标准输入复制到标准输出,并复制到提供给它的任何文件中。当你不仅想要将数据传送到管道中,还想要保存一份副本时,这非常有用。

由于我今天下午比较闲,就画了一些精美的ASCII艺术...

           +-----+                   +---+                  +-----+  
stdin ->   |cmd 1|    -> stdout ->   |tee|   ->  stdout  -> |cmd 2|
           +-----+                   +---+                  +-----+
                                       |
                                       v
                                     file

正如@greyfade在另一个回答中所示,'file'不必是普通文件,而可以是FIFO,让您将Tee的输出导入到第三个命令中。

           +-----+                   +---+                  +-----+  
stdin ->   |cmd 1|    -> stdout ->   |tee|   ->  stdout  -> |cmd 2|
           +-----+                   +---+                  +-----+
                                       |
                                       v
                                     FIFO
                                       |
                                       v
                                    +-----+
                                    |cmd 3|
                                    +-----+

但是我想要实时压缩中间文件。 只使用 tee 可以实现吗? - Ross Rogers
是的,另一个保罗写了一个简洁明了的答案,而我则在玩ASCII艺术图 :) - Paul Dixon
我熟悉像你描述的将输出重定向到文件。但是我不知道如何对该文件进行gzip压缩。如果我执行"echo foo | tee bar.log",除了Paul Tomblin发布的解决方案外,我不知道如何让tee gzip 'bar.log'。 - Ross Rogers
哇,StackOverflow真是太棒了。我甚至不用去骚扰办公室的Linux专家。谢谢大家。 - Ross Rogers
2
greyfade的出色回答展示了如何使用tee命令输出任意内容。 - Paul Dixon
是的,grayfade的答案确切地做到了这个建议,但使用非常简洁的语法。这里的cmd 3写作>(cmd 3) - SamB

6

这里提供一种不需要操作磁盘的方法:

echo "hey hey, we're the monkees" | (exec 1>&3 && tee /proc/self/fd/3 | gzip --stdout > my_log.gz)

什么?你的意思是/tmp在磁盘上?!?!?! - SamB
2
从安全角度来看,这是必要的。 - Joshua

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接