2012-05-22 29 views
7

He escrito una biblioteca de registro de bash que se implementará con algunos scripts complejos que mi compañía está utilizando actualmente. No me gusta proporcionar el nombre de archivo del script ($ {BASH_SOURCE}) y el número de línea ($ {LINENO}) del script de llamada al hacer las llamadas de registro. Sin embargo, no quería tener que depender del usuario o implementar el script para pasar estas dos variables como parámetros. Si esto fuera C/C++, simplemente crearía una macro que precede "__FILE__" y "__LINE__" a la lista de parámetros.Citas de parámetros de Bash y eval

Finalmente pude hacer que esta pieza funcionara. Aquí están algunos extractos mucho-simplificados como una prueba de concepto:

Aquí está la biblioteca de registro:

# log.sh 

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

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

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

Y un script de prueba de aplicación:

# MyTest.sh 

. ./log.sh 

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

Esto funciona bastante bien (y, me estaba encantado de hacerlo funcionar al principio). Sin embargo, esto tiene un problema evidente: la interacción entre presupuestos y eval. Si realizo la llamada:

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

Ahora, creo que entiendo por qué sucede esto. Los parámetros citados se mantienen intactos a medida que se pasan a eval, pero en ese punto, el contenido se coloca tal cual en la cadena de comando resultante. Sé que puedo arreglar esto escapando un poco; sin embargo, I REALMENTE no quiero obligar a los scripts de implementación a tener que hacer esto. Antes de implementar esta capacidad de "evaluación macro", hacía que los usuarios realizaran llamadas directamente a "_log" y les permitía pasar opcionalmente "$ {LINENO}". Con esta implementación, la llamada de falla anterior (con solo una oración entrecomillada) funcionó bien.

En el nivel más básico, todo lo que realmente quiero es una secuencia de comandos para poder llamar [función log/macro] "Cadena para conectarse con characers especiales" y tienen el mensaje de registro resultante contendrá el nombre del archivo y la línea número de la secuencia de comandos de llamada, seguido del mensaje de registro. Si es posible, asumiría que estoy muy cerca, pero si hay algo que estoy pasando por alto que requeriría un enfoque diferente, estoy abierto a eso también. No puedo obligar a los usuarios a escapar de todos sus mensajes, ya que es probable que no utilicen esta biblioteca. Este es un problema tal que si no puedo encontrar una solución a esto, es probable que regrese a la capacidad anterior (que requería que $ {LINENO} pasara como parámetro de función; esto era mucho menos intrusivo).

TLDR: ¿Hay alguna forma de que eval respete los caracteres especiales dentro de un parámetro entre comillas, sin tener que escapar de ellos?

+0

Si utiliza construcciones específicas de bash, su secuencia de comandos de inicio de sesión no se debe llamar "log.sh". Debería ser "log.bash". –

+0

@WilliamPursell - Realicé algunas búsquedas y no pude encontrar ningún tipo de estándar con respecto a las convenciones de nomenclatura de los scripts de shell. De hecho, la mayoría de la gente parece dejar un sufijo por completo. Para ser honesto, nunca he visto nada más que '\ *. Sh' (o un script sin extensión/sufijo). Y con toda la practicidad, no debería tener ningún impacto, ¿verdad? El intérprete se extrae de la primera línea del script o se usa su shell actual. Parece que básicamente se reduce a las preferencias personales. Pero, tal vez "\ *. Sh" puede implicar para un usuario que el script usa "sh". – LousyG

+1

El único impacto práctico sería si un desarrollador está utilizando sus funciones con un shell diferente. Con un nombre '* .sh', un desarrollador supondría razonablemente que su biblioteca puede obtenerse en un script zsh. –

Respuesta

12

Recomiendo evitar eval si es posible. Para su caso de uso de registro, puede echar un vistazo al shell builtin caller. Si necesita más información, puede usar las variables BASH_SOURCE, BASH_LINENO y FUNCNAME. Tenga en cuenta que todas estas variables son matrices y contienen la pila de llamadas completa.Véase el siguiente ejemplo:

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

¡Nunca tuve idea de que esas eran matrices! Si lo hubiera sabido, hace mucho tiempo habría llegado a una solución diferente ... La persona que llama está bastante cerca de lo que quiero, aunque soy quisquillosa y probablemente prefiera ['nombre de archivo': 'línea']. Sin embargo, sin duda podría vivir con lo que tiene la persona que llama. SIN EMBARGO, porque esas variables incorporadas son todas las matrices, tengo (como dijiste) mi rastro de pila para trabajar. Debería poder extraer la información necesaria desde allí dentro de la función. Eso me permitirá volver a llamar a funciones en lugar de macros variables. ¡¡¡Gracias!!! – LousyG

+1

Solo una nota: si desea controlar el formato del nombre del archivo: línea, puede usar el llamador en una asignación de matriz y usar el nombre de archivo y la línea por separado: local fl = ($ (llamador)); echo "error en $ (fl [1]): $ (fl [0])" – Floyd

2

(Nota: esto resuelve el problema inmediato de citar, pero la respuesta de @ nosid sobre acceder a la pila de llamadas es mucho mejor)

Cambiar su definición de _log ligeramente, para leer de la entrada estándar en lugar de tomar el mensaje de registro de parámetros posicionales:

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

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

a continuación, pasar su mensaje de registro a través de la entrada estándar usando un doc aquí o aquí cadena:

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

¡Gracias por la entrada! Probablemente vaya con la solución de nosid, ya que podría ver a los desarrolladores quejarse de algo tan insignificante como "<<<". Pero, es bueno saber que hay una solución con menos impacto disponible, en caso de que la necesite. – LousyG

Cuestiones relacionadas