Send stdout and stderr to file, syslog, and terminal

6,388

Solution 1

Add a nested process substitution and another tee in there like:

exec &> >(tee >(tee "/tmp/box-setup.log" | logger -t box-setup))

The first tee within the main process substitution sends STDOUT/STDERR to terminal and also to the nested process substitution, the tee inside that saves the content in file /tmp/box-setup.log and the pipe is used to send the output to logger's STDIN too.

Solution 2

I'm completely blind because the console is blank as the bash script executes.

You might want to solve that problem a different way: run your bash script in the background, and run less /tmp/box-setup.log and hit F to keep updating the screen as lines are added to the file it's watching (like tail -f).

If running the script in the background is a problem, use tmux or screen to get multiple sessions multiplexed onto one ssh connection. Use the same less command in another shell.


The original problem:

tee can copy to multiple destinations. Make one of them the terminal, using the /dev/tty special file. I think it always refers to the controlling tty of the current process. Or probably better, to /dev/stderr, since tee's stderr is still connected to the shell's stderr. (This lets you silence the script with &> /dev/null).

exec &> >(tee /dev/stderr "/tmp/box-setup.log" | logger -t box-setup)

BTW, this is equivalent to but more efficient than (tee /dev/stderr | tee "/tmp/box-setup.log" | logger ...).

It would be possible to use some file-descriptor cloning to give tee the original script's stdout, rather than stderr.

Solution 3

Simply add /dev/stderr (your choice) as an output to tee.

exec &> >(tee /dev/stderr "/tmp/box-setup.log" | logger -t box-setup)

Standard output and standard error will be merged. There's no way to keep them separate if you want to preserve their order, which is usually desirable. It doesn't matter if they're both going to the same place (e.g. the terminal) anyway.

Share:
6,388

Related videos on Youtube

Kaito Kume
Author by

Kaito Kume

Updated on September 18, 2022

Comments

  • Kaito Kume
    Kaito Kume almost 2 years

    For some cloud machines I'm launching, I'm trying to log to a specific file, syslog, and the terminal/console.

    At the top of my machine setup/cloud-init scripts, I have the following:

    #!/bin/bash
    exec &> >(tee "/tmp/box-setup.log" | logger -t box-setup)
    apt-get install -y some-package
    

    This works great at sending output to a file and syslog, but it doesn't pipe to the output to the terminal.

    Generally speaking not having terminal output isn't a huge problem except when I'm debugging from a remote console. When that happens, I'm completely blind because the console is blank as the bash script executes.

    Is there a simple way using bash redirection or whatever to pipe all output (standard output along with standard error) to a file, syslog, and the terminal simultaneously?

    I'm running Ubuntu 16.04.

  • Peter Cordes
    Peter Cordes almost 8 years
    tee's stdout is the pipe to logger, not the terminal. It's stderr at that point is still the terminal, based on exec &> >(tee /dev/stderr > /dev/null) working as expected in an interactive shell. stderr seems like a better idea than my /dev/tty idea, since it lets you easily shut the script up with a redirect if you want.
  • ThorSummoner
    ThorSummoner over 4 years
    i'm confused about multiple tee invocations, it will fork to n streams, shouldn't this be equivalent? exec &> >(tee /tmp/box-setup.log >(logger -t box-setup))