68

I want to split stdout so that it is printed both to stdout and stderr. This sounds like a job for tee but the syntax is evading me -

./script.sh | tee stderr

Of course, how should stderr actually be referred to here?

djechlin
  • 59,258
  • 35
  • 162
  • 290
  • @JonathanLeffler There are systems with /dev/fd but without the symlinks stderr, stdout. I think that includes Solaris? A few outliers, like HP-UX, AIX, Irix don't have /dev/fd, of course, but all sane systems do. – Nicholas Wilson Dec 10 '12 at 16:31
  • My bad, Solaris 8 and 9 do have /dev/stderr... – Nicholas Wilson Dec 10 '12 at 16:34
  • Really? Solaris 10 has `/dev/stderr`. I've not encountered a system with `/dev/fd` that does not also have `/dev/std{in,out,err}`, but that doesn't mean they don't exist. Mac OS X 10.7.5 has both; Solaris has both; Linux has both. AIX 6 has neither; HP-UX 11.00 has neither. – Jonathan Leffler Dec 10 '12 at 16:39
  • Possible dupe with lots of potential answers: http://stackoverflow.com/questions/692000/how-do-i-write-stderr-to-a-file-while-using-tee-with-a-pipe – Eric Leschinski Dec 10 '12 at 16:41
  • `bash` accepts `/dev/stderr` as a synonym for standard error whether or not the file exists in the file system; it's a special case. – chepner Dec 10 '12 at 18:47
  • 1
    Regarding `/dev/fd/2` and `/dev/stderr`; bash manual on redirections: [Bash handles several filenames specially when they are used in redirections, ...](https://www.gnu.org/software/bash/manual/html_node/Redirections.html) – Joel Purra Nov 28 '13 at 17:29

4 Answers4

87

The only cross platform method I found which works in both interactive and non-interactive shells is:

command | tee >(cat 1>&2)

The argument to tee is a file or file handle. Using process substitution we send the output to a process. In the process =cat=, we redirect stdout to stderr. The shell (bash/ksh) is responsible for setting up the 1 and 2 file descriptors.

Brent Bradburn
  • 51,587
  • 17
  • 154
  • 173
brianegge
  • 29,240
  • 13
  • 74
  • 99
61
./script.sh | tee /dev/fd/2

Note that this is dependant on OS support, not any built-in power in tee, so isn't universal (but will work on MacOS, Linux, Solaris, FreeBSD, probably others).

Nicholas Wilson
  • 9,435
  • 1
  • 41
  • 80
  • For a better explanation of what is taking place behind the scenes in the above command, read the bash hackers site on redirection: http://wiki.bash-hackers.org/syntax/redirection – Eric Leschinski Dec 10 '12 at 17:33
  • It's not really redirection, is it? It's just pipe syntax. The /dev/fd/2 argument is opaque to the shell; it only sets up the stdin fd up for the tee child, which is the process that interprets the argument (as a filename, to obtain the output fd with `open()`). – Nicholas Wilson Dec 10 '12 at 18:11
  • 1
    The manpage for `bash` implies that it accepts `/dev/fd/2` as a file name for file descriptor 2 even the file system doesn't have such a file. – chepner Dec 10 '12 at 18:48
  • 2
    @chepner That's interesting. But, in this example the filename isn't ever interpreted by `bash`, so that isn't coming into play here. – Nicholas Wilson Dec 10 '12 at 18:51
  • Oh, right. It would work with redirection, but not as an argument to `tee`. – chepner Dec 11 '12 at 17:15
  • 4
    Note that it may fail depending on what `/dev/fd/2` exactly is. For example, if you log in as `root`, your terminal is owned by `root`. Then, if you switch user, you will have no permission to access `/dev/fd/2`. – Michał Górny Sep 06 '13 at 10:01
  • Also, `/proc/self/fd/2` is something may be available. in fact, on my system [CentOS6] `/dev/fd` is a symlink to `/proc/self/fd`. – Sammitch May 04 '16 at 01:08
  • NOTE: not awlays has write permision to `/dev/fd/2` or `/proc/self/fd/2` or `/dev/pts/xx`. for example: if `su` to other user (then pts not belong to current uesr) – yurenchen Apr 05 '23 at 18:46
0

About /dev/stderr permission

user not always has write permission for

  • /dev/stderr
  • /dev/fd/2
  • /proc/self/fd/2
    (they all link to same pts)

for example, after sudo:

sudo su - nobody -s /bin/bash

nobody@test:/$ ls -lh /dev/stderr /dev/fd/2 /proc/self/fd/2
lrwxrwxrwx 1 root   root    15 Jun 10  2021 /dev/stderr -> /proc/self/fd/2
lrwx------ 1 nobody nogroup 64 Apr  7 01:58 /dev/fd/2 -> /dev/pts/5
lrwx------ 1 nobody nogroup 64 Apr  7 01:58 /proc/self/fd/2 -> /dev/pts/5

nobody@test:/$ echo hell >&2
hell

nobody@test:/$ echo hell >/dev/fd/2
-su: /dev/fd/2: Permission denied

nobody@test:/$ echo hell >/dev/stderr 
-su: /dev/stderr: Permission denied

nobody@test:/$ echo hell > /dev/pts/5
-su: /dev/pts/5: Permission denied

Another choice: awk

nobody@test:/$ echo hell | awk '{print>"/dev/stderr";print}' 
hell
hell

nobody@test:/$ echo ' hell   12 ' | awk '{print|"cat 1>&2";print}'
 hell   12 
 hell   12 

Although there has >"/dev/stderr", but it not really same as it in shell.

Maybe awk is not as performant as tee,
But it's worth noting, about the real stderr.

RTSC

Figure out the difference

To be continue..

yurenchen
  • 1,897
  • 19
  • 17
-4
./script.sh 2>&1 >/dev/null | tee stderr.out

That opens STDERR to STDOUT, and then disposes of STDOUT.

David W.
  • 105,218
  • 39
  • 216
  • 337
  • This pipes all output to `/dev/null`. – Lexi Jul 28 '16 at 09:10
  • 2
    It does not pipe all to /dev/null, just the script.sh's stdout. The script.sh's stderr goes to tee's stdin and tee writes that all to tee's stdout and the file stderr.out... It is not what was asked for in the question, but it happens to be what I am looking for so +1 from me :-) – David L. Feb 27 '17 at 18:52