Ist es üblich, größere Skripte in mehrere Skripte aufzuteilen und diese im Hauptskript zu verwenden?

22

Im Moment entwickle ich ein größeres Bash-Skript (es ist ein Open Source-Projekt von mir) und es wird langsam zu einem Chaos. Ich habe die Logik in Funktionen aufgeteilt, lokale Variablen verwendet, wo ich kann, und nur eine Handvoll globaler Variablen deklariert. Trotzdem wird es ziemlich schwer zu warten.

Ich dachte darüber nach, das Skript in mehrere Skripte aufzuteilen und sie in meinem Hauptskript zu verwenden (ähnlich wie Importe in andere Sprachen).

Aber ich frage mich, ob dies ein praktikabler Ansatz ist. Erstens kann die Beschaffung mehrerer Skripte die Ausführungszeit des Skripts erheblich verlangsamen, und zweitens erschwert dies die Verteilung.

Ist das ein guter Ansatz und machen es andere (Open Source) Projekte genauso?

Hilfemethode
quelle
4
Ich habe nach einem wirklich langen Shell-Skript gesucht und bei 3500 Zeilen und 125 KB möchte ich nicht versuchen, es beizubehalten. Die Shell ist sehr benutzerfreundlich beim Einbinden von Programmen, aber wenn sie versucht zu rechnen, wird es ziemlich hässlich. Ich weiß, dass der Code, den Sie haben, meistens funktioniert und die Portierungskosten hoch sind, aber Sie möchten vielleicht in Zukunft etwas anderes in Betracht ziehen.
msw
1
Ich bin auf dasselbe Problem gestoßen. Ich habe ein mäßig großes Bash-Projekt sourceforge.net/projects/duplexpr . Derzeit ist jedes Skript eigenständig, aber ich denke darüber nach, alle gängigen Funktionen in eine separate Datei (en) zu verschieben und sie gegebenenfalls einzuschließen, um zu vermeiden, dass sie an mehreren Stellen aktualisiert werden müssen. Im Allgemeinen halte ich es für besser, jedes nachfolgende Skript nur aufzurufen, anstatt es als Quelle zu verwenden. Es ermöglicht das unabhängige Betreiben der Teile. Dann müssen Sie Variablen als Argumente oder in Parameterdateien übergeben (oder Sie können sie einfach exportieren, wenn Sie nicht eigenständig sein möchten.)
Joe

Antworten:

12

Ja, das ist eine gängige Praxis. In den Anfängen von Unix war der Shell-Code, der das System durch die Startphasen zum Mehrbenutzerbetrieb führte, beispielsweise eine einzelne Datei /etc/rc. Heutzutage wird der Startvorgang von vielen Shellskripten gesteuert, die nach Funktionen unterteilt sind und deren allgemeine Funktionen und Variablen von zentralen Standorten aus nach Bedarf bezogen werden. Linux-Distributionen, Macs und BSDs haben diesen Ansatz in unterschiedlichem Maße übernommen.

Kyle Jones
quelle
2
In diesem Fall ist es jedoch mehr für die Wiederverwendbarkeit. Verschiedene Skripte verwenden eine gemeinsame Bibliothek von Shell-Funktionen.
Stéphane Chazelas
16

Ist Shell zu diesem Zeitpunkt das richtige Werkzeug für den Job? Als Entwickler, der auf Probleme mit herauswachsendem Code gestoßen ist, kann ich Ihnen sagen, dass ein Umschreiben nicht in Betracht gezogen werden sollte, sondern die Aufteilung der Teile in etwas, das besser für den Maßstab geeignet ist, den Sie für Ihre Anwendung benötigen - vielleicht Python, Ruby oder sogar Perl ?

Shell ist eine Utility-Sprache - es ist eine Skriptsprache - und daher wird es schwierig sein, sie auf diese Größen zu erweitern.

JasonG
quelle
Genau das wollte ich posten.
zwol
Vor allem, da auch ältere Betriebssysteme wie Solaris jetzt mit Perl und Python usw. ausgeliefert werden. Ein Grund für die Verwendung von Shell-Skripten auf älteren Systemen ist, dass die Shell immer verfügbar war, größere Unices wie HP-UX und Solaris sowie AIX jedoch keine anderen Tools enthalten.
Tim Kennedy
6

Wenn es Ihnen die Wartung erleichtert, können Sie beides haben. Teilen Sie es in logische Teile auf, damit Sie es einfach warten können, und schreiben Sie dann (z. B.) ein Makefile, um es zur Verteilung wieder zusammenzusetzen die sourceZeile oder machen Sie einfach etwas Triviales wie dieses (Sie müssen dies neu tabulieren, wenn makeTabulatoren erforderlich sind):

all: myscript

myscript: includes/* body/*
    cat $^ > "$@" || (rm -f "$@"; exit 1)

Sie haben dann eine "Quell" -Version (für die Bearbeitung verwendet) und eine "Binär" -Version (für die einfache Installation verwendet).

derobert
quelle
1
Aha! Jemand anderes, das den einfachen Katzenansatz verwendet :)
Clayton Stanley
4

Ein Skript kann wie von Ihnen beschrieben aufgebrochen werden - fast alles kann getan werden. Ich würde sagen, dass der „gute Ansatz“ darin besteht, Ihr großes Skript zu unterteilen und herauszufinden, wo Teile davon als separater Prozess ausgeführt werden können, der über IPC-Mechanismen kommuniziert.

Darüber hinaus würde ich ein Shell-Skript als einzelne Datei packen. Wie Sie sagen, erschwert dies die Verteilung: Entweder müssen Sie wissen, wo sich die 'Bibliothek'-Skripte befinden - es gibt keinen netten Standard für Shell-Skripte - oder Sie müssen sich darauf verlassen, dass der Benutzer ihren Pfad korrekt einstellt.

Sie können ein Installationsprogramm verteilen, das dies alles für Sie erledigt, die Dateien extrahiert, an die richtige Stelle legt und den Benutzer export PROGRAMDIR=$HOME/lib/PROGRAMauffordert, der Datei ~ / .bashrc etwas Ähnliches hinzuzufügen. Das Hauptprogramm kann dann fehlschlagen, wenn $PROGRAMDIRes nicht festgelegt ist oder die erwarteten Dateien nicht enthält.

Ich würde mir nicht so viele Sorgen um den Aufwand beim Laden der anderen Skripte machen. Der Aufwand ist wirklich nur das Öffnen einer Datei; Die Verarbeitung des Textes ist die gleiche, insbesondere wenn es sich um Funktionsdefinitionen handelt.

Arcege
quelle
1

Gängige Praxis oder nicht, ich halte nichts anderes als eine Reihe von Exporten für eine gute Idee. Ich finde, dass das Ausführen von Code durch Sourcing nur verwirrend ist und die Wiederverwendung einschränkt, da Umgebungs- und andere variable Einstellungen den Sourcing-Code stark vom Sourcing-Code abhängig machen.

Sie sollten Ihre Anwendung besser in kleinere, eigenständige Skripts aufteilen und diese dann als eine Reihe von Befehlen ausführen. Dies erleichtert das Debuggen, da Sie jedes in sich geschlossene Skript in einer interaktiven Shell ausführen und zwischen den einzelnen Befehlsaufrufen Dateien, Protokolle usw. untersuchen können. Ihr einzelnes, umfangreiches Anwendungsskript wird zu einem einfacheren Steuerungsskript, das nach dem Debuggen nur eine Reihe von Befehlen ausführt.

Eine Gruppe kleinerer, in sich geschlossener Skripte stößt auf das schwieriger zu installierende Problem.

Bruce Ediger
quelle
1

Eine Alternative zum Sourcing der Skripte besteht darin, sie einfach mit Argumenten aufzurufen. Wenn Sie den größten Teil Ihrer Funktionalität bereits in Shell-Funktionen aufgeteilt haben, sind Sie wahrscheinlich fast in der Lage, dies bereits zu tun. Mit dem folgenden Codeausschnitt von Bash kann jede in einem Skript deklarierte Funktion als Unterbefehl verwendet werden:

if [[ ${1:-} ]] && declare -F | cut -d' ' -f3 | fgrep -qx -- "${1:-}"
then "$@"
else main "$@" # Try the main function if args don't match a declaration.
fi

Der Grund, dies nicht zu tun, sourceist die Vermeidung von Umwelt- und Optionsverschmutzung.

SolidSnack
quelle
0

Hier ist mein Beispiel, wie ein großes Bash-Skript in mehrere Dateien aufgeteilt und dann in ein daraus resultierendes Skript integriert werden kann: https://github.com/zinovyev/bash-project

Ich benutze ein Makefilefür diesen Zweck:

TARGET_FILE = "target.sh"
PRJ_SRC = "${PWD}/src/main.sh"
PRJ_LIB = $(shell ls -d ${PWD}/lib/*) # All files from ./lib

export PRJ_LIB

SHELL := /bin/env bash
all: define_main add_dependencies invoke_main

define_main:
    echo -e "#!/usr/bin/env bash\n" > ${TARGET_FILE}
    echo -e "function main() {\n" >> ${TARGET_FILE}
    cat "${PRJ_SRC}" | sed -e 's/^/  /g' >> ${TARGET_FILE}
    echo -e "\n}\n" >> ${TARGET_FILE}

invoke_main:
    echo "main \$$@" >> ${TARGET_FILE}

add_dependencies:
    for filename in $${PRJ_LIB[*]}; do cat $${filename} >> ${TARGET_FILE}; echo >> ${TARGET_FILE}; done
zinovyev
quelle