Frage

Kennt jemand von Ressourcen, die über die besten Praktiken oder Design-Muster für die Shell-Skripten (sh, bash etc.)?

sprechen
War es hilfreich?

Lö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.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top