PJLM
PJLM

Reputation: 67

switch output file redirection in the middle of a bash script

I just want to save the output of my script at a place I don't know at the beginning of the script. I try something but I'm pretty sure that it's ugly. Is there an elegant way to do that:

#!/bin/bash

# Here I don't know where to write outputfile so I create a tmpfile
fic=$(mktemp -t)
trap 'rm -f $fic' EXIT
rm -f $fic
:> $fic
exec 3<> $fic
exec 1>&3
exec 2>&3

# some code in particular reading options and dest dir
echo foo
dir="."

# Here I finally know where I can write my output
fic2=$dir/log.log

cp -f $fic $fic2
exec 3>&- # close fd #3
exec 1>> $fic2
exec 2>&1

echo bar

Furthermore, I would like to tee the entire output, something like $ exec ... >(tee $fic)$ but I failed to find a solution.

Thanks a lot for any advice. PJLM

Upvotes: 2

Views: 518

Answers (1)

Grisha Levit
Grisha Levit

Reputation: 8617

If you know that both output files are on the same file system, you can just mv the output file. The file descriptors you have open will continue to work.

exec 1>/tmp/out1 2>&1
echo out1
mv /tmp/out1 /tmp/out2   # replace with your desired destination
echo out2

If you want to tee the output, and, again, both output files are on the same filesystem, you can do pretty much the same thing (once tee has opened the file for writing it will likewise keep writing to the same fd even if the file moves).

log1=$(mktemp)
exec 3>"$log1"
exec 1> >(tee /dev/fd/3) 2>&1
echo out1
mv "$log1" "$log2"
echo out2

Note that instead of doing >(tee "$log1") I first open fd 3 in the shell and then use >(tee /dev/fd/3). This is because otherwise there is a potential race condition where tee will not have opened the file by the time we get to the mv step. (exec only waits until the subshell in which tee will run has started, but it takes some time for tee itself to start and open the file).


If your first and second output files may not be on the same file system, you will have to do some more advanced shuffling and make sure that writing to the first file finishes before copying it.

In the case of a simple redirect, we need to close the file descriptors before moving:

exec 1>"$log1" 2>&1
echo out1
exec 1>&- 2>&-
mv "$log1" "$log2"
exec 1>>"$log2" 2>&1
echo out2

In the case of a process substitution with output files potentially on different file systems, we need to make sure that the process substitution finishes before moving the file:

exec 3>&1 4>&2                # save original stdout, stderr
exec 1> >(tee "$log1") 2>&1   # redirect to tee
pid=$!                        # save pid of tee's subshell

echo out1
exec 1>&3 2>&4                # restore original stdout, stderr

# wait until tee is done. on newer bash can use `wait $pid` instead
while kill -0 $pid 2>/dev/null; do :; done

mv "$log1" "$log2"

# repeat steps above for new file
exec 3>&1 4>&2
exec 1> >(tee -a "$log2") 2>&1
pid=$!
echo out2
exec 1>&3 2>&4
while kill -0 $pid 2>/dev/null; do :; done

Upvotes: 2

Related Questions