2012-05-22 15 views
7

Napisałem bibliotekę rejestrowania bash, która ma być implementowana za pomocą złożonych skryptów, z których obecnie korzysta moja firma. Zaniepokoiłem się dostarczaniem nazwy skryptu ($ {BASH_SOURCE}) i numerem linii ($ {LINENO}) skryptu wywołującego podczas wykonywania wywołań dziennika. Jednak nie chciałem polegać na użytkowniku lub implementowaniu skryptu, aby przekazać te dwie zmienne jako parametry. Jeśli był to C/C++, po prostu utworzyłbym makro, które dodaje "__FILE__" i "__LINE__" do listy parametrów.Cytaty parametrów Bash i eval

W końcu udało mi się uruchomić tę część. Oto niektóre znacznie uproszczone streszczenia jako dowód pojęcia:

oto biblioteka rejestrowania:

# log.sh 

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

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

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

i wdrażaniu skrypt testowy:

# MyTest.sh 

. ./log.sh 

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

Działa to całkiem dobrze (i ja był zachwycony, że początkowo działał). Ma to jednak jeden rażący problem: interakcje między cytatami i ewaluacją. Jeśli zadzwonię:

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

Teraz wierzę, że rozumiem, dlaczego tak się dzieje. Przytoczone parametry pozostają nienaruszone, gdy są przekazywane do eval, ale w tym momencie zawartość jest umieszczana jako wynik w wynikowym łańcuchu poleceń. Wiem, że mogę to naprawić, robiąc trochę ucieczki; jednak nie chcę wymuszać na skryptach implementacji, aby to zrobić. Zanim zaimplementowałem tę funkcję "eval macro", kazałem użytkownikom nawiązywać połączenia bezpośrednio do "_log" i pozwalałem im opcjonalnie przekazywać "$ {LINENO}". Dzięki tej implementacji, niepowodzenie połączenia powyżej (tylko z cytowanym zdaniem) działało dobrze.

Na najbardziej podstawowym poziomie, wszystko, czego naprawdę chcę, to skrypt, aby móc wywołać [funkcja/makra dziennika] "Ciąg do logowania ze specjalnymi znakami" i wynikowy komunikat dziennika zawiera nazwę pliku i linię numer skryptu wywołującego, a następnie komunikat dziennika. Jeśli jest to możliwe, zakładam, że jestem bardzo blisko, ale jeśli jest coś, co przeoczyłem, wymagałoby innego podejścia, jestem także otwarty na to. Nie mogę zmusić użytkowników do ucieczki od wszystkich ich wiadomości, ponieważ prawdopodobnie spowoduje to, że nie będą używać tej biblioteki. Jest to taki problem, że jeśli nie znajdę rozwiązania tego problemu, prawdopodobnie powrócę do starej możliwości (która wymagała podania parametru $ {LINENO} jako parametru funkcji - było to znacznie mniej uciążliwe).

TLDR: Czy istnieje sposób na to, aby eval szanował znaki specjalne w cytowanym parametrze, bez konieczności ucieczki od nich?

+0

Jeśli używasz konstruktów specyficznych dla basha, twój skrypt logowania nie powinien być nazywany "log.sh". Powinien to być "log.bash". –

+0

@WilliamPursell - Zrobiłem trochę wyszukiwania i nie mogłem znaleźć żadnego standardu dotyczącego konwencji nazewniczych skryptów powłoki. W rzeczywistości większość osób wydaje się całkowicie pominąć sufiks. Szczerze mówiąc, nigdy nie widziałem czegoś innego niż "\ *. Sh" (lub skrypt bez rozszerzenia/sufiksu). I przy całej praktyczności nie powinno to mieć żadnego wpływu, prawda? Interpreter jest albo pobierany z pierwszej linii skryptu, albo używana jest twoja obecna powłoka. Wygląda na to, że po prostu sprowadza się to do osobistych preferencji. Ale być może "\ *. Sh" może sugerować użytkownikowi, że skrypt używa "sh". – LousyG

+1

Jedyny praktyczny wpływ będzie, jeśli programista używa twoich funkcji z inną powłoką. Z nazwą "* .sh" programiści rozsądnie założyliby, że twoja biblioteka może pochodzić ze skryptu zsh. –

Odpowiedz

12

Zaleca się unikanie, jeśli to możliwe, eval. Dla twojego przypadku użycia logowania możesz rzucić okiem na powłokę wbudowaną caller. Jeśli potrzebujesz więcej informacji, możesz użyć zmiennych BASH_SOURCE, i . Zauważ, że wszystkie te zmienne są tablicami i zawierają pełny stos wywołań.Zobacz poniższy przykład:

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

Nigdy nie miałem pojęcia, że ​​to tablice! Gdybym to wiedział, już dawno wymyśliłbym inne rozwiązanie ... Dzwoniący jest bardzo blisko tego, co chcę, chociaż jestem dziwaczny i prawdopodobnie wolałabym ['filename': 'line']. Jednak z pewnością mógłbym żyć z tym, co dzwoniący ma. JEDNAKŻE, ponieważ te wbudowane zmienne są tablicami, mam (jak powiedziałeś) mój ślad stosu do pracy. Powinienem być w stanie wyciągnąć potrzebne informacje z tej funkcji. Pozwoli mi to powrócić do wywołań funkcji zamiast makr zmiennych. Dziękuję Ci!!! – LousyG

+1

Uwaga: jeśli chcesz kontrolować formatowanie do nazwy pliku: linia, możesz użyć wywołującego w przypisaniu do tablicy i użyć nazwy pliku i wiersza oddzielnie: local fl = ($ (caller)); echo "error at $ (fl [1]): $ (fl [0])" – Floyd

2

(Uwaga: to rozwiązuje natychmiastowego problem cytowanie, ale na @ nosid odpowiedź o dostęp do stosu wywołań jest znacznie lepiej)

Zmień swoje definicja _log lekko, aby przeczytać ze standardowego wejścia zamiast biorąc wiadomości dziennika od parametrów pozycyjnych:

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

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

Następnie przekazać wiadomość dziennika poprzez standardowe wejście używając tutaj doc lub tutaj ciąg:

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

Dzięki za wejście! Prawdopodobnie pójdę z rozwiązaniem nosid, ponieważ widziałem programistów narzekających na coś tak nieznaczącego jak "<<<". Miło jest jednak wiedzieć, że istnieje rozwiązanie o mniejszym dostępnym dostępie, gdybym go potrzebował. – LousyG