簡體   English   中英

Output stdout and stderr to file and screen and stderr to file 在有限的環境中

[英]Output stdout and stderr to file and screen and stderr to file in a limited environment

該問題可能可以使用mkfifo解決,但在我的 QNAP 上不存在。 所以,這里是問題的描述以及我到目前為止所嘗試的內容。

我有一個名為activateLogs的 function 如果將日志寫入磁盤或兩者(屏幕和磁盤),它會重新啟動腳本。 這兩個選項都是我想要實現的新功能。

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

這段代碼是寫入磁盤的版本。 mainArgs包含傳遞給腳本的所有 arguments,並在此 function 中定義。 該解決方案來自https://stackoverflow.com/a/45426547/214898 它將 stderr 和 stdout 組合在一個文件中,而 output stderr 在另一個文件中。

所以,現在,我希望能夠保留它並將打印標准錯誤和標准輸出添加到屏幕。

無法應用上面鏈接的問題中接受的解決方案,因為腳本正在使用sh運行並且mkfifo不存在。

嘗試#1

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

--> 替換上面的代碼並在if分支(已經存在)中添加了tee命令。

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

我有屏幕,錯誤文件,但日志文件是空的。

嘗試 #2現在,上面的if分支中沒有 tee,但包含在重新啟動命令中。

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

結果相同。 我可能在這個中理解第一個tee最初沒有寫入文件描述符#3,因此3>&1什么都不做。

嘗試 #3 (不再重新啟動腳本)

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"

我收到 mkfifo:從busybox mkfifo: applet not found

嘗試 #4 (不再重新啟動腳本)

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"

我沒有日志(“真實”日志“也沒有錯誤)。臨時文件被刪除了。此外,腳本永遠不會結束,這是由trap引起的。

嘗試#5

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

該解決方案似乎很有希望,但現在重新啟動腳本不起作用,因為我的代碼檢測當前是否正在執行並停止它。 這是正常的,因為即使我在整行末尾使用& ,它也會在子進程中執行,但是......(在編寫時進行測試)。 更換終結器; 通過&修復它。

現在我將 stdout 和 stderr 放到一個文件中,並將 stderr 放到一個單獨的文件中。

最終版本工作

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

令人難以置信的是,我的第一次嘗試是如此接近解決方案。

也許我沒有正確閱讀問題,但似乎您可以執行以下操作:

#!/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"

我的activateLogs function 的完整代碼供感興趣的人使用。 我還包括了依賴項,即使它們可以插入到activateLogs function 中。 有關具體問題的解決方案,請參閱@WilliamPursell 的回答。

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
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM