79195380

Date: 2024-11-16 14:51:57
Score: 1
Natty:
Report link

I did some further testing of the code that was suggested. On reflection it wasn't complete as per my original question. For that reason, I'm posting here the working code, which has been called a few times to prove it works in all possible modes (with basic arguments only - not a list as has been suggested will not work)

I found it fascinating that the redirect to tee needs a sleep 0 afterwards because the first command to echo will often do that prior to the tee being set up. I don't know how to wait only until that redirect is set before continuing.

The following code echos quite a bit to the various destinations, each echo is done via a method to ensure a unique sequential id. This was needed to confirm they are logged in the correct order.

The initial solution unfortunately has the bug my question has, that is log statements would be out of order. This is less neat because I need to bookend the block I want redirected to script_output.log with two statements rather than just wrap it in {}, but at least it keeps things as you'd expect from execution order.

#!/bin/bash

script_log="script_output.log"
process_log="output.log"

function store_output_redirects() {
    exec 3>&1 4>&2
}

function redirect_all_output_to_file() {
    exec  1>> "${script_log}" 2>&1
}

function clear_output_redirects() {
    exec 1>&3 2>&4
}

#!/bin/bash
my_function() {
    local pid_file=$1; logfile=$2
    local tee_output="none"
    if [ "$3" = "tee" ] || [ "$3" = "tee_console" ] ; then
        tee_output=$3
        shift
    fi
    cmd=("${@:3}")
    log "console: inside function"
    store_output_redirects
    redirect_all_output_to_file
    {
        # this block is redirected to ${script_log}
        log "${script_log}: inside redirected block"
        
        if [[ "$logfile" = "console" ]]; then
            # swap the output to the default (console)
            clear_output_redirects
        elif [ "$tee_output" = "tee" ] ; then
            #clear_output_redirects
            
            exec 1> >(tee "$logfile")
            # insert a tiny time delay for the tee redirect to be set up (otherwise output will go to the previous destination).
            sleep 0
        elif [ "$tee_output" = "tee_console" ] ; then
            clear_output_redirects
            exec 1> >(tee "$logfile")
            # insert a tiny time delay for the tee redirect to be set up (otherwise output will go to the previous destination).
            sleep 0
        else
            exec >>"$logfile"
            # insert a tiny time delay for the tee redirect to be set up (otherwise output will go to the previous destination).
            sleep 0
        fi
        
        echo "$BASHPID" > "$pid_file"
        
        if ! [ "$tee_output" = "none" ] ; then
            log "tee: command to be executed with output to $logfile and console: ${cmd[*]}"
        else
            log "$logfile: command to be executed with output to $logfile: ${cmd[*]}"
        fi
        # this cannot be escaped as per shellcheck.net as error 'command not found' when not using tee.
        ${cmd[@]}

        
        # reset the defaults for this block
        redirect_all_output_to_file
        
        log "${script_log}: end of redirected block"
    }
    clear_output_redirects
    log "console: function ends"
}

function log() {
    (( log_line = log_line + 1 ))
    echo "${log_line} $*"
}

function print_log() {
    local logfile="$1"
    echo
    if ! [ -f "$logfile" ] ; then
        echo "log file $logfile does not exist"
    else
        echo Contents of "$logfile":
        cat "$logfile" | while read -r logline 
        do
            echo "$logline"
        done
    fi
}

function test_redirection() {
    log_line=0
    local cmd_log_file=$1; shift
    local tee=$1; shift
    
    
    [ -f "${process_log}" ] && rm "${process_log}"
    [ -f "${script_log}" ] && rm "${script_log}"
    log "console: Redirecting requested cmd output to $cmd_log_file"
    log console: making copy of the stdout and stderr destinations
    
    log console: calling function
    my_function cmd.pid "$cmd_log_file" "$tee" echo hi
    
    log console: stdout and sterr reset to the defaults for the next test
    
    print_log "$script_log"
    echo
    print_log "$process_log"
}

echo "*****************************"
echo "** Testing output to console"
echo "*****************************"
test_redirection "console" 
echo "*****************************"
echo "** Testing output to logfile"
echo "*****************************"
test_redirection "$process_log" 
echo "*****************************"
echo "** Testing output to logfile and console"
echo "*****************************"
test_redirection "$process_log" "tee_console"
echo "*****************************"
echo "** Testing output to logfile and default"
echo "*****************************"
test_redirection "$process_log" "tee"

Reasons:
  • Blacklisted phrase (0.5): I need
  • RegEx Blacklisted phrase (1): I want
  • Long answer (-1):
  • Has code block (-0.5):
  • Self-answer (0.5):
  • Low reputation (0.5):
Posted by: extorn