2012-10-10 14 views
31

Ich habe ein Shell-Skript, das ich mit ShUnit testen möchte. Das Skript (und alle Funktionen) befinden sich in einer einzigen Datei, da es die Installation viel einfacher macht.Importieren von Funktionen aus einem Shell-Skript

Beispiel für script.sh

#!/bin/sh 

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

code 

Ich wollte eine zweite Datei schreiben (das muss nicht verteilt und installiert werden), um die Funktionen in script.sh definiert testen

So etwas wie run_tests.sh

#!/bin/sh 

. script.sh 

# Unit tests 

Jetzt liegt das Problem in der . (oder source in Bash). Es analysiert nicht nur Funktionsdefinitionen, sondern führt auch den Code im Skript aus.

Da das Skript ohne Argumente tut nichts Schlechtes ich konnte

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

aber ich irrte, wenn es einen besseren Weg ist mein Ziel zu erreichen.

bearbeiten

Meine vorgeschlagene Abhilfe funktioniert nicht im Fall der Quellen-Skript ruft exit so dass ich zu stoppen haben die Ausfahrt

#!/bin/sh 

trap run_tests ERR EXIT 

run_tests() { 
    ... 
} 

. script.sh 

Die run_tests Funktion aufgerufen wird, aber sobald ich umleiten die Ausgabe des Quellbefehls die Funktionen im Skript werden nicht geparst und sind nicht in der Trap-Handler

Dies funktioniert, aber ich bekomme das outp ut von script.sh:

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

dieser Druck nicht den Ausgang, aber ich erhalte eine Fehlermeldung, dass die Funktion nicht definiert ist:

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

Dieser druckt nicht die Ausgabe und die run_tests Trap-Handler nicht aufgerufen überhaupt:

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

Antwort

39

nach dem „Shell Builtin Befehle“ des bash manpage, . aka source verwendet eine optionale Liste von Argumenten, die an das zu sendende Skript übergeben werden. Sie könnten das verwenden, um eine Do-Nothing-Option einzuführen. Zum Beispiel könnte script.sh sein:

#!/bin/sh 

foo() { 
    echo foo $1 
} 

main() { 
    foo 1 
    foo 2 
} 

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

und unit.sh sein könnte:

#!/bin/bash 

. ./script.sh --source-only 

foo 3 

Dann script.sh normal verhalten wird, und unit.sh wird der Zugriff auf alle Funktionen von script.sh haben aber den main() Code nicht aufrufen .

Bitte beachte, dass die zusätzlichen Argumente source sind nicht in POSIX, so /bin/sh könnte es-also nicht behandeln die #!/bin/bash zu Beginn des unit.sh.

+1

Vielleicht möchten Sie auf 'shift' die Argumentliste so' main' nicht mit dem '--source in Berührung kommt -only' –

+0

@HubertGrzeskowiak Guter Punkt, behoben. Danke für den Vorschlag! – andrewdotn

+1

Die 'shift' macht da keinen Sinn, da' main' nur dann läuft, wenn '--source-only' ** nicht ** das erste Argument ist. –

16

diese Technik aus Python Abgeholt, aber das Konzept funktioniert in bash oder einer anderen Schale ganz gut ...

Die Idee ist, dass wir den Hauptcodeabschnitt unseres Skript in eine Funktion einzuschalten. Dann setzen wir am Ende des Skripts eine 'if'-Anweisung, die diese Funktion nur aufruft, wenn wir das Skript ausgeführt haben, aber nicht, wenn wir sie aufgerufen haben. Dann rufen wir explizit die script() -Funktion von unserem Skript 'runtests' auf, das das Skript 'script' aufgerufen hat und somit alle seine Funktionen enthält.

Dies beruht auf der Tatsache, dass, wenn wir das Skript beziehen, die bash gewartete Umgebungsvariable $0, die den Namen des Skripts ist, wird der Name in der Aufruf (Eltern) Skript (runtests wird ausgeführt wird diesem Fall), nicht das Quellskript.

(Ich habe script.sh umbenannt nur script verursachen die .sh überflüssig ist und verwirrt mich. :-)

Im Folgenden die beiden Skripte sind. Einige Anmerkungen ...

  • [email protected] ausgewertet alle Argumente für die Funktion oder Skript als einzelne Strings übergeben. Wenn wir stattdessen $* verwenden, werden alle Argumente zu einer Zeichenfolge zusammengefügt.
  • Die RUNNING="$(basename $0)" ist erforderlich, da $0 immer unter mindestens das aktuelle Verzeichnispräfix wie in ./script enthält.
  • Der Test if [[ "$RUNNING" == "script" ]].... ist die Magie, die script verursacht, um die script() Funktion nur aufzurufen, wenn direkt von der Kommandozeile aus ausgeführt wurde.

Skript

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

Das hat zwei Nachteile: Wenn Sie das Skript umbenennen, müssen Sie seinen Inhalt ändern; und es funktioniert nicht mit Symlinks (nicht sicher über Aliase). –

3

Wenn Sie Bash verwenden, eine ähnliche Lösung zu @ andrewdotn Ansatz (aber ohne auf die eine zusätzliche Flagge oder je zu benötigen Skriptname) kann mit dem Array BASH_SOURCE durchgeführt werden.

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 
Verwandte Themen