2

I'm converting a script from bash to zsh and have been floundering for days with the output. I want the stdout and stderr to both go to both the console and a log file. I've tried everything I can find online but no joy. I've read the redirection section of the zsh manual but found nothing that helped, although I must admit it is unclear to me.

Here are the things I've tried near the beginning of the script, experimentally, and their result:

exec 2>&1 | tee -i test.log     # everything in console, log created but empty
exec 2>&1 | tee -i > test.log   # everything in console, log created but empty
exec 1>>$LOG; exec 2>&1         # everything in log, none in console
exec 2>&1; exec 1>>$LOG         # console gets errors only; log stdout only
exec &> >(tee "$LOG")           # works in bash, error in zsh
exec > >(tee -i ${LOG?}) 2>&1   # error
exec |& tee $LOG                # error
exec > >(tee $LOG) 2>&1         # error

I also tried to understand the zsh option multios, but couldn't make much sense of it. It's supposed to be on by default, but I don't find it in the list of options when I run setopt. Nor when I run setopt multios. When I run unsetopt multios, I see the option nomultios, but that doesn't seem to help

I understand that similar questions have been asked and answered, but the answers are mostly in the list above and have not worked for me.

I don't think it's relevant, but I also set up a debug log that gets file descriptor 3. It's just for debug output lines that are ended with >&3.

Note for anyone looking for such a solution: The above exec trials were done with a messed up shebang, and the results with that corrected were a bit different. In addition to the solution by Gairfowl, below, this also worked:

exec > >(tee $LOG) 2>&1
2
  • Is that a literal exec that's used or did you replace your executable name with confusing exec?
    – Destroy666
    Commented Aug 10, 2023 at 0:33
  • @Destroy666, I'm not entirely clear what you're asking, but the script literally says "exec". Commented Aug 10, 2023 at 4:18

1 Answer 1

2

This appears to work:

#!/usr/bin/env zsh
exec >&1 >>log.txt 2>&1
print -u1 stdout stuff
print -u2 stderr info

Testing:

> ./testexec
stdout stuff
stderr info
> cat log.txt
stdout stuff
stderr info

Change >>log.txt to >log.txt if you want to overwrite the file each time rather than appending to it. This also worked with >>$log when the log variable was set to a filename. The multios syntax is described in this answer, and there's a bit more info here.

This does require that multios be enabled - as you noted, that is the default. set -o (nb: set, not setopt) can be used to display the status of all of the options; setopt without parameters only lists options that have been changed from the default.

Some notes on the pieces:

  • exec ... - the exec builtin will replace the current shell with a new process. When it's invoked like this without a command argument, the replacement is another instance of zsh, with the redirects set to the new values in the command line. The effect is almost identical to invoking a command with those redirects on the command line, e.g.
    {print abc;print -u2 def} >&1 >log.txt 2>&1.
  • >&1 - redirect stdout to stdout. Yes, it's a bit redundant - this is a signal to multios that we want to continue to send stdout to its current destination (which is probably the terminal) in addition to the following redirects.
  • >>log.txt - add a redirect of stdout to the file log.txt in append mode. This is a multios-specific behavior; now the standard output is being sent to two places, via a mechanism in zsh that looks a lot like tee.
  • 2>&1 - send stderr to the same place as stdout. Thanks to the previous two redirects, stdout is going to two places, so this also sends stderr output to the file and (probably) the terminal.
  • print -u<n> - just for testing. The -u option sends the output to file descriptor <n>.
0

You must log in to answer this question.

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