How do I get both STDOUT and STDERR to go to the t

2020-05-11 03:17发布

I have a script which will be run interactively by non-technical users. The script writes status updates to STDOUT so that the user can be sure that the script is running OK.

I want both STDOUT and STDERR redirected to the terminal (so that the user can see that the script is working as well as see if there was a problem). I also want both streams redirected to a log file.

I've seen a bunch of solutions on the net. Some don't work and others are horribly complicated. I've developed a workable solution (which I'll enter as an answer), but it's kludgy.

The perfect solution would be a single line of code that could be incorporated into the beginning of any script that sends both streams to both the terminal and a log file.

EDIT: Redirecting STDERR to STDOUT and piping the result to tee works, but it depends on the users remembering to redirect and pipe the output. I want the logging to be fool-proof and automatic (which is why I'd like to be able to embed the solution into the script itself.)

9条回答
别忘想泡老子
2楼-- · 2020-05-11 04:05

The Pattern

the_cmd 1> >(tee stdout.txt ) 2> >(tee stderr.txt >&2 )

This redirects both stdout and stderr separately, and it sends separate copies of stdout and stderr to the caller (which might be your terminal).

  • In zsh, it will not proceed to the next statement until the tees have finished.

  • In bash, you may find that the final few lines of output appear after whatever statement comes next.

In either case, the right bits go to the right places.


Explanation

Here's a script (stored in ./example):

#! /usr/bin/env bash
the_cmd()
{
    echo out;
    1>&2 echo err;
}

the_cmd 1> >(tee stdout.txt ) 2> >(tee stderr.txt >&2 )

Here's a session:

$ foo=$(./example)
    err

$ echo $foo
    out

$ cat stdout.txt
    out

$ cat stderr.txt
    err

Here's how it works:

  1. Both tee processes are started, their stdins are assigned to file descriptors. Because they're enclosed in process substitutions, the paths to those file descriptors are substituted in the calling command, so now it looks something like this:

the_cmd 1> /proc/self/fd/13 2> /proc/self/fd/14

  1. the_cmd runs, writing stdout to the first file descriptor, and stderr to the second one.

  2. In the bash case, once the_cmd finishes, the following statement happens immediately (if your terminal is the caller, then you will see your prompt appear).

  3. In the zsh case, once the_cmd finishes, the shell waits for both of the tee processes to finish before moving on. More on this here.

  4. The first tee process, which is reading from the_cmd's stdout, writes a copy of that stdout back to the caller because that's what tee does. Its outputs are not redirected, so they make it back to the caller unchanged

  5. The second tee process has it's stdout redirected to the caller's stderr (which is good, because it's stdin is reading from the_cmd's stderr). So when it writes to its stdout, those bits go to the caller's stderr.

This keeps stderr separate from stdout both in the files and in the command's output.

If the first tee writes any errors, they'll show up in both the stderr file and in the command's stderr, if the second tee writes any errors, they'll only show up only in the terminal's stderr.

查看更多
淡お忘
3楼-- · 2020-05-11 04:05

Use the tee program and dup stderr to stdout.

 program 2>&1 | tee > logfile
查看更多
Emotional °昔
4楼-- · 2020-05-11 04:06

the to redirect stderr to stdout append this at your command: 2>&1 For outputting to terminal and logging into file you should use tee

Both together would look like this:

 mycommand 2>&1 | tee mylogfile.log

EDIT: For embedding into your script you would do the same. So your script

#!/bin/sh
whatever1
whatever2
...
whatever3

would end up as

#!/bin/sh
( whatever1
whatever2
...
whatever3 ) 2>&1 | tee mylogfile.log
查看更多
登录 后发表回答