January 14, 2009

ptee

Have you ever wanted to use the UNIX shell utility tee, but instead of simply dumping to a file, invoke a series of subcommands that would run inside the pipeline? If so, I give you ptee, now available in version 1.0.

ptee extends the idea of tee by allowing not just filenames, but a whole shell expression (or command). For instance: ... | ptee "grep foo | zcat > outfile" | ... The expression in quotes is passed to a new instance of the active shell (expressed by $SHELL), and the standard input to ptee is duplicated, both to normal standard output and to this new sub-shell (and any number of commands given to ptee). In effect, the sub-shell is running in parallel with the normal shell pipeline. The simplest case of running ptee is with no arguments, where it acts as an expensive version of the “|” operator.

ptee accepts any realistic number of command line arguments, which are all launched and will receive a copy of the pipeline data.

There are of course some restrictions:

  • The sub-shell standard output is thrown away; its simply not generically possible to try re-weaving it with the normal data flow.
  • Also, ptee does not buffer large amounts of data internally, meaning the shell pipeline will run at the speed of the slowest sub-shell (if the slowest command is stalled, the whole pipeline will be stalled). ptee is however tolerant of sub-shells aborting early, and will continue passing data to any remaining outputs.
  • Sub-shell exit codes are ignored and not propagated to the main ptee exit code.

ptee will compile on most POSIX like systems; I have tested both Linux (Ubuntu 8.04) and FreeBSD (7.1). Feel free to grab the download here (available as a .tar.gz).

The very simple C code is made available under an MIT license. It launches very quickly, and is very light weight, perfect for shell scripting. I’d like to hear ideas you have for using ptee in your normal shell scripting activities. I know I am going to be cleaning up some ugly git post-receive hooks I have created in the past. How about you?

© 2020 Yann Ramin