Home > Blockchain >  Output stdout and stderr to file and screen and stderr to file in a limited environment
Output stdout and stderr to file and screen and stderr to file in a limited environment

Time:01-21

The problem could be probably fixed using mkfifo, but it doesn't exist on my QNAP. So, here is the description of problem and what I tried so far.

I have a function called activateLogs that restarts the script if writing logs to disk or both (screen and disk). Both option is the new functionality I would like to achieve.

exec 3<> "$logPath/$logFileName.log"
"$0" "${mainArgs[@]}" 2>&1 1>&3 | tee -a "$logPath/$logFileName.err" 1>&3 &

This piece of code is the version that writes to disk. mainArgs contains all the arguments passed to the script and is defined out of this function. This solution came from https://stackoverflow.com/a/45426547/214898. It combines stderr & stdout in a file and still output stderr in another.

So, now, I would like to be able to keep this and add printing stderr & stdout to screen.

The accepted solution from the question linked above cannot be applied because the script is running using sh and mkfifo is not present.

Attempt #1

exec 3>&1
"$0" "${mainArgs[@]}" 2>&1 1>&3 | tee -a "$logPath/$logFileName.err" 1>&3 &

--> to replace the code above and in a if branching (that already existed) I added the tee command.

local isFileDescriptor3Exist=$(command 2>/dev/null >&3 && echo "Y")

if [ "$isFileDescriptor3Exist" = "Y" ]; then
    tee -a "logs/123.log" &
    echo "Logs are configured"
else
    ### CODE ABOVE
fi

I have the screen, the error file, but the log file is empty.

Attempt #2 Now, no tee in the if branching above, but included in the relaunching command.

exec 3>&1
"$0" "${mainArgs[@]}" 2>&1 1>&3 | tee -a "$logPath/$logFileName.err" 1>&3 3>&1 | tee -a "logs/123.log" &

Same result. I may understand in this one that the first tee not initially writing to the file descriptor #3, thus, the 3>&1 does nothing.

Attempt #3 (No more relaunching the script)

out="${TMPDIR:-/tmp}/out.$$"
err="${TMPDIR:-/tmp}/err.$$"
busybox mkfifo "$out" "$err"

trap 'rm "$out" "$err"' EXIT

tee -a "$logPath/$logFileName.log" &
tee -a "$logPath/$logFileName.err" < "$err" >&2 &
command >"$out" 2>"$err"

I am getting mkfifo: applet not found from busybox

Attempt #4 (No more relaunching the script)

out="${TMPDIR:-/tmp}/out.$$"
err="${TMPDIR:-/tmp}/err.$$"
python -c "import os; os.mkfifo(\"$out\")"
python -c "import os; os.mkfifo(\"$err\")"

trap 'rm "$out" "$err"' EXIT

tee -a "$logPath/$logFileName.log" &
tee -a "$logPath/$logFileName.err" < "$err" >&2 &
command >"$out" 2>"$err"

I have no logs (neither "real" logs" nor errors). The temporary files are deleted though. Moreover, the script never ends which is caused by trap.

Attempt #5

exec 3>&1
{ "$0" "${mainArgs[@]}" | tee -a "$logPath/$logFileName.log"; } 2>&1 1>&3 | tee -a "$logPath/$logFileName.err" &
exit

That solution seemed promising, but now relaunching the script doesn't work because my code detects if currently executing and stop it. This is normal because it is executed in subprocess even though I use & at the end of the full line, but... (testing while writing). Replacing the terminator ; by & fixed it.

Now I have stdout & stderr to one file & to screen plus stderr to a separate file.

Final version working

exec 3>&1
{ "$0" "${mainArgs[@]}" | tee -a "$logPath/$logFileName.log" & } 2>&1 1>&3 | tee -a "$logPath/$logFileName.err" &
exit

Incredible how my first attempt was so close to the solution.

CodePudding user response:

Maybe I'm not reading the question correctly, but it seems that you could do something like:

#!/bin/sh


exec 3>&1
cmd (){
        echo stdout;
        echo stderr >&2;
}

stderr_outfile=outfile
if test -n "$log_stdout"; then
        stdout_outfile=$log_stdout
else
        stdout_outfile=/dev/null
fi

{ cmd | tee "$stdout_outfile"; } 2>&1 1>&3 | tee "$stderr_outfile"

CodePudding user response:

Full code of my activateLogs function for those interested. I also included the dependencies even though they could be inserted into the activateLogs function. For the specific issue resolution, see @WilliamPursell's answer.

m=0
declare -a mainArgs
if [ ! "$#" = "0" ]; then
    for arg in "$@"; do
        mainArgs[$m]=$arg
        m=$(($m   1))
    done
fi

function hasMainArg()
# $1 string to find
# return 0 if there is a match, otherwise 1
{
    local match="$1"
    containsElement "$1" "${mainArgs[@]}"
    return $?
}

function containsElement()
# $1 string to find
# $2 array to search in
# return 0 if there is a match, otherwise 1
{
  local e match="$1"
  shift
  for e; do [[ "$e" == "$match" ]] && return 0; done
  return 1
}

function activateLogs()
# $1 = logOutput: What is the output for logs: SCREEN, DISK, BOTH. Default is DISK. Optional parameter.
{
    local logOutput=$1
    if [ "$logOutput" != "SCREEN" ] && [ "$logOutput" != "BOTH" ]; then
        logOutput="DISK"
    fi
    
    if [ "$logOutput" = "SCREEN" ]; then
        echo "Logs will only be output to screen"
        return
    fi
    
    hasMainArg "--force-log"
    local forceLog=$?
        
    local isFileDescriptor3Exist=$(command 2>/dev/null >&3 && echo "Y")
    
    if [ "$isFileDescriptor3Exist" = "Y" ]; then
        echo "Logs are configured"
    elif [ "$forceLog" = "1" ] && ([ ! -t 1 ] || [ ! -t 2 ]); then
        # Use external file descriptor if they are set except if having "--force-log"
        echo "Logs are configured externally"
    else
        echo "Relaunching with logs files"
        local logPath="logs"
        if [ ! -d $logPath ]; then mkdir $logPath; fi
        
        local logFileName=$(basename "$0")"."$(date  %Y-%m-%d.%k-%M-%S)
    
        if [ "$logOutput" = "DISK" ]; then
            # FROM: https://stackoverflow.com/a/45426547/214898
            exec 3<> "$logPath/$logFileName.log"
            "$0" "${mainArgs[@]}" 2>&1 1>&3 | tee -a "$logPath/$logFileName.err" 1>&3 &
        else
            # FROM: https://stackoverflow.com/a/70790574/214898
            exec 3>&1
            { "$0" "${mainArgs[@]}" | tee -a "$logPath/$logFileName.log" & } 2>&1 1>&3 | tee -a "$logPath/$logFileName.err" &
        fi
        
        exit        
    fi
}
  •  Tags:  
  • Related