302

I would like to pipe standard output of a program while keeping it on screen.

With a simple example (echo use here is just for illustration purpose) :

$ echo 'ee' | foo
ee <- the output I would like to see

I know tee could copy stdout to file but that's not what I want.
$ echo 'ee' | tee output.txt | foo

I tried
$ echo 'ee' | tee /dev/stdout | foo but it does not work since tee output to /dev/stdout is piped to foo

Vadim Kotov
  • 8,084
  • 8
  • 48
  • 62
gentooboontoo
  • 4,653
  • 3
  • 20
  • 15
  • 9
    Note that `echo 'ee' | tee /dev/stderr` works though, so if your _"on screen"_ requirement is satisfied by stderr too, that'll do. – nh2 Mar 26 '16 at 23:49

5 Answers5

419

Here is a solution that works at on any Unix / Linux implementation, assuming it cares to follow the POSIX standard. It works on some non Unix environments like cygwin too.

echo 'ee' | tee /dev/tty | foo

Reference: The Open Group Base Specifications Issue 7 IEEE Std 1003.1, 2013 Edition, §10.1:

/dev/tty

Associated with the process group of that process, if any. It is useful for programs or shell procedures that wish to be sure of writing messages to or reading data from the terminal no matter how output has been redirected. It can also be used for applications that demand the name of a file for output, when typed output is desired and it is tiresome to find out what terminal is currently in use. In each process, a synonym for the controlling terminal

Some environments like Google Colab have been reported not to implement /dev/tty while still having their tty command returning a usable device. Here is a workaround:

tty=$(tty)
echo 'ee' | tee $tty | foo

or with an ancient Bourne shell:

tty=`tty`
echo 'ee' | tee $tty | foo
jlliagre
  • 29,783
  • 6
  • 61
  • 72
  • this will work interactively, for processes running with tty (cron job, for instance) you want to use one of the tricks mentioned in the other answers. – Asya Kamsky Nov 26 '14 at 02:38
  • 1
    @AsyaKamsky The question is about processes which are outputting on the screen. This rules out cron jobs which are detached from any screen in the first place. – jlliagre Nov 26 '14 at 06:08
  • 7
    @static_rtti Why are you ignoring year after year my replies to your comment? – jlliagre Dec 31 '15 at 10:41
  • No /dev/tty on my FreeBSD 9.1 box. Any clues for me? (I wasn't able to solve it using the manual, https://www.freebsd.org/doc/handbook/term.html). The process substitution answer works but I'm curious if this one can be made to work as well. – Paul Bissex Jan 25 '16 at 13:51
  • 1
    @PaulBissex `/dev/tty` is a mandatory Unix device. Are you running in a BSD jail? – jlliagre Jan 27 '16 at 22:16
  • @jillagre Yes, this is a VPS. – Paul Bissex Jan 28 '16 at 20:32
  • 1
    @PaulBissex That's either an implementation or a configuration bug. Is /dev mounted? What shows "ls -l /dev/tty /dev/tty* /dev"? See https://lists.freebsd.org/pipermail/freebsd-bugs/2012-November/050870.html https://forums.freebsd.org/threads/solution-for-lack-of-tty-giving-ssh-problems-when-using-je.20026/ – jlliagre Jan 28 '16 at 21:09
  • @jlliagre - /dev is mounted, yes. It contains http://dpaste.com/06CME89. There is no /dev/tty. AFAIK this is the standard jail setup at johncompanies.com (my host). – Paul Bissex Jan 29 '16 at 21:06
  • 1
    And you can cascade `tee` like this: `cat some.log | tee /dev/tty | tee -a other.log | grep -i 'foo' >> foo.log` to 1) get it all to the console, 2) get it all appended to another file, 3) get the `foo` lines to a different file. – Jesse Chisholm Mar 09 '17 at 16:05
  • 1
    Google Colab doesn't have `/dev/tty`, but the output of `tty` is usable. – Tom Hale Jan 12 '19 at 07:05
  • @TomHale Thanks, answer updated to include this case. – jlliagre Jan 13 '19 at 14:40
  • 1
    Shouldn't the colab command have `$(/usr/bin/tty)` instead of `$(/dev/tty)`? – SpoonMeiser Jan 14 '19 at 10:37
  • @SpoonMeiser Sure it does. Fixed. I left a pathless command as `tty` is not necessarily in `/usr/bin`. – jlliagre Jan 14 '19 at 10:46
  • When `tty` is in a pipe, it returns `"not a tty\n"`, since its standard input is connected to a pipe, not a terminal. You can save the output of `tty` in a variable first, like `tty=$(tty);echo ee|tee $tty|foo`. – Chris Feb 05 '19 at 14:16
  • @Chris Did you observe this behavior? That would be an unexpected one. Commands are expanded when the shell parses the whole line, before the pipeline is executed, not later. – jlliagre Feb 05 '19 at 14:23
  • 1
    @jlliagre Yes, I observed it, on Centos 7 as well as Cygwin. Commands in command substitution inherit stdin from the parent: for instance in `echo 'ls'|$(cat)`, `cat` consumes the stdin, `$(cat)` expands to `ls`, and the end result of this command is equivalent to `ls`. – Chris Feb 05 '19 at 15:11
  • @Chris Oh, I should have checked. You are absolutely correct. Answer fixed. – jlliagre Feb 05 '19 at 15:25
  • I am loosing the color when I am doing `echo 123 | grep 3 | tee /dev/tty` compared to `echo 123 | grep 3`. How can I get the same effect but with color? – vstepaniuk Oct 06 '19 at 19:19
  • This is not working on a Github CI runner for some reason. No `/dev/tty` and using `$(tty)` results in no output. Using `tee /dev/stderr` as suggested below results in only stderr appearing without any stdout, despite having redirected it to stdout: `python test.py 2>&1 | tee /dev/stderr | grep...`. Very weird... – Sam Bull Dec 31 '22 at 15:59
  • @SamBull Maybe related to https://github.com/actions/runner/issues/241 – jlliagre Dec 31 '22 at 16:51
  • Note that if the last command also produces output, then it may be randomly interspersed with the output of `tee`. – Joald Apr 03 '23 at 16:33
81

Another thing to try is:

echo 'ee' | tee >(foo)

The >(foo) is a process substitution.

Edit:
To make a bit clearer, (.) here start a new child process to the current terminal, where the output is being redirected to.

echo ee | tee >(wc | grep 1)
#              ^^^^^^^^^^^^^^ => child process

Except that any variable declarations/changes in child process do not reflect in the parent, there is very few of concern with regard to running commands in a child process.

bmk
  • 13,849
  • 5
  • 37
  • 46
  • 1
    what if I want to pipe the output of foo to another bar? – Jack Tang Oct 10 '14 at 12:27
  • 3
    @JackTang - I think any further piping on the output of `foo` will have to be part of the process substitution. Here's an example: `echo 'ee' | tee file.txt >(wc -c | tr -d ' ')` – Nick Chammas Oct 16 '14 at 03:31
  • 1
    This was the solution for me on FreeBSD (no /dev/tty) – Paul Bissex Jan 25 '16 at 13:52
  • 12
    @Nick Chammas,To maintain a normal pipeline, you can swap the outputs of tee: `echo 'ee' | tee >(cat) | foo | bar`. – Vaelus Oct 31 '17 at 15:25
  • 1
    @Vaelus For me `foo` sees 2x the `ee` and none is printed on terminal. Try `echo 'ee' | tee >(cat) | grep .` vs. `echo 'ee' | tee >(cat) | grep x`. – Marki555 Aug 20 '20 at 22:47
  • @Marki555, I can't reproduce you behavior, but maybe you need to use tee >(cat -) instead to tell cat to read from stdin. – Vaelus Aug 21 '20 at 00:35
  • This prints the prompt one more time between the outputs, effectively appending the second output to one of the prompts instead of printing from the begging of the terminal line. – Nakilon May 29 '21 at 17:26
  • `echo 'ee' | tee >(cat) | foo | bar` has `cat` outputting to stdout along with `tee`, so you're effectively doubling up the input to `foo`. Instead, if you're trying to see what's flowing through `tee` in your terminal, have `cat` output to stderr: `echo 'ee' | tee >(cat 1>&2) | foo | bar` – Aaron May 26 '23 at 01:14
23

Try:

$ echo 'ee' | tee /dev/stderr | foo

If using stderr is an option, of course.

Jan
  • 1,807
  • 13
  • 26
22

Access to "/dev/stdout" is denied on some systems, but access to the user terminal is given by "/dev/tty". Using "wc" for "foo", the above examples work OK (on linux, OSX, etc.) as:

% echo 'Hi' | tee /dev/tty | wc Hi 1 1 3

To add a count at the bottom of a list of matching files, I use something like:
% ls [A-J]* | tee /dev/tty | wc -l

To avoid having to remember all this, I define aliases:
% alias t tee /dev/tty
% alias wcl wc -l

so that I can simply say:
% ls [A-J]* | t | wcl


POSTSCRIPT: For the younger set, who might titter at its pronunciation as "titty", I might add that "tty" was once the common abbreviation for a "teletype" terminal, which used a roll of yellow paper and had round keys that often stuck.

user51527
  • 321
  • 2
  • 2
12

first you need to figure out the terminal associated with your screen (or whichever screen you want the output to display on):

tty

then you can tee the output to that terminal and pipe the other copy through your foo program:

echo ee | tee /dev/pty/2 | foo
Michael Martinez
  • 2,693
  • 1
  • 16
  • 19
  • 2
    oneliner: t=$(tty) echo ee | tee $t | foo | bar – Jack Tang Oct 10 '14 at 12:44
  • 5
    @JackTang That's indeed better but `t` is useless. You can use `echo ee | tee $(tty) | foo` but it still has a useless command (`tty`), given the fact `/dev/tty` just works. – jlliagre Oct 24 '14 at 20:56