2012-05-22 57 views
7

我已經寫了一個bash日誌庫來實現一些我公司目前使用的複雜腳本。在進行日誌調用時,我一直在提供調用腳本的腳本文件名($ {BASH_SOURCE})和行號($ {LINENO})。但是,我不想依靠用戶或實現腳本來傳遞這兩個變量作爲參數。如果這是C/C++,我只需創建一個宏,它將參數列表中的「__FILE__」和「__LINE__」預先設置好。Bash參數報價和評估

我終於能夠得到這部分工作。這裏有一些非常簡單的摘要作爲一個概念證明:

這裏的日誌庫:

# log.sh 

LOG="eval _log \${BASH_SOURCE} \${LINENO}" 

_log() { 
    _BASH_SOURCE=`basename "${1}"` && shift 
    _LINENO=${1} && shift 

    echo "(${_BASH_SOURCE}:${_LINENO}) [email protected]" 
} 

而且一個執行測試腳本:

# MyTest.sh 

. ./log.sh 

${LOG} "This is a log message" 
# (test.sh:5) This is a log message 

這工作得很好(和我很高興能夠讓它在第一時間工作)。然而,這有一個明顯的問題:引號與eval之間的相互作用。如果我撥打電話:

${LOG} "I'm thrilled that I got this working" 
# ./test.sh: eval: line 5: unexected EOF while looking for matching `'' 
# ./test.sh: eval: line 6: syntax error: unexpected end of file 

現在,我相信我明白爲什麼會發生這種情況。引用的參數在傳遞給eval時保持不變,但在此時,內容將按照原樣放置到生成的命令字符串中。我知道我可以通過做一些逃避來解決這個問題。然而,我真的不想強制執行腳本必須這樣做。在我實現這個「eval宏」功能之前,我讓用戶直接調用「_log」並允許他們可選地傳入「$ {LINENO}」。通過這個實現,上面的失敗調用(只有引用的句子)工作得很好。

在最基本的層面上,我真正想要的是一個腳本,能夠調用[日誌功能/宏]「字符串登錄特殊characers」和具有生成的日誌消息包含文件名和行調用腳本的編號,然後是日誌消息。如果可能的話,我會認爲我非常接近,但是如果我忽略了一些需要採用不同方法的東西,我也會這樣認爲。我不能強迫用戶轉移他們的所有消息,因爲這可能會導致他們不使用這個庫。這是一個問題,如果我找不到解決方案,我可能會回到舊的功能(需要$ {LINENO}作爲函數參數傳遞 - 這是非常不容易侵入的)。

TLDR:有什麼辦法讓eval尊重引用參數中的特殊字符,而不必轉義它們嗎?

+0

如果您使用bash特定結構,則不應將您的日誌記錄腳本命名爲「log.sh」。它應該是「log.bash」。 –

+0

@WilliamPursell - 我做了一些搜索,找不到任何關於shell腳本命名約定的標準。事實上,大多數人似乎完全沒有後綴。說實話,我從來沒有見過除'\ *。sh'之外的東西(或者沒有擴展名/後綴的腳本)。在所有的實際應用中,它不應該有任何影響,對吧?解釋器或者從腳本的第一行中拉出來,或者使用當前的shell。它似乎基本上歸結爲個人偏好。但是,也許「\ *。sh」可能暗示給用戶腳本使用「sh」。 – LousyG

+1

唯一的實際影響是如果開發人員使用不同的shell使用你的函數。用'* .sh'這個名字,開發人員會合理地認爲你的庫可以用zsh腳本來源。 –

回答

12

如果可能,我建議避免eval。對於你的日誌用例,你可以看一下shell內建的caller。如果您需要更多信息,可以使用變量BASH_SOURCEBASH_LINENOFUNCNAME。請注意,所有這些變量都是數組,幷包含完整的調用堆棧。請看下面的例子:

#! /bin/bash  

function log() { 
    echo "[$(caller)] $*" >&2 
    echo "BASH_SOURCE: ${BASH_SOURCE[*]}" 
    echo "BASH_LINENO: ${BASH_LINENO[*]}" 
    echo "FUNCNAME: ${FUNCNAME[*]}" 
} 

function foobar() { 
    log "failed:" "[email protected]" 
} 

foobar "[email protected]" 
+1

我從來沒有想過那些是數組!如果我知道,我會早就想出一個不同的解決方案... 調用者是非常接近我想要的,雖然我nitpicky,並可能會更喜歡['文件名':'行']。但是,我當然可以接受呼叫者的服務。但是,因爲這些內置變量都是數組,所以我有(像你說的)我的堆棧跟蹤工作。我應該能夠從該功能中提取必要的信息。這將讓我回到函數調用,而不是變量宏。謝謝!!! – LousyG

+1

只需要注意:如果你想通過格式化來控制文件名:行,你可以在一個數組賦值中使用調用者,並單獨使用文件名和行:local fl =($(caller));回聲「錯誤$(fl [1]):$(fl [0])」 – Floyd

2

(注:這解決了報價的迫在眉睫的問題,但@ nosid的約 答案訪問調用堆棧更好)

更改您的_log定義咯,閱讀從標準輸入,而不是從 位置參數取日誌消息:

_log() { 
    # Set up _BASH_SOURCE and _LINENO the same way 

    cat <(echo -n "$(_BASH_SOURCE:$_LINENO) ") - 
} 

然後通過標準輸入使用這裏文檔或此處串通過您的日誌消息:

${LOG} <<<"This is a log message" 
${LOG} <<<"I'm thrilled this works, too!" 
${LOG} <<HERE 
Even this long 
message works as 
intended! 
HERE 
+0

感謝您的輸入!我可能會用nosid的解決方案,因爲我可以看到開發人員抱怨像「<<<」那樣微不足道的東西。但是,很高興知道有一個可用的影響較小的解決方案,如果需要的話。 – LousyG