• Willkommen im Linux Club - dem deutschsprachigen Supportforum für GNU/Linux. Registriere dich kostenlos, um alle Inhalte zu sehen und Fragen zu stellen.

[Gelöst] Shell Skript modular aufbauen?

Hallo,
auch dank der guten Tipps hier macht es immer mehr Spass, bash Skripte zu schreiben.
Ich habe im Moment mein auf xdialog umgestelltes Updateskript nun auch in Funktionen unterteilt,
zum Beispiel:
Code:
make_tailbox () {
#Datei für Tailbox erzeugen
echo -n > $homedir/.progress.txt
#das "&" am Ende einer Anweisung führt dazu, daß der Befehl in einer subshell im Hintergrund abläuft.
Xdialog --no-buttons --title "Ein Softwareupdate des Systems wird durchgeführt." --tailbox $homedir/.progress.txt 1200x400 &
echo "Update Script für zypper - Die Xdialog Version 0.3xd">>$homedir/.progress.txt
echo "Originalskript verändert und Xdialog Fenster eingeführt von Ingo Trautwein 03-2013">>$homedir/.progress.txt
echo "">>$homedir/.progress.txt
}

Nun zu meiner Frage:
Um Spaghetti-Code vorzubeugen und das Ding übersichtlich zu machen (die vielen Userdialoge blähen inzwischen den Code auf...)
möchte ich gerne jede einzelne Funktion in genau eine Modul Datei schreiben, und diese dann von einem Zentralskript aus aufrufen.
irgendwie so:
Code:
#!/bin/bash
#natürlich so nicht ausführbar nur als Idee
#Zentrales Aufrufskript für die Funktionen

DATEI=pfad_zur_funktions_verzeichnisdatei
#funktionsaufrufe nacheinander
while [ not EOF DATEI ]
do
(irgendwas)
done<DATEI

Hat jemand so was schon mal gemacht? (Ich müsste mich wundern, wenn das nicht mit der bash ginge :irre: )
 

framp

Moderator
Teammitglied
Ueblicherweise macht man das so dass Konstanten und Subroutinen in verschiedenen Dateien stehen. Das Hauptprogramm included dann die Dateien.
Code:
#!/bin/bash
# main file

. constants
. sub1
. sub2

v1=sub1
v2=sub2

echo $v1
echo $v2

echo $c1
echo $c2

# constant file

c1="c1"
c2="c2"

# subroutine sub1 file

function sub1 () {

echo "sub1"
}
# subroutine sub2  file

function sub2 () {

echo "sub2"
}
 
A

Anonymous

Gast
ingo.trautwein schrieb:
Um Spaghetti-Code vorzubeugen und das Ding übersichtlich zu machen (die vielen Userdialoge blähen inzwischen den Code auf...)
möchte ich gerne jede einzelne Funktion in genau eine Modul Datei schreiben, und diese dann von einem Zentralskript aus aufrufen.

JAEIN. Ganz so global ist es auch nicht sinnvoll und richtig.

Prinzipell gilt in etwa folgendes:
  • # Alles was in gleicher oder ähnlicher Art an verschiedenen Stellen im Programmablauf mehrfach benötigt wird, ist in Funktionen sehr gut aufgehoben. was nur mehrfach an immer nur an "ein und der selben Stelle" benötigt wird, ist oft in Schleifen besser aufgehoben, insbesondere wenn es klein ist.
    # Alles was ein in sich logisch abgeschlossener Prozess ist, den man eventuell irgendwo anders genauso gebrauchen könnte darf sehr gerne in eine Funktion. (also alles für das man eigentlich ein eigenes Script schreiben könnte, und das man dann als Befehl auch anderweitig verwenden könnte)
    # Eine Funktion benötigt immer eindeutig definierte Schnittstellen. Das bedeutet, ein Funktion eines Shellscriptes sollte wenn möglich nie auf etwas anderes zugreifen, als auf das, was ihr mit den Übergabeparametern übergeben wurde. Also nicht auf außerhalb der Funktion definierte oder sonstige exportierte Variablen, (außer standardisierte globale Umgebungs Variablen der Shell) und möglichst auch nicht auf private Dateien deren Namen nicht mit übergeben wurde und die in anderen Funktionen oder vom Mainscript aus auch benutzt werden usw.
    Um auf diese definierten und möglichst universellen Schnittstellen zu kommen ist es nicht selten notwendig die Funktionen größer und umfangreicher zu gestalten, als man es ursprünglich angedacht hatte.
    # die nachtägliche Umwandlung von Teilen eines fertigen Scriptes in einzelne Funktionen verlangt nicht selten das umschreiben des haben Scripts, (aus den Gründen von Punkt vorher)
    # Die Nutzung von Funktionen sollte die Lesbarkeit, Übersichtlichkeit und Größe des Programmes vereinfachen und nicht den Code aufblähen und unnötig verkomplizieren. Das ist manchmal leichter gesagt als getan


Auch gibt es hier in der Shell schnell Probleme über die vor allem Programmierer aus anderen Programmiersprachen stolpern, da die Bash einige Möglichkeiten überhaupt nicht bietet die in anderen Programmiersprachen Standard sind. Als Übergabe des Ergebnisses sind zB somit eigentlich nur 3 praktikable Möglichkeiten angezeigt :
  • # eine Ganzzahl als Returnwert von "return", der aber in der bash nur recht umständlich ausgewertet oder an Variablen übergeben werden kann. (hier sollte man bedenken das ohne return immer der status des letzten gelaufenen Kommandos der Funktion zurückgegeben wird, das kann durchaus auch mal zu Missverständnissen führen. ),
    # eine Übergabe von Zeichenketten oder ähnlichen Ausgaben entweder an einer named- oder unnamed Pipe , (hier bei hat man oftmals das Problem, das das Ergebniss der Funktion als Zeichenkette dann nach dem Aufruf der Funktion im Mainscript wieder auseinandergelegt werden muss um es in mehreren Variablen abzulegen.
    # oder eine/mehrere externe Dateien.(wobei hier die Funktion die Dateien erst schließen muss und die Dateien immer erst wieder neu geöffnet werden, das bei oftmaliger und häufiger Nutzung solcher Funktionen eigentlich unschön und unpraktisch ist.

Fazit, Funktionen sind der Weg zur schönen und strukturierten Programmierung in der Bash, aber um schönen übersichtliche Scripte zu schreiben muss man sich ein bisschen näher mit der bash beschäftigen sie verstehen und ausreizen lernen. De Manpage von bash und vielen anderen einfachen Kommandos mal ein paar Wochen unter dem Kopfkissen liegen zu haben, und öfter mal ein Script komplett auseinanderlegen, hin und wieder mal eines selbst schreiben oder mal ein eigenes nach ein paar Wochen wieder hernehmen und zu schauen ob man noch versteht, was man dort mal programmiert hat, hilft viel.
function () alleine , ist nicht das Allheilmittel.

Das hier als Funktion:
Code:
make_tailbox () {
#Datei für Tailbox erzeugen
echo -n > $homedir/.progress.txt
#das "&" am Ende einer Anweisung führt dazu, daß der Befehl in einer subshell im Hintergrund abläuft.
Xdialog --no-buttons --title "Ein Softwareupdate des Systems wird durchgeführt." --tailbox $homedir/.progress.txt 1200x400 &
echo "Update Script für zypper - Die Xdialog Version 0.3xd">>$homedir/.progress.txt
echo "Originalskript verändert und Xdialog Fenster eingeführt von Ingo Trautwein 03-2013">>$homedir/.progress.txt
echo "">>$homedir/.progress.txt
}
macht nur eines, das Chaos aus einem Scriptabschitt in einen anderen umzulagern.

robi
 
@robi
Vielen Dank für die detaillierte Ausführung. Sehr hilfreich. Ein paar Sachen will ich noch kommentieren:

...Die Manpage von bash und vielen anderen einfachen Kommandos mal ein paar Wochen unter dem Kopfkissen liegen zu haben, und öfter mal ein Script komplett auseinanderlegen, hin und wieder mal eines selbst schreiben oder mal ein eigenes nach ein paar Wochen wieder hernehmen und zu schauen ob man noch versteht, was man dort mal programmiert hat, hilft viel.
Genau das mache ich gerade, ich schreibe Skripte (z.T auch sinnlose Übungen) und versuche mein Verständnis
und die Skripte zu verbessern. Ist natürlich ein zähes Unterfangen.
...macht nur eines, das Chaos aus einem Scriptabschitt in einen anderen umzulagern.
define "Chaos" ??? Zugegeben, sieht vielleicht nicht schön aus, ist aber alles laut manpage von xdialog formuliert.
Ich war erst mal froh, daß die bash macht was ich will, ob ich diese Zeilen so lasse weiß ich nicht.
Die Kommentare muss ich leider reinschreiben, damit ich nicht vergesse, wie ich auf die Lösung gekommen bin.
Für konkrete Kritik bin ich immer aufgeschlossen.
...Die Nutzung von Funktionen sollte die Lesbarkeit, Übersichtlichkeit und Größe des Programmes vereinfachen und nicht den Code aufblähen und unnötig verkomplizieren. Das ist manchmal leichter gesagt als getan
Genau das hat es jetzt in meinem Fall bewirkt. Ich kann jetzt an den einzelnen Subroutinen Verbesserungen
vornehmen, ohne durch den gesamten Code scrollen zu müssen und womöglich versehentlich irgendein " oder ;; ...etc zu löschen
und dann plötzlich skurrile Fehler oder Abstürze zu haben.
Die Übersichtlichkeit ist mir hier erstmal wichtiger, sonst kann ich das von Dir zitierte "Chaos" nicht finden und eliminieren.

@framp:
Dein Beispiel hat auf Anhieb funktioniert, Danke.
 

spoensche

Moderator
Teammitglied
Du kannst die Funktionen alle in eine separate Datei auslagern, die du dann wie folgt einbindest:
Möglichkeit 1:
Code:
. /pfad/zur/datei/dateiname

Möglichkeit 2:
Code:
source /pfad/zur/datei/dateiname
 

framp

Moderator
Teammitglied
ingo.trautwein schrieb:
...@framp:
Dein Beispiel hat auf Anhieb funktioniert, Danke...
Hätte mich auch gewundert, denn das habe ich kurz vor dem posten auch getestet ;)

Ich hatte auch den Eindruck dass Du nach solch einer pragmatischen Lösung suchst. robi hat natürlich Recht und ich würde auch nie für ein grösseres Project bash einsetzen. Aber Deine Klagepunkte habe ich sehr gut verstanden und die Lösung ist ja auch die die man in anderen Programmiersprachen wie C und C++ einsetzt: Separate Implementierungs- und Konstantendateien, die einfach 'included' werden. Oftmals will man aber für einen konkreten Einsatz ein Problem lösen und dann nicht erst 10 Dateien irgendwo installieren. Deshalb wird auch oft bei bash alles in eine Datei gschrieben um ein kompaktes abgeschlossenes Script zu haben was einfach nur kopiert werden kann und sofort funktioniert.
 

abgdf

Guru
ingo.trautwein schrieb:
Um Spaghetti-Code vorzubeugen und das Ding übersichtlich zu machen (die vielen Userdialoge blähen inzwischen den Code auf...)
möchte ich gerne jede einzelne Funktion in genau eine Modul Datei schreiben, und diese dann von einem Zentralskript aus aufrufen.
Das hab' ich auch ein paarmal gemacht, aber mir hat nicht gefallen, daß das Programm/Skript dann in viele kleine Dateien zersplittert.

Bei größeren Skripten (Python) benutze ich Module (= Moduldateien) während des Schreibens *, aber wenn alles fertig ist, füge ich es meistens wieder zu einem Skript zusammen. Eines hat z.B. über 2.000 Zeilen und ist dadurch ca. 80K groß. Aber was sind schon 80K? Ich finde es jedenfalls am Ende praktischer, wenn man das Skript in einem Stück verschieben und auch ausführen kann.

* Edit: Genauer: Jeweils eine Datei für die Klassen "Model, View und Controller", na ja, bash kennt keine Klassen. ;)

Edit2: Zu framp: Stimmt, in C/C++ verwendet man viele "#include"s. Aber der Compiler bindet am Ende alles zu ausführbaren Dateien zusammen, oft zu einer. Diese Module bleiben beim/zum Ausführen also nicht so erhalten. Na ja, bis auf die Libraries.
 
A

Anonymous

Gast
ingo.trautwein schrieb:
define "Chaos" ??? Zugegeben, sieht vielleicht nicht schön aus, ist aber alles laut manpage von xdialog formuliert.

schau mal ob dir ein solcher Schreibstiel hier besser gefällt.
Code:
# globale Variablen für das gesamte Script
homedir=$HOME
Logfile=${homedir}/.progress.txt

VERSION="Update Script für zypper - Die Xdialog Version 0.3xd"
KOMMENTAR="Originalskript verändert und Xdialog Fenster eingeführt von Ingo Trautwein 03-2013"

#-------------------------------------------
# Funktionen
# diese Funktion schreibt die übergebenen Argument in die Logdatei
function log(){
        if [ ! -f "${Logfile}" ]
                then touch "${Logfile}"
        fi
        echo -e "$*" >> "${Logfile}"
}
#------------------------------------------

# diese Funktion erzeugt einen zusätzlichen Zeilensprung in die Logdatei
function log_NL(){
        log "\n"
}
#-----------------------------------------

# erzeugen eine Xdialog fensters . Titel wird als Argument übergeben.
function make_tailbox (){
        Xdialog --no-buttons --title "$*" --tailbox "$logfile" 1200x400 &
        log "$VERSION"
        log "$KOMMENTAR"
        log_NL
}
#----------------------------------------

#Main-script
# Start des Scriptes, es macht folgendes......
log_NL
make_tailbox "Ein Softwareupdate des Systems wird durchgeführt."

# END Main
ob das mit dem "$*" dort in der Xdialog-Zeile so funktioniert kann ich nicht test, hab ich nicht installiert, möglich dort muss ein bisschen probiert werden. (möglich dort muss "$@" hin)
aber so könnte strukturierte Programmierung mit Funktionen in der Bash aussehen, Hier in diesem kleinem Beispiel sieht man die Vorteile noch nicht so deutlich, aber sobald das Script anwachsen würde, währe es sehr schnell ersichtlich, wo die Vorteile sind.

robi
 
@robi
...ohne Ironie: Chapeau!!
Viel eleganter geht's wohl nicht. - Lerneffekt = "y" :D

Als "Automotive" Verfahrenstechnik-Ingenieur muß ich leider ab und zu "quick and dirty" arbeiten,
ich fürchte ds färbt auf meinen script "Schreibstil" ab. :igitt:
In der Regel sind wir zufrieden, wenn's endlich läuft und wenden uns dem nächsten Schreihals zu,
der einen nicht einhaltbaren Termin zu nicht einhaltbaren Kosten fordert...

Hintergrund zu meiner Frage:
Ich habe vor Urzeiten mal mit dBase und OpenAcess programmiert. (kennt das noch jemand?)
Mein damaliger Lehrer hat mir damals eingehämmert: "Alles was größer ist als 1 Bildschirmseite ist unkontrollierbar"
Natürlich werde ich keine Riesenprojekte mit der bash realisieren, aber das Konzept mit den subroutinen
gefällt mir trotzdem.
 

framp

Moderator
Teammitglied
ingo.trautwein schrieb:
@robi
...ohne Ironie: Chapeau!!
Full ack
... muß ich leider ab und zu "quick and dirty" arbeiten...
Kenne ich - aber um so mehr Zeit braucht man später wenn der Code geändert/erweiter werden muss.
..."Alles was größer ist als 1 Bildschirmseite ist unkontrollierbar"...
Ist zu eng gefasst. Aber man kann sagen - wenn Du beim Springen zwischen den Codestellen zu viele Seiten blättern musst ist es zu viel.
 

abgdf

Guru
framp schrieb:
ingo.trautwein schrieb:
@robi
...ohne Ironie: Chapeau!!
Full ack
Nichts gegen robi, aber diese 39 Zeilen waren nun auch nicht soo beeindruckend. Ist doch eigentlich Standard, siehe z.B. die Beispielskripte im bash-guide.
Meist werden bei Einrückungen heute 4 Zeichen empfohlen (daß ich nochmal den PEP zitiere ... :mrgreen:).
framp schrieb:
Aber man kann sagen - wenn Du beim Springen zwischen den Codestellen zu viele Seiten blättern musst ist es zu viel.
Och, dafür gibt's bei vim doch extra "ma"/"'a" und "mb"/"'b" usw., das heißt "Sprungmarke setzen" und "zur Sprungmarke gehen". Damit kommt man auch bei längerem Code in einer Datei ganz gut klar. ;)
 

framp

Moderator
Teammitglied
abgdf schrieb:
framp schrieb:
ingo.trautwein schrieb:
@robi
...ohne Ironie: Chapeau!!
Full ack
Nichts gegen robi, aber diese 39 Zeilen waren nun auch nicht soo beeindruckend. Ist doch eigentlich Standard, siehe z.B. die Beispielskripte im bash-guide.{/quote]
Nein, es ist nicht Standard dass in einem Forum so detailliert und fundiert geantwortet wird. Die Einrückungen sind da peripher ... aber nixdestrotrotz eine Empfehlung für den TE ;)
framp schrieb:
Aber man kann sagen - wenn Du beim Springen zwischen den Codestellen zu viele Seiten blättern musst ist es zu viel.
Och, dafür gibt's bei vim doch extra "ma"/"'a" und "mb"/"'b" usw., das heißt "Sprungmarke setzen" und "zur Sprungmarke gehen". Damit kommt man auch bei längerem Code in einer Datei ganz gut klar. ;)
War mir klar dass so eine Antwort kommt. Per search kann man natürlich recht schnell navigieren. Aber meist weisst man nicht genau wonach zu suchen (hiess die Var Temp - oder tmp - oder auch nur tp :???: ) und man fängt doch an zu blättern ...
 
So Leute,
ich mach dann den thread mal zu.
Wie immer Danke an alle für die rege Beteiligung und ausführliche Diskussion.
Den bash-guide kannte ich noch nicht.
Ich werde zukünftig ein bisschen mehr auf Formatierungskonventionen achten.
Meine Meinung zu vim erspare ich Euch aber... :censored: :D

Schönes Wochenende
 
Oben