views:

718

answers:

4

I want to execute a command, have the output of that command get gzip'd on the fly, and also echo/tee out the output of that command.

i.e., something like:

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

Except when the line executes, I want to see this on standard out:

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

As pointed out in the comments, /dev/stdout might work better than /dev/tty in some circumstances.

Paul Tomblin
_A_w_e_s_o_m_e_. Thank you.
Ross Rogers
What's /dev/tty doing there? The original question wanted the output on standard output, not necessarily on the terminal.
Gareth Rees
/dev/tty is a synonym for the current terminal. The questioner used "standard out" in the customary manner to mean the the current terminal, rather than a more strict definition of the term.
Paul Tomblin
If it were indeed customary to use "standard output" to mean "current terminal", then that would be a custom likely to lead to much confusion! For this question, bash has /dev/stdout.
Gareth Rees
Under what circumstances would /dev/tty not produce the desired outcome where /dev/stdout would?
Paul Tomblin
When stdout goes to a file and not to the terminal.
Gareth Rees
This is just plain not what was asked -- what was asked for was a shell pipeline that would send the output to its `stdout` AND gzip it to a file. (What if I wanted to send the output on to yet another program -- say, **less(1)**.
SamB
@SamB, what part of your ass did you pull that requirement out of? The person who asked the question was satisfied with my answer.
Paul Tomblin
+12  A: 

Have a nice cup of tee!

The tee command copies standard input to standard output and also to any files given as arguments. This is useful when you want not only to send some data down a pipe, but also to save a copy

As I'm having a slow afternoon, here's some gloriously illustrative ascii-art...

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

As greyfade demonstrates in another answer the 'file' need not be a regular file, but could be FIFO letting you pipe that tee'd output into a third command.

           +-----+                   +---+                  +-----+  
stdin ->   |cmd 1|    -> stdout ->   |tee|   ->  stdout  -> |cmd 2|
           +-----+                   +---+                  +-----+
                                       |
                                       v
                                     FIFO
                                       |
                                       v
                                    +-----+
                                    |cmd 3|
                                    +-----+
Paul Dixon
But I want to gzip the intermediary file on the fly. Is that possible only using tee?
Ross Rogers
yes, the other paul wrote a nice succinct answer while I messed about with an ascii art diagram :)
Paul Dixon
But +1 for the excellent explanation.
Paul Tomblin
:-PI'm familiar with tee'ing to a file like you're describing. I just don't know how to gzip that file. if i do "echo foo | tee bar.log", I don't know how to make tee gzip 'bar.log', aside from the solution Paul Tomblin posted.
Ross Rogers
+1 for the ASCII art.
Chris Lutz
man, stackoverflow is awesome. I didn't even have to harass the office linux guru.Thanks all.
Ross Rogers
greyfade's excellent answer shows how you can do what you like with the tee'd output.
Paul Dixon
Yes, grayfade's answer does precisely what this one suggests, but with a very succinct syntax. This ones `cmd 3` is written `>(cmd 3)` there.
SamB
+17  A: 

Another way (assuming a shell like bash or zsh):

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

The admittedly strange >() syntax basically does the following:

  • Create new FIFO (usually something in /tmp/)
  • Execute command inside () and bind the FIFO to stdin on that subcommand
  • Return FIFO filename to command line.

What tee ends up seeing, then, is something like:

tee /tmp/arjhaiX4

All gzip sees is its standard input.

For Bash, see man bash for details. It's in the section on redirection. For Zsh, see man zshexpn under the heading "Process Substitution."

As far as I can tell, the Korn Shell, variants of the classic Bourne Shell (including ash and dash), and the C Shell don't support this syntax.

greyfade
Cool, I'm learning something myself here. Can you elaborate on what is happening there?
Paul Dixon
I'll edit my post.
greyfade
if I read it right, instead of given a file to tee, you've got it sending the copy as input to bracketed expression which writes the gzip output to another file. The uncompressed data leaves tee as normal on stdout
Paul Dixon
thanks for the edit, appreciate the extra info! Never knew about that FIFO syntax.
Paul Dixon
It came as a shock to me when I first learned it. But it lets you get away with very complex redirections, with several programs operating on the same input simultaneously. Of course, you can also do `mkfifo` by hand and run all of these commands in different consoles if you need to.
greyfade
It also works the other way with <()
greyfade
I've been doing Unix for 20 years, and I haven't wrapped my head around this new syntax yet.
Paul Tomblin
+2  A: 

Just to post a way that doesn't involve touching disk:

echo "hey hey, we're the monkees" | (exec 1>&3 && tee /proc/self/fd/3 | gzip --stdout > my_log.gz)
Joshua
What, you mean /tmp is on a disk ?!?!?!?
SamB
From a security perspective, it is.
Joshua