2012-05-22 16 views
7

Ich habe eine Bash-Logging-Bibliothek geschrieben, um mit einigen komplexen Skripten implementiert werden, die meine Firma derzeit verwendet. Ich war todadset auf den Skript-Dateinamen ($ {BASH_SOURCE}) und die Zeilennummer ($ {LINENO}) des aufrufenden Skripts bei den Protokollaufrufen. Ich wollte mich jedoch nicht auf den Benutzer oder das Implementierungsscript verlassen müssen, um diese beiden Variablen als Parameter zu übergeben. Wenn dies C/C++ wäre, würde ich einfach ein Makro erstellen, das "__FILE__" und "__LINE__" der Parameterliste vorstellt.Bash-Parameter-Anführungszeichen und Eval

Ich konnte endlich diesen Teil arbeiten. Hier sind einige viel vereinfachte Abstracts als Proof of Concept:

Hier ist die Logging-Bibliothek:

# log.sh 

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

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

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

Und eine Implementierung Testskript:

# MyTest.sh 

. ./log.sh 

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

Das funktioniert ziemlich gut (und ich war begeistert, dass es zuerst funktioniert). Dies hat jedoch ein eklatantes Problem: Die Interaktion zwischen Anführungszeichen und eval. Wenn ich den Anruf mache:

${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 

Jetzt glaube ich, dass ich verstehe, warum das passiert. Die in Anführungszeichen gesetzten Parameter bleiben intakt, wenn sie an eval übergeben werden. Zu diesem Zeitpunkt wird der Inhalt unverändert in die resultierende Befehlszeichenfolge eingefügt. Ich weiß, dass ich das beheben kann, indem ich etwas entfliehe; aber ich WIRKLICH wollen nicht die Implementierungsskripts zu zwingen, dies zu tun haben. Bevor ich diese "eval macro" -Funktionalität implementiert habe, ließ ich Benutzer direkt zu "_log" aufrufen und erlaubte ihnen, optional "$ {LINENO}" zu übergeben. Mit dieser Implementierung funktionierte der obige fehlgeschlagene Aufruf (mit nur einem Satz in Anführungszeichen) einwandfrei.

Auf der untersten Ebene, alles, was ich wirklich will, ist für einen Skript in der Lage sein aufrufe [Funktion/Makro lügt] „String mit speziellem characers anmelden“ und die resultierenden Protokollmeldung enthalten hat die Dateinamen und Zeile Nummer des aufrufenden Skripts, gefolgt von der Protokollnachricht. Wenn es möglich ist, würde ich annehmen, dass ich sehr nahe bin, aber wenn etwas, das ich übersehe, etwas anderes erfordert, bin ich auch dafür offen. Ich kann die Benutzer nicht zwingen, allen ihren Nachrichten zu entkommen, da dies wahrscheinlich dazu führen wird, dass sie diese Bibliothek nicht benutzen. Das ist solch ein Problem, dass, wenn ich keine Lösung dafür finden kann, ich wahrscheinlich zu der alten Fähigkeit zurückkehren werde (die $ {LINENO} erforderte, um als Funktionsparameter übergeben zu werden - das war viel weniger aufdringlich).

TLDR: Gibt es eine Möglichkeit, eval dazu zu bringen, Sonderzeichen innerhalb eines in Anführungszeichen gesetzten Parameters zu respektieren, ohne ihnen entkommen zu müssen?

+0

Wenn Sie Bash-spezifische Konstrukte verwenden, sollte Ihr Logging-Skript nicht "log.sh" heißen. Es sollte "log.bash" sein. –

+0

@WilliamPursell - Ich habe ein wenig gesucht und konnte keinen Standard bezüglich der Namenskonventionen von Shell-Skripten finden. Tatsächlich scheinen die meisten Leute ein Suffix ganz zu verlassen. Um ehrlich zu sein habe ich noch nie etwas anderes als '\ *. Sh' (oder ein Skript ohne Erweiterung/Suffix) gesehen. Und praktisch sollte es keine Auswirkungen haben, oder? Der Interpreter wird entweder aus der ersten Zeile des Skripts gezogen oder die aktuelle Shell wird verwendet. Es scheint, dass es im Wesentlichen nur auf persönliche Vorlieben ankommt. Aber vielleicht kann "\ *. Sh" für einen Benutzer bedeuten, dass das Skript "sh" verwendet. – LousyG

+1

Die einzige praktische Auswirkung wäre, wenn ein Entwickler Ihre Funktionen mit einer anderen Shell verwendet. Mit einem * .sh-Namen würde ein Entwickler vernünftigerweise annehmen, dass Ihre Bibliothek in einem Zsh-Skript bezogen werden kann. –

Antwort

12

Ich empfehle, eval zu vermeiden, wenn möglich. Für Ihren Anwendungsfall für die Protokollierung können Sie sich die Shell caller ansehen. Wenn Sie weitere Informationen benötigen, können Sie die Variablen BASH_SOURCE, BASH_LINENO und FUNCNAME verwenden. Beachten Sie, dass alle diese Variablen Arrays sind und den vollständigen Aufruf-Stack enthalten.Siehe das folgende Beispiel:

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

Ich hatte nie eine Ahnung, dass das Arrays waren! Hätte ich das gewusst, hätte ich vor langer Zeit eine andere Lösung gefunden ... Anrufer ist ziemlich nah an dem, was ich will, obwohl ich pingelig bin und wahrscheinlich ['filename': 'line'] bevorzugen würde. Allerdings könnte ich sicherlich mit dem leben, was der Anrufer hat. Da jedoch diese integrierten Variablen alle Arrays sind, habe ich (wie Sie sagten) meine Stack-Trace zu arbeiten. Ich sollte in der Lage sein, die notwendigen Informationen von dort innerhalb der Funktion herauszuholen. Dadurch kann ich auf Funktionsaufrufe anstelle von Variablenmakros zurückgreifen. Vielen Dank!!! – LousyG

+1

Nur ein Hinweis: Wenn Sie Kontrolle über die Formatierung auf Dateiname: Zeile haben möchten, können Sie Anrufer in einer Array-Zuweisung verwenden und Dateiname und Zeile getrennt verwenden: local fl = ($ (caller)); echo "Fehler bei $ (fl [1]): $ (fl [0])" – Floyd

2

(Anmerkung: diese das unmittelbare Problem zu zitieren löst, aber @ nosid Antwort über den Call-Stack Zugriff ist viel besser)

ändern Ihre Definition von _log leicht zu lesen, die Protokollmeldung von Positionsparametern von der Standardeingabe statt unter:

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

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

Dann Ihre Log-Nachricht über die Standardeingabe ein hier doc oder eine hier Zeichenfolge mit übergeben:

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

Danke für die Eingabe! Ich werde wahrscheinlich mit Nosids Lösung gehen, da ich sehen konnte, dass Entwickler sich über etwas, das so unbedeutend ist wie "<<<", beschwerten. Aber es ist schön zu wissen, dass es eine Lösung mit weniger Auswirkungen gibt, falls ich sie brauche. – LousyG