9

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.

3 Answers 3

10

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.

1
  • 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)) Feb 23, 2020 at 23:38
3

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.

2

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.

1
  • 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. Sep 11, 2016 at 1:13

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .