2012-10-10 46 views
31

Tengo un script de shell que me gustaría probar con shUnit. El script (y todas las funciones) están en un único archivo, ya que facilita la instalación.Importación de funciones desde un script de shell

Ejemplo para script.sh

#!/bin/sh 

foo() { ... } 
bar() { ... } 

code 

que quería escribir un segundo archivo (que no tiene que ser distribuido e instalado) para probar las funciones definidas en script.sh

Algo así como run_tests.sh

#!/bin/sh 

. script.sh 

# Unit tests 

Ahora el problema está en el . (o source en Bash). No solo analiza las definiciones de funciones sino que también ejecuta el código en el script.

Dado que la secuencia de comandos sin argumentos no hace nada mal que pude

. script.sh > /dev/null 2>&1 

pero yo estaba vagando si hay una mejor manera de lograr mi objetivo.

Editar

Mi solución propuesta no trabajar en el caso de que el script de origen llama exit así que tengo controlar la salida

#!/bin/sh 

trap run_tests ERR EXIT 

run_tests() { 
    ... 
} 

. script.sh 

La función run_tests se llama pero tan pronto como vuelvo a dirigir la salida del comando fuente las funciones en el script no se analizan y no están disponibles en el controlador de trampa

Esto funciona pero obtengo el outp UT de script.sh:

#!/bin/sh 
trap run_tests ERR EXIT 
run_tests() { 
    function_defined_in_script_sh 
} 
. script.sh 

Esto no imprime la salida, pero me da un error que la función no está definida:

#!/bin/sh 
trap run_tests ERR EXIT 
run_tests() { 
    function_defined_in_script_sh 
} 
. script.sh | grep OUTPUT_THAT_DOES_NOT_EXISTS 

Esto no imprime la salida y el manejador run_tests trampa no se llama en absoluto:

#!/bin/sh 
trap run_tests ERR EXIT 
run_tests() { 
    function_defined_in_script_sh 
} 
. script.sh > /dev/null 

Respuesta

39

de acuerdo con la sección “interna del shell de comandos” de la bash manpage, . aka source toma una lista opcional de argumentos que se pasan al script que se obtiene. Podría usar eso para introducir una opción de no hacer nada. Por ejemplo, script.sh podrían ser:

#!/bin/sh 

foo() { 
    echo foo $1 
} 

main() { 
    foo 1 
    foo 2 
} 

if [ "${1}" != "--source-only" ]; then 
    main "${@}" 
fi 

y unit.sh podrían ser:

#!/bin/bash 

. ./script.sh --source-only 

foo 3 

Entonces script.sh se comportará normalmente, y unit.sh tendrá acceso a todas las funciones de script.sh pero no invocará el código main() .

Tenga en cuenta que los argumentos adicionales a source no están en POSIX, por lo /bin/sh no podría manejar la situación, de ahí el #!/bin/bash al comienzo de unit.sh.

+1

Es posible que desee 'shift' la lista de argumentos de modo' main' no ponerse en contacto con el '--source -only' –

+0

@HubertGrzeskowiak Buen punto, arreglado. ¡Gracias por la sugerencia! – andrewdotn

+1

El 'shift' no tiene sentido allí, porque' main' solo se ejecuta si '--source-only' es ** not ** el primer argumento. –

16

recogió esta técnica desde Python, pero el concepto funciona bien en bash o cualquier otro tejido ...

La idea es que nos volvemos la sección de código principal de nuestra secuencia de comandos en una función. Luego, al final del script, colocamos una declaración 'if' que solo llamará a esa función si ejecutamos el script, pero no si lo originamos. A continuación, llamamos explícitamente a la función script() de nuestro script 'runtests' que ha obtenido el script 'script' y por lo tanto contiene todas sus funciones.

Esto se basa en el hecho de que si nos fuente del script, la variable de entorno bash-mantenido $0, que es el nombre del script que se está ejecutando, será el nombre de la llamando guión (padre) (runtests en este caso), no el script de origen.

(I script.sh haya cambiado el nombre a simplemente hacer que el script.sh es redundante y me confunde. :-)

A continuación se presentan los dos guiones. Algunas notas ...

  • [email protected] evalúa a todos los argumentos que se pasan a la función o la escritura como cadenas individuales. Si, en cambio, usamos $*, todos los argumentos se concatenarían juntos en una cadena.
  • Se requiere RUNNING="$(basename $0)" ya que $0 siempre incluye en menos el prefijo de directorio actual que en ./script.
  • La prueba if [[ "$RUNNING" == "script" ]].... es la magia que causa script para llamar a la función script() solo si script se ejecutó directamente desde la línea de comandos.

guión

#!/bin/bash 

foo() { echo "foo()"; } 

bar() { echo "bar()"; } 

script() { 
    ARG1=$1 
    ARG2=$2 
    # 
    echo "Running '$RUNNING'..." 
    echo "script() - all args: [email protected]" 
    echo "script() -  ARG1: $ARG1" 
    echo "script() -  ARG2: $ARG2" 
    # 
    foo 
    bar 
} 

RUNNING="$(basename $0)" 

if [[ "$RUNNING" == "script" ]] 
then 
    script "[email protected]" 
fi 

runtests

#!/bin/bash 

source script 

# execute 'script' function in sourced file 'script' 
script arg1 arg2 arg3 
+1

Esto tiene dos inconvenientes: cada vez que cambia el nombre del guión, debe cambiar su contenido; y no funciona con enlaces simbólicos (no estoy seguro de los alias). –

3

Si está utilizando Bash, una solución similar al enfoque de @ andrewdotn (pero sin necesidad de una bandera adicional o dependiendo de la nombre de script) se puede lograr utilizando BASH_SOURCE matriz.

script.sh:

#!/bin/bash 

foo() { ... } 
bar() { ... } 

main() { 
    code 
} 

if [[ "${#BASH_SOURCE[@]}" -eq 1 ]]; then 
    main "[email protected]" 
fi 

run_tests.sh:

#!/bin/bash 

. script.sh 

# Unit tests 
Cuestiones relacionadas