Design-Muster oder Best Practices für die Shell-Skripten [geschlossen]
-
09-06-2019 - |
Frage
Kennt jemand von Ressourcen, die über die besten Praktiken oder Design-Muster für die Shell-Skripten (sh, bash etc.)?
sprechenLösung
Ich schrieb ziemlich komplexe Shell-Skripten und mein erster Vorschlag ist „nicht“. Der Grund dafür ist, dass ist ziemlich einfach, einen kleinen Fehler zu machen, dass Ihr Skript behindert oder sogar gefährlich machen.
Das heißt, ich habe keine andere Ressourcen, die Sie aber meine persönliche Erfahrung weitergeben. Hier ist, was ich normalerweise tun, was übertrieben ist, neigt aber dazu, fest zu sein, obwohl sehr ausführlich.
Invocation
macht Ihren Skript Long- und Short-Optionen akzeptieren. vorsichtig sein, da es zwei Befehle sind Optionen, getopt und getopts zu analysieren. Verwenden Sie getopt, wie Sie weniger Schwierigkeiten gegenüber.
CommandLineOptions__config_file=""
CommandLineOptions__debug_level=""
getopt_results=`getopt -s bash -o c:d:: --long config_file:,debug_level:: -- "$@"`
if test $? != 0
then
echo "unrecognized option"
exit 1
fi
eval set -- "$getopt_results"
while true
do
case "$1" in
--config_file)
CommandLineOptions__config_file="$2";
shift 2;
;;
--debug_level)
CommandLineOptions__debug_level="$2";
shift 2;
;;
--)
shift
break
;;
*)
echo "$0: unparseable option $1"
EXCEPTION=$Main__ParameterException
EXCEPTION_MSG="unparseable option $1"
exit 1
;;
esac
done
if test "x$CommandLineOptions__config_file" == "x"
then
echo "$0: missing config_file parameter"
EXCEPTION=$Main__ParameterException
EXCEPTION_MSG="missing config_file parameter"
exit 1
fi
Ein weiterer wichtiger Punkt ist, dass ein Programm immer Null zurückkehren soll, wenn erfolgreich abgeschlossen wird, nicht Null, wenn etwas schief gelaufen ist.
Funktionsaufrufe
Sie können Funktionen in bash nennen, nur nicht vergessen, sie vor dem Aufruf zu definieren. Funktionen wie Skripte sind, können sie nur numerische Werte zurückgeben. Dies bedeutet, dass Sie eine andere Strategie erfinden müssen, um String-Werte zurück. Meine Strategie ist eine Variable namens RESULT zu verwenden, um das Ergebnis zu speichern, und die Rückkehr 0, wenn die Funktion sauber abgeschlossen. Sie können aber auch Ausnahmen auslösen, wenn Sie einen anderen Wert als Null zurückkehrt, und legen Sie zwei „Ausnahmevariablen“. (Meine: AUSNAHME und EXCEPTION_MSG), die erste den Ausnahmetyp enthält, und die zweite ist eine vom Menschen lesbare Nachricht
Wenn Sie eine Funktion aufrufen, die Parameter der Funktion der speziellen zugeordnet sind Vars $ 0, $ etc. 1 Ich schlage vor, Sie sie in sinnvoller Namen zu setzen. deklarieren die Variablen innerhalb der Funktion als lokale:
function foo {
local bar="$0"
}
Fehler trächtige Situationen
In der bash, wenn Sie nichts anderes zu erklären, eine unset Variable wird als eine leere Zeichenfolge verwendet. Das ist sehr gefährlich bei Tippfehlern, da der schlecht getippt Variable wird nicht berichtet werden, und es wird als leer bewertet werden. verwenden
set -o nounset
verhindern, dass dies geschieht. Seien Sie aber vorsichtig, denn wenn Sie das tun, wird abgebrochen, das Programm jedes Mal, wenn eine undefinierte Variable auswerten. Aus diesem Grunde zu prüfen die einzige Möglichkeit, wenn eine Variable nicht definiert ist ist die folgende:
if test "x${foo:-notset}" == "xnotset"
then
echo "foo not set"
fi
Sie können Variablen deklarieren als Nur-Lese-:
readonly readonly_var="foo"
Modularisierung
Sie können „Python wie“ Modularisierung erreichen, wenn Sie den folgenden Code verwenden:
set -o nounset
function getScriptAbsoluteDir {
# @description used to get the script path
# @param $1 the script $0 parameter
local script_invoke_path="$1"
local cwd=`pwd`
# absolute path ? if so, the first character is a /
if test "x${script_invoke_path:0:1}" = 'x/'
then
RESULT=`dirname "$script_invoke_path"`
else
RESULT=`dirname "$cwd/$script_invoke_path"`
fi
}
script_invoke_path="$0"
script_name=`basename "$0"`
getScriptAbsoluteDir "$script_invoke_path"
script_absolute_dir=$RESULT
function import() {
# @description importer routine to get external functionality.
# @description the first location searched is the script directory.
# @description if not found, search the module in the paths contained in $SHELL_LIBRARY_PATH environment variable
# @param $1 the .shinc file to import, without .shinc extension
module=$1
if test "x$module" == "x"
then
echo "$script_name : Unable to import unspecified module. Dying."
exit 1
fi
if test "x${script_absolute_dir:-notset}" == "xnotset"
then
echo "$script_name : Undefined script absolute dir. Did you remove getScriptAbsoluteDir? Dying."
exit 1
fi
if test "x$script_absolute_dir" == "x"
then
echo "$script_name : empty script path. Dying."
exit 1
fi
if test -e "$script_absolute_dir/$module.shinc"
then
# import from script directory
. "$script_absolute_dir/$module.shinc"
elif test "x${SHELL_LIBRARY_PATH:-notset}" != "xnotset"
then
# import from the shell script library path
# save the separator and use the ':' instead
local saved_IFS="$IFS"
IFS=':'
for path in $SHELL_LIBRARY_PATH
do
if test -e "$path/$module.shinc"
then
. "$path/$module.shinc"
return
fi
done
# restore the standard separator
IFS="$saved_IFS"
fi
echo "$script_name : Unable to find module $module."
exit 1
}
Sie können dann importieren Dateien mit der Endung .shinc mit folgenden Syntax
Import "aModule / module"
Welche in SHELL_LIBRARY_PATH gesucht wird. Wie Sie immer im globalen Namespace importieren, denken Sie daran mit einem richtigen Präfix alle Funktionen und Variablen Präfix, sonst riskieren Sie Namenskonflikte. Ich benutze Doppelstrich als Python-Punkt.
Auch setzen dies als erstes in Ihrem Modul
# avoid double inclusion
if test "${BashInclude__imported+defined}" == "defined"
then
return 0
fi
BashInclude__imported=1
Objektorientierte Programmierung
In der bash, können Sie die objektorientierte Programmierung nicht tun, wenn Sie ein recht komplexes System der Zuordnung von Objekten zu bauen (ich dachte darüber nach. Es ist machbar, aber verrückt). In der Praxis kann man jedoch tun „Singleton orientierte Programmierung“: Sie haben eine Instanz jedes Objekt, und nur ein
.Was ich tue, ist: Ich sehe ein Objekt in einem Modul (die Modularisierung Eintrag sehen). Dann definieren I leer Vars (analog Elementvariablen) eine Funktion init (Konstruktor) und Elementfunktionen, wie in diesem Beispiel Code
# avoid double inclusion
if test "${Table__imported+defined}" == "defined"
then
return 0
fi
Table__imported=1
readonly Table__NoException=""
readonly Table__ParameterException="Table__ParameterException"
readonly Table__MySqlException="Table__MySqlException"
readonly Table__NotInitializedException="Table__NotInitializedException"
readonly Table__AlreadyInitializedException="Table__AlreadyInitializedException"
# an example for module enum constants, used in the mysql table, in this case
readonly Table__GENDER_MALE="GENDER_MALE"
readonly Table__GENDER_FEMALE="GENDER_FEMALE"
# private: prefixed with p_ (a bash variable cannot start with _)
p_Table__mysql_exec="" # will contain the executed mysql command
p_Table__initialized=0
function Table__init {
# @description init the module with the database parameters
# @param $1 the mysql config file
# @exception Table__NoException, Table__ParameterException
EXCEPTION=""
EXCEPTION_MSG=""
EXCEPTION_FUNC=""
RESULT=""
if test $p_Table__initialized -ne 0
then
EXCEPTION=$Table__AlreadyInitializedException
EXCEPTION_MSG="module already initialized"
EXCEPTION_FUNC="$FUNCNAME"
return 1
fi
local config_file="$1"
# yes, I am aware that I could put default parameters and other niceties, but I am lazy today
if test "x$config_file" = "x"; then
EXCEPTION=$Table__ParameterException
EXCEPTION_MSG="missing parameter config file"
EXCEPTION_FUNC="$FUNCNAME"
return 1
fi
p_Table__mysql_exec="mysql --defaults-file=$config_file --silent --skip-column-names -e "
# mark the module as initialized
p_Table__initialized=1
EXCEPTION=$Table__NoException
EXCEPTION_MSG=""
EXCEPTION_FUNC=""
return 0
}
function Table__getName() {
# @description gets the name of the person
# @param $1 the row identifier
# @result the name
EXCEPTION=""
EXCEPTION_MSG=""
EXCEPTION_FUNC=""
RESULT=""
if test $p_Table__initialized -eq 0
then
EXCEPTION=$Table__NotInitializedException
EXCEPTION_MSG="module not initialized"
EXCEPTION_FUNC="$FUNCNAME"
return 1
fi
id=$1
if test "x$id" = "x"; then
EXCEPTION=$Table__ParameterException
EXCEPTION_MSG="missing parameter identifier"
EXCEPTION_FUNC="$FUNCNAME"
return 1
fi
local name=`$p_Table__mysql_exec "SELECT name FROM table WHERE id = '$id'"`
if test $? != 0 ; then
EXCEPTION=$Table__MySqlException
EXCEPTION_MSG="unable to perform select"
EXCEPTION_FUNC="$FUNCNAME"
return 1
fi
RESULT=$name
EXCEPTION=$Table__NoException
EXCEPTION_MSG=""
EXCEPTION_FUNC=""
return 0
}
Trapping und Handhabungs Signale
Ich fand diese nützlichen Ausnahmen zu fangen und zu behandeln.
function Main__interruptHandler() {
# @description signal handler for SIGINT
echo "SIGINT caught"
exit
}
function Main__terminationHandler() {
# @description signal handler for SIGTERM
echo "SIGTERM caught"
exit
}
function Main__exitHandler() {
# @description signal handler for end of the program (clean or unclean).
# probably redundant call, we already call the cleanup in main.
exit
}
trap Main__interruptHandler INT
trap Main__terminationHandler TERM
trap Main__exitHandler EXIT
function Main__main() {
# body
}
# catch signals and exit
trap exit INT TERM EXIT
Main__main "$@"
Tipps und Tricks
Wenn etwas aus irgendeinem Grund nicht funktioniert, versuchen Sie den Code neu zu ordnen. Die Reihenfolge ist wichtig und nicht immer intuitiv.
nicht der Ansicht, selbst mit tcsh arbeiten. es bietet keine Unterstützung für Funktionen, und es ist im Allgemeinen schrecklich.
Hoffe, es hilft, obwohl zu beachten. Wenn Sie die Art von Dingen verwenden müssen, schrieb ich here, es bedeutet, dass Ihr Problem zu komplex ist, mit Schale gelöst werden. verwenden Sie eine andere Sprache. Ich hatte, es zu benutzen durch menschliche Faktoren und Vermächtnis.
Andere Tipps
Werfen Sie einen Blick auf die Erweiterte Bash-Scripting-Handbuch für eine viel Weisheit auf Shell Scripting - nicht nur Bash, auch nicht.
Hören Sie nicht auf Menschen, die Sie sagen, in anderen zu suchen, wohl komplexere Sprachen. Wenn Shell-Scripting Ihren Bedürfnissen entspricht, verwenden Sie das. Sie wollen Funktionalität, nicht fanciness. Neue Sprachen liefern wertvolle neue Fähigkeiten für Ihren Lebenslauf, aber das hilft nicht, wenn Sie die Arbeit, die getan werden muss, und Sie bereits wissen, Shell.
Wie bereits erwähnt, gibt es nicht viele „Best Practices“ oder „Design Patterns“ für Shell-Skripten. Verschiedene Anwendungen haben unterschiedliche Richtlinien und Bias - wie jede andere Programmiersprache.
Shell-Skript ist eine Sprache entwickelt, um Dateien und Prozesse zu manipulieren. Während es für die große, es ist nicht ein Allzweck-Sprache, so immer versuchen, Logik aus vorhandenen Dienstprogramme zu kleben, anstatt neue Logik in Shell-Skript neu zu erstellen.
Anders als das allgemeine Prinzip, das ich gesammelt habe einige gemeinsame Schale Skript Fehler .
Es gab eine große Sitzung auf der OSCON in diesem Jahr (2008) auf nur dieses Thema: http://assets.en.oreilly.com/1/event/12/Shell%20Scripting%20Craftsmanship%20Presentation%201.pdf
Ganz einfach: verwenden Python anstelle von Shell-Skripten. Sie erhalten eine nahezu 100-fache Steigerung readablility, ohne etwas zu erschweren, die Sie nicht benötigen, und die Erhaltung der Fähigkeit, Teile des Skripts in Funktionen, Objekte, persistente Objekte (zodb), verteilte Objekte (pyro) fast ohne sich zu entwickeln Extra-Code.
verwenden gesetzt -e, so dass Sie pflügen nicht vorwärts nach Fehlern. Versuchen Sie, es, ohne sich auf bash kompatibel sh wenn Sie es auf nicht-Linux ausgeführt werden soll.
Wissen, wann es zu benutzen. Für eine schnelle und schmutzige Verleimung Befehle zusammen, es ist okay. Wenn Sie mehr als ein paar nicht-triviale Entscheidungen treffen müssen, Schleifen, etwas, gehen Sie für Python, Perl und modularisieren .
Das größte Problem mit Schale ist oft, dass Endergebnis sieht aus wie eine große Kugel aus Schlamm, 4000 Linien von bash und wächst ... und Sie können nicht loswerden es denn jetzt dein ganzes Projekt davon abhängt. Natürlich es bei 40 Linien begann der schönen bash.
Um einig "Best Practices" zu finden, schauen, wie Linux Distributionen (zum Beispiel Debian) schreiben ihren init-Skripte (in der Regel in /etc/init.d gefunden)
Die meisten von ihnen sind ohne „bash-isms“ und haben eine gute Trennung von Konfigurationseinstellungen, Bibliothek-Dateien und Source-Formatierung.
Mein persönlicher Stil ist ein Master-Shellscript zu schreiben, das einige Standardgrößen definiert, und dann versucht ( „Quelle“), um eine Konfigurationsdatei zu laden, die neuen Werte enthalten.
Ich versuche, Funktionen zu vermeiden, da sie das Skript komplizierter machen neigen. (Perl wurde zu diesem Zweck erstellt.)
Um sicherzustellen, dass das Skript tragbar ist, Test nicht nur mit #! / Bin / sh, aber auch #! / Bin / ash, #! / Bin / dash verwenden usw. Sie werden die Bash-spezifischen Code beschmutzen bald genug.
oder das ältere Zitat ähnlich dem, was Joao sagte:
"Verwenden Sie Perl. Sie wollen bash wissen, aber es nicht benutzen."
Leider habe ich vergessen, wer das gesagt.
Und ja in diesen Tagen würde ich Python über Perl empfehlen.