Warum ein komplettes Bash-Skript in Funktionen schreiben?


59

Bei der Arbeit schreibe ich häufig Bash-Skripte. Mein Vorgesetzter hat vorgeschlagen, das gesamte Skript in Funktionen zu unterteilen, ähnlich dem folgenden Beispiel:

#!/bin/bash

# Configure variables
declare_variables() {
    noun=geese
    count=three
}

# Announce something
i_am_foo() {
    echo "I am foo"
    sleep 0.5
    echo "hear me roar!"
}

# Tell a joke
walk_into_bar() {
    echo "So these ${count} ${noun} walk into a bar..."
}

# Emulate a pendulum clock for a bit
do_baz() {
    for i in {1..6}; do
        expr $i % 2 >/dev/null && echo "tick" || echo "tock"
        sleep 1
    done
}

# Establish run order
main() {
    declare_variables
    i_am_foo
    walk_into_bar
    do_baz
}

main

Gibt es einen anderen Grund, als die "Lesbarkeit", die meiner Meinung nach mit ein paar weiteren Kommentaren und einigem Zeilenabstand gleich gut festgestellt werden könnte?

Lässt sich das Skript effizienter ausführen (ich würde eigentlich eher das Gegenteil erwarten) oder ist es einfacher, den Code über das oben genannte Lesbarkeitspotential hinaus zu ändern? Oder ist es wirklich nur eine stilistische Präferenz?

Bitte beachten Sie, dass , obwohl das Skript nicht gut funktioniert zeigen, die „run Ordnung“ der Funktionen in unserem aktuellen Skripte sehr linear zu sein scheint - walk_into_barabhängig von Sachen , die i_am_foogetan hat, und do_bazwirkt auf Material eingesetzt durch walk_into_bar- so seinen Die Laufreihenfolge willkürlich tauschen zu können, ist etwas, was wir normalerweise tun würden. Zum Beispiel, würden Sie nicht plötzlich setzen wollen declare_variablesnach walk_into_bar, dass sich die Dinge brechen.

Ein Beispiel, wie ich das obige Skript schreiben würde, wäre:

#!/bin/bash

# Configure variables
noun=geese
count=three

# Announce something
echo "I am foo"
sleep 0.5
echo "hear me roar!"

# Tell a joke
echo "So these ${count} ${noun} walk into a bar..."

# Emulate a pendulum clock for a bit
for i in {1..6}; do
    expr $i % 2 >/dev/null && echo "tick" || echo "tock"
    sleep 1
done

30
Ich mag deinen Chef. In meinen Skripten setze ich auch main()oben und main "$@"unten, um es aufzurufen. Auf diese Weise können Sie die übergeordnete Skriptlogik als Erstes anzeigen, wenn Sie sie öffnen.
John Kugelman

22
Ich bin mit der Vorstellung nicht einverstanden, dass die Lesbarkeit "mit ein paar weiteren Kommentaren und einigem Zeilenabstand gleich gut hergestellt werden kann". Mit Ausnahme von Belletristik würde ich mich nicht mit einem Buch befassen wollen, das kein Inhaltsverzeichnis und keine beschreibenden Namen für jedes Kapitel und jeden Abschnitt enthält. In Programmiersprachen ist dies die Art von Lesbarkeit, die Funktionen bieten können, und Kommentare können dies nicht.
Rhymoid

6
Beachten Sie, dass in Funktionen deklarierten Variablen deklariert werden sollte local- bietet diese Variable Umfang , die in jedem nicht-trivialen Skript unglaublich wichtig ist.
Boris die Spinne

8
Ich bin nicht einverstanden mit Ihrem Chef. Wenn Sie Ihr Skript in Funktionen aufteilen müssen, sollten Sie wahrscheinlich überhaupt kein Shell-Skript schreiben. Schreiben Sie stattdessen ein Programm.
el.pescado

6
Funktionen gelten für Prozesse, die entweder innerhalb des Skripts oder in mehreren Skripts wiederholt werden. Sie ermöglichen auch die Einführung einheitlicher Methoden. ZB mit einer Funktion zum Schreiben in Syslog. Solange alle dieselbe Funktion verwenden, sind Ihre Syslog-Einträge konsistenter. Einwegfunktionen wie Ihr Beispiel erschweren das Skript unnötig. In einigen Fällen führen sie zu Problemen (variabler Geltungsbereich).
Xalorous

Antworten:


39

Nachdem ich Kfir Lavis Blogpost "Defensive Bash Programming" gelesen habe, habe ich angefangen, diesen Stil der Bash-Programmierung zu verwenden . Er gibt einige gute Gründe an, aber ich persönlich finde diese die wichtigsten:

  • Prozeduren werden beschreibend: Es ist viel einfacher herauszufinden, was ein bestimmter Teil des Codes tun soll. Anstelle der Codewand sehen Sie "Oh, die find_log_errorsFunktion liest diese Protokolldatei auf Fehler". Vergleichen Sie dies damit, dass Sie eine ganze Reihe von awk / grep / sed-Zeilen finden, die genau wissen, welche Art von Regex in der Mitte eines langen Skripts vorkommt.

  • Sie können Funktionen debuggen, indem Sie in set -xund einschließen set +x. Sobald Sie wissen, dass der Rest des Codes in Ordnung ist, können Sie sich mit diesem Trick darauf konzentrieren, nur diese bestimmte Funktion zu debuggen. Sicher, Sie können Teile des Skripts einschließen, aber was ist, wenn es ein langer Teil ist? So etwas ist einfacher:

     set -x
     parse_process_list
     set +x
  • Drucknutzung mit cat <<- EOF . . . EOF. Ich habe es einige Male benutzt, um meinen Code viel professioneller zu machen. Darüber hinaus ist parse_args()mit getoptsFunktion ganz bequem. Auch dies trägt zur Lesbarkeit bei, anstatt alles als riesige Textwand in die Schrift zu schreiben. Es ist auch bequem, diese wiederzuverwenden.

Und offensichtlich ist dies für jemanden, der C, Java oder Vala kennt, aber nur über begrenzte Bash-Erfahrung verfügt, viel besser lesbar. Was die Effizienz angeht, können Sie nicht viel tun - bash selbst ist nicht die effizienteste Sprache, und die Leute bevorzugen Perl und Python, wenn es um Geschwindigkeit und Effizienz geht. Sie können jedoch niceeine Funktion:

nice -10 resource_hungry_function

Verglichen mit dem Aufruf von nice in jeder Codezeile, verringert sich dadurch der Aufwand für die Eingabe von AND und kann bequem verwendet werden, wenn nur ein Teil Ihres Skripts mit niedrigerer Priorität ausgeführt werden soll.

Das Ausführen von Funktionen im Hintergrund hilft meiner Meinung nach auch, wenn Sie eine ganze Reihe von Anweisungen im Hintergrund ausführen möchten.

Einige Beispiele, in denen ich diesen Stil verwendet habe:


3
Ich bin mir nicht sicher, ob Sie Vorschläge aus diesem Artikel sehr ernst nehmen sollten. Zugegeben, es hat ein paar gute Ideen, aber es ist eindeutig nicht jemand, der sich mit Shell-Skripten auskennt. In keinem der Beispiele wird eine einzelne Variable in Anführungszeichen (!) Gesetzt, und es wird empfohlen, UPPER CASE-Variablennamen zu verwenden. Dies ist häufig eine sehr schlechte Idee, da sie mit vorhandenen Umgebungsvariablen in Konflikt geraten können. Ihre Punkte in dieser Antwort sind sinnvoll, aber der verlinkte Artikel scheint von jemandem geschrieben worden zu sein, der nur mit anderen Sprachen vertraut ist und versucht, seinen Stil zu erzwingen.
Terdon

1
@terdon Ich ging zurück zum Artikel und las ihn noch einmal. Der einzige Ort, an dem der Autor die Benennung von Variablen in Großbuchstaben erwähnt, ist "Immutable Global Variables". Wenn Sie globale Variablen als solche betrachten, die sich in der Funktionsumgebung befinden müssen, ist es sinnvoll, sie groß zu schreiben. Bashs Handbuch enthält keine Konventionen für den Fall von Variablen. Auch hier sagt die akzeptierte Antwort "normalerweise" und der einzige "Standard" ist von Google, das nicht die gesamte IT-Branche repräsentiert.
Sergiy Kolodyazhnyy

@terdon in einem anderen Punkt, ich stimme zu 100% zu, dass das variable Zitieren im Artikel hätte erwähnt werden sollen, und es wurde auch in den Kommentaren im Blog darauf hingewiesen. Außerdem würde ich niemanden beurteilen, der diesen Codierungsstil verwendet, unabhängig davon, ob er an eine andere Sprache gewöhnt ist oder nicht. Diese ganze Frage und die Antworten zeigen deutlich, dass es Vorteile gibt, und der Grad, in dem eine Person an eine andere Sprache gewöhnt ist, spielt hier wahrscheinlich keine Rolle.
Sergiy Kolodyazhnyy

1
@terdon na ja, der Artikel wurde als Teil des "Quell" -Materials gepostet. Ich hätte alles als meine eigene Meinung veröffentlichen können, aber ich musste nur gutschreiben, dass einige der Dinge, die ich aus dem Artikel gelernt habe, und dass all dies aus der Forschung im Laufe der Zeit stammte. Die Linkedin-Seite des Autors zeigt, dass er gute Erfahrungen mit Linux und IT im Allgemeinen hat. Ich schätze, der Artikel zeigt das nicht wirklich, aber ich vertraue auf Ihre Erfahrung, wenn es um Linux und Shell-Scripting geht, also haben Sie vielleicht Recht .
Sergiy Kolodyazhnyy

1
Das ist eine ausgezeichnete Antwort, aber ich möchte auch hinzufügen, dass der variable Bereich in Bash irre ist. Aus diesem Grund ziehe ich es vor, meine Variablen innerhalb von Funktionen zu deklarieren localund alles über die main()Funktion aufzurufen . Dies macht die Dinge viel leichter handhabbar und Sie können eine möglicherweise unordentliche Situation vermeiden.
Housni

65

Lesbarkeit ist eine Sache. Modularisierung ist aber noch viel mehr . ( Semi-Modularisierung ist für Funktionen möglicherweise korrekter.)

In Funktionen können Sie einige Variablen lokal halten, was die Zuverlässigkeit erhöht und die Wahrscheinlichkeit verringert, dass Dinge durcheinander geraten.

Ein weiterer Pluspunkt ist die Wiederverwendbarkeit . Sobald eine Funktion codiert ist, kann sie im Skript mehrmals angewendet werden. Sie können es auch in ein anderes Skript portieren.

Ihr Code ist jetzt möglicherweise linear, aber in Zukunft können Sie in der Bash-Welt in den Bereich des Multi-Threading oder der Multi-Verarbeitung eintreten . Wenn Sie erst einmal gelernt haben, Dinge in Funktionen zu tun, sind Sie für den Schritt in die Parallele gut gerüstet.

Ein weiterer Punkt zum Hinzufügen. Wie Etsitpab Nioliv im Kommentar unten bemerkt, ist es einfach, Funktionen als zusammenhängende Einheit umzuleiten. Aber es gibt noch einen Aspekt bei Umleitungen mit Funktionen. Die Umleitungen können nämlich entlang der Funktionsdefinition festgelegt werden. Z.B.:

f () { echo something; } > log

Jetzt sind keine expliziten Weiterleitungen durch die Funktionsaufrufe erforderlich.

$ f

Dies kann viele Wiederholungen ersparen, was wiederum die Zuverlässigkeit erhöht und hilft, die Dinge in Ordnung zu halten.

Siehe auch


55
Sehr gute Antwort, obwohl es viel besser wäre, wenn es in Funktionen unterteilt wäre.
Pierre Arlaud

1
Wenn Sie diese Funktionen hinzufügen, können Sie das Skript möglicherweise in ein anderes Skript importieren (indem Sie sourceoder verwenden . scriptname.shund diese Funktionen so verwenden, als wären sie in Ihrem neuen Skript enthalten).
SnakeDoc

Das ist bereits in einer anderen Antwort behandelt.
Tomasz

1
Ich weis das zu schätzen. Aber ich möchte lieber zulassen, dass auch andere Menschen wichtig sind.
Tomasz

7
Ich war heute mit einem Fall konfrontiert, in dem ich einen Teil der Ausgabe eines Skripts in eine Datei umleiten musste (um sie per E-Mail zu senden), anstatt zu wiederholen. Ich musste einfach myFunction >> myFile ausführen, um die Ausgabe der gewünschten Funktionen umzuleiten. Ziemlich praktisch. Könnte relevant sein.
Etsitpab Nioliv

39

In meinem Kommentar erwähnte ich drei Vorteile von Funktionen:

  1. Sie sind einfacher zu testen und auf Richtigkeit zu überprüfen.

  2. Funktionen können in zukünftigen Skripten problemlos wiederverwendet (bezogen) werden

  3. Ihr Chef mag sie.

Und unterschätzen Sie niemals die Wichtigkeit von Nummer 3.

Ich möchte noch ein Problem ansprechen:

... also ist es etwas, was wir normalerweise tun würden, wenn wir in der Lage wären, die Laufreihenfolge willkürlich zu tauschen. Zum Beispiel, würden Sie nicht plötzlich setzen wollen declare_variablesnach walk_into_bar, dass sich die Dinge brechen.

Um den Vorteil einer Aufteilung des Codes in Funktionen zu erzielen, sollten Sie versuchen, die Funktionen so unabhängig wie möglich zu gestalten. Wenn walk_into_bareine Variable erforderlich ist, die an keiner anderen Stelle verwendet wird, sollte diese Variable in definiert und lokal für festgelegt werden walk_into_bar. Das Aufteilen des Codes in Funktionen und das Minimieren ihrer gegenseitigen Abhängigkeiten sollten den Code klarer und einfacher machen.

Im Idealfall sollten Funktionen einfach einzeln zu testen sein. Wenn sie aufgrund von Interaktionen nicht einfach zu testen sind, ist dies ein Zeichen dafür, dass sie von einem Refactoring profitieren könnten.


Ich würde argumentieren, dass es manchmal sinnvoll ist, diese Abhängigkeiten zu modellieren und zu erzwingen, anstatt sie umzugestalten, um sie zu vermeiden (denn wenn genug davon vorhanden und ausreichend behaart sind, kann dies dazu führen, dass die Dinge nicht mehr modularisiert werden funktioniert überhaupt). Ein sehr komplizierter Anwendungsfall hat einst ein Framework dazu inspiriert .
Charles Duffy

4
Was in Funktionen unterteilt werden muss, sollte sein, aber das Beispiel geht zu weit. Ich denke, das einzige, was mich wirklich stört, ist die Variablendeklarationsfunktion. Globale Variablen, insbesondere statische, sollten global in einem dafür vorgesehenen kommentierten Abschnitt definiert werden. Dynamische Variablen sollten lokal für die Funktionen sein, die sie verwenden und ändern.
Xalorous

@Xalorous Ich habe eine Vorgehensweise gesehen, bei der globale Variablen in einer Prozedur initialisiert werden. Dies ist ein Zwischenschritt und ein schneller Schritt vor der Entwicklung einer Prozedur, die ihren Wert aus einer externen Datei liest Initialisierung, aber selten muss man sich bücken, um zum Vorteil Nummer 3 zu kommen;-)
Hastur

13

Sie unterteilen den Code in Funktionen aus dem gleichen Grund, aus dem Sie dies für C / C ++, Python, Perl, Ruby oder einen beliebigen Programmiersprachencode tun würden. Der tiefere Grund ist die Abstraktion - Sie kapseln Aufgaben auf niedrigerer Ebene in übergeordnete Grundelemente (Funktionen), sodass Sie sich nicht darum kümmern müssen, wie die Dinge ausgeführt werden. Gleichzeitig wird der Code lesbarer (und wartbarer) und die Programmlogik wird klarer.

Wenn ich mir jedoch Ihren Code anschaue, finde ich es ziemlich seltsam, eine Funktion zum Deklarieren von Variablen zu haben. das lässt mich wirklich die Augenbrauen hochziehen.


Unterschätzte Antwort IMHO. Schlagen Sie dann vor, die Variablen in der mainFunktion / Methode zu deklarieren ?
David Tabernero M.

12

Obwohl ich der Wiederverwendbarkeit , Lesbarkeit und dem zarten Küssen der Chefs vollkommen zustimme , gibt es einen weiteren Vorteil der Funktionen in : den variablen Umfang . Wie LDP zeigt :

#!/bin/bash
# ex62.sh: Global and local variables inside a function.

func ()
{
  local loc_var=23       # Declared as local variable.
  echo                   # Uses the 'local' builtin.
  echo "\"loc_var\" in function = $loc_var"
  global_var=999         # Not declared as local.
                         # Therefore, defaults to global. 
  echo "\"global_var\" in function = $global_var"
}  

func

# Now, to see if local variable "loc_var" exists outside the function.

echo
echo "\"loc_var\" outside function = $loc_var"
                                      # $loc_var outside function = 
                                      # No, $loc_var not visible globally.
echo "\"global_var\" outside function = $global_var"
                                      # $global_var outside function = 999
                                      # $global_var is visible globally.
echo                      

exit 0
#  In contrast to C, a Bash variable declared inside a function
#+ is local ONLY if declared as such.

Ich sehe das nicht sehr oft in echten Shell-Skripten, aber es scheint eine gute Idee für komplexere Skripten zu sein. Durch das Reduzieren der Kohäsion können Sie Fehler vermeiden, bei denen Sie eine Variable blockieren, die in einem anderen Teil des Codes erwartet wird.

Wiederverwendbarkeit bedeutet oft, eine gemeinsame Bibliothek von Funktionen zu erstellen und sourcediese Bibliothek in alle Ihre Skripte einzubinden. Dies wird ihnen nicht helfen, schneller zu laufen, aber es wird Ihnen helfen, sie schneller zu schreiben.


Nur wenige Leute verwenden sie explizit local, aber ich denke, die meisten Leute, die Skripte schreiben, die in Funktionen unterteilt sind, folgen immer noch dem Designprinzip. Usign localmacht es nur schwieriger, Fehler einzuführen.
Voo

localStellt Variablen für function und seine untergeordneten Elemente zur Verfügung. Daher ist es sehr schön, eine Variable zu haben, die von function A übergeben werden kann, für function B jedoch nicht verfügbar ist. Möglicherweise möchten Sie eine Variable mit demselben Namen, aber mit einem anderen Zweck. Das ist also gut, um den Umfang zu definieren, und wie Voo sagte - weniger Bugs
Sergiy Kolodyazhnyy

10

Ein ganz anderer Grund als der, der bereits in anderen Antworten angegeben wurde: Ein Grund, warum diese Technik manchmal verwendet wird, wenn die einzige Nicht-Funktionsdefinitionsanweisung auf oberster Ebene ein Aufruf von ist main, ist sicherzustellen, dass das Skript nicht versehentlich etwas Böses tut wenn das Skript abgeschnitten ist. Das Skript wird möglicherweise abgeschnitten, wenn es von Prozess A zu Prozess B (der Shell) weitergeleitet wird und Prozess A aus irgendeinem Grund beendet wird, bevor das gesamte Skript geschrieben wurde. Dies ist besonders wahrscheinlich, wenn Prozess A das Skript von einer Remote-Ressource abruft. Obwohl dies aus Sicherheitsgründen keine gute Idee ist, wird dies getan, und einige Skripte wurden geändert, um das Problem zu antizipieren.


5
Interessant! Aber ich finde es beunruhigend, dass man sich in jedem der Programme um diese Dinge kümmern muss. Andererseits ist genau dieses main()Muster in Python üblich, wo es if __name__ == '__main__': main()am Ende der Datei verwendet wird.
Martin Ueding

1
Das Python-Idiom hat den Vorteil, dass andere Skripte importdas aktuelle Skript ohne Ausführung lassen main. Ich nehme an, eine ähnliche Wache könnte in ein Bash-Skript eingefügt werden.
Jake Cobb

@ Jake Cobb Ja. Das mache ich jetzt in allen neuen Bash-Skripten. Ich habe ein Skript, das eine Kerninfrastruktur von Funktionen enthält, die von allen neuen Skripten verwendet werden. Dieses Skript kann bezogen oder ausgeführt werden. Wenn bezogen, wird seine Hauptfunktion nicht ausgeführt. Die Erkennung von Quelle und Ausführung erfolgt über die Tatsache, dass BASH_SOURCE den Namen des ausführenden Skripts enthält. Wenn es mit dem Kernskript identisch ist, wird das Skript ausgeführt. Ansonsten wird es beschafft.
DocSalvager

7

Ein Prozess erfordert eine Sequenz. Die meisten Aufgaben sind sequentiell. Es macht keinen Sinn, sich mit der Bestellung herumzuschlagen.

Aber die große Sache beim Programmieren - einschließlich Skripten - ist das Testen. Testen, testen, testen. Mit welchen Testskripten müssen Sie derzeit die Richtigkeit Ihrer Skripten überprüfen?

Ihr Chef versucht, Sie vom Drehbuchkind zum Programmierer zu führen. Das ist eine gute Richtung. Leute, die nach dir kommen, werden dich mögen.

ABER. Denken Sie immer an Ihre prozessorientierten Wurzeln. Wenn es sinnvoll ist, die Funktionen in der Reihenfolge anzuordnen, in der sie normalerweise ausgeführt werden, dann tun Sie dies zumindest im ersten Durchgang.

Später werden Sie feststellen, dass einige Ihrer Funktionen die Eingabe, andere die Ausgabe, andere die Verarbeitung, andere die Modellierung von Daten und andere die Bearbeitung der Daten verarbeiten. Es kann daher sinnvoll sein, ähnliche Methoden zu gruppieren und sie möglicherweise sogar in separate Dateien zu verschieben .

Später werden Sie vielleicht feststellen, dass Sie jetzt Bibliotheken mit kleinen Hilfsfunktionen geschrieben haben, die Sie in vielen Ihrer Skripten verwenden.


6

Kommentare und Abstände können nicht annähernd die Lesbarkeit erreichen, die Funktionen können, wie ich demonstrieren werde. Ohne Funktionen kann man den Wald vor lauter Bäumen nicht sehen - große Probleme verbergen sich in vielen Detaillinien. Mit anderen Worten, man kann sich nicht gleichzeitig auf die feinen Details und das Gesamtbild konzentrieren. Das mag in einem kurzen Drehbuch nicht offensichtlich sein; Solange es kurz bleibt, ist es möglicherweise ausreichend lesbar. Software wird jedoch nicht kleiner, sondern größer und gehört mit Sicherheit zum gesamten Softwaresystem Ihres Unternehmens, das mit Sicherheit sehr viel größer ist, wahrscheinlich Millionen von Zeilen.

Überlegen Sie, ob ich Ihnen Anweisungen wie diese gegeben habe:

Place your hands on your desk.
Tense your arm muscles.
Extend your knee and hip joints.
Relax your arms.
Move your arms backwards.
Move your left leg backwards.
Move your right leg backwards.
(continue for 10,000 more lines)

Bis Sie die Hälfte oder sogar 5% erreicht haben, haben Sie die ersten Schritte vergessen. Die meisten Probleme konnten Sie unmöglich erkennen, da Sie den Wald vor lauter Bäumen nicht sehen konnten. Vergleichen Sie mit Funktionen:

stand_up();
walk_to(break_room);
pour(coffee);
walk_to(office);

Das ist sicherlich viel verständlicher, egal wie viele Kommentare Sie in die zeilenweise sequentielle Version einfügen. Es macht es auch viel wahrscheinlicher, dass Sie bemerken, dass Sie vergessen haben , Kaffee zu kochen, und dass Sie am Ende wahrscheinlich sit_down () vergessen haben. Wenn Ihr Verstand an die Details von Grep und Awk Regexes denkt, können Sie kein großes Bild denken - "Was ist, wenn kein Kaffee gemacht wird?"

Mit den Funktionen können Sie in erster Linie den Überblick behalten und feststellen, dass Sie vergessen haben, Kaffee zuzubereiten (oder dass jemand Tee bevorzugt). Zu einem anderen Zeitpunkt, in einer anderen Stimmung, sorgen Sie sich um die detaillierte Implementierung.

Es gibt natürlich auch andere Vorteile, die in anderen Antworten erörtert werden. Ein weiterer Vorteil, der in den anderen Antworten nicht klar angegeben ist, besteht darin, dass Funktionen eine Garantie bieten, die wichtig ist, um Fehler zu verhindern und zu beheben. Wenn Sie feststellen, dass eine Variable $ foo in der richtigen Funktion walk_to () falsch war, müssen Sie sich nur die anderen 6 Zeilen dieser Funktion ansehen, um alles zu finden, was von diesem Problem betroffen sein könnte, und alles, was davon betroffen sein könnte habe es falsch gemacht. Ohne (ordnungsgemäße) Funktionen kann alles und jedes im gesamten System dazu führen, dass $ foo falsch ist und alles und jedes von $ foo beeinflusst wird. Daher können Sie $ foo nicht sicher reparieren, ohne jede einzelne Zeile des Programms erneut zu untersuchen. Wenn $ foo für eine Funktion lokal ist,


1
Dies ist keine bashSyntax. Es ist allerdings eine Schande; Ich glaube nicht, dass es eine Möglichkeit gibt, Eingaben an solche Funktionen weiterzuleiten. (dh pour();< coffee). Es sieht eher aus wie c++oder php(denke ich).
Stimmen

2
@ tjt263 Ohne die Klammern lautet die Bash-Syntax: Kaffee einschenken. Mit Parens ist es so ziemlich jede andere Sprache. :)
Ray Morris

5

Einige relevante Binsenweisheiten zur Programmierung:

  • Ihr Programm wird sich ändern, auch wenn Ihr Chef darauf besteht, dass dies nicht der Fall ist.
  • Nur Code und Eingaben beeinflussen das Verhalten des Programms.
  • Die Benennung ist schwierig.

Kommentare beginnen als eine Lücke, um Ihre Ideen nicht klar in Code * auszudrücken, und werden durch Änderungen schlimmer (oder einfach falsch). Drücken Sie daher, wenn möglich, Konzepte, Strukturen, Argumentation, Semantik, Ablauf, Fehlerbehandlung und alles andere aus, was für das Verständnis des Codes als Code von Belang ist.

Bash-Funktionen haben jedoch einige Probleme, die in den meisten Sprachen nicht zu finden sind:

  • Namespacing ist in Bash schrecklich. Wenn Sie beispielsweise vergessen, das localSchlüsselwort zu verwenden, wird der globale Namespace verschmutzt.
  • Die Verwendung von local foo="$(bar)"führt zum Verlust des Exit-Codes vonbar .
  • Da es keine benannten Parameter gibt, müssen Sie berücksichtigen, was "$@"in verschiedenen Kontexten bedeutet.

* Es tut mir leid, wenn dies beleidigt, aber nachdem ich Kommentare für einige Jahre verwendet und ohne sie entwickelt habe ** für weitere Jahre ist es ziemlich klar, was überlegen ist.

** Die Verwendung von Kommentaren für die Lizenzierung, API-Dokumentation und dergleichen ist weiterhin erforderlich.


Ich setze fast alle lokalen Variablen, indem ich sie am Anfang der Funktion auf null setze. local foo=""Dann setze ich sie mit der Befehlsausführung, um auf das Ergebnis zu reagieren foo="$(bar)" || { echo "bar() failed"; return 1; }. Dies bringt uns schnell aus der Funktion heraus, wenn ein erforderlicher Wert nicht eingestellt werden kann. Die geschweiften Klammern sind notwendig, um sicherzustellen, dass das Programm return 1nur im Fehlerfall ausgeführt wird.
DocSalvager

5

Zeit ist Geld

Es gibt andere gute Antworten , die die technischen Gründe für das modulare Schreiben eines möglicherweise langen Skripts beleuchten , das in einer Arbeitsumgebung entwickelt wurde und von einer Gruppe von Personen verwendet werden kann und nicht nur für den eigenen Gebrauch bestimmt ist.

Ich möchte Fokus auf der einen erwarten: in einer Arbeitsumgebung „Zeit ist Geld“ . So werden das Fehlen von Fehlern und die Leistung Ihres Codes zusammen mit Lesbarkeit , Testbarkeit, Wartbarkeit, Refactorability, Wiederverwendbarkeit ... bewertet.

Das Schreiben eines Codes in "Module" verringert nicht nur die vom Codierer selbst benötigte Lesezeit , sondern auch die von den Testern oder vom Chef verwendete Zeit. Beachten Sie außerdem, dass die Zeit eines Chefs in der Regel mehr als die Zeit eines Programmierers bezahlt wird und dass Ihr Chef die Qualität Ihrer Arbeit beurteilt.

Darüber hinaus können Sie durch das Schreiben eines Codes (sogar eines Bash-Skripts) in unabhängigen "Modulen" "parallel" mit anderen Komponenten Ihres Teams arbeiten. Dies verkürzt die gesamte Produktionszeit und nutzt bestenfalls das Fachwissen der Single, um einen Teil zu überprüfen oder umzuschreiben Keine Nebenwirkung für die anderen, um den Code, den Sie gerade geschrieben haben, so wie er ist , zu recyceln.für ein anderes Programm / Skript, um Bibliotheken (oder Bibliotheken von Snippets) zu erstellen, um die Gesamtgröße und die damit verbundene Fehlerwahrscheinlichkeit zu verringern, um jeden einzelnen Teil gründlich zu debuggen und zu testen ... und natürlich wird Ihr Programm in logischen Abschnitten organisiert / script und verbessern die Lesbarkeit. Alles, was Zeit und damit Geld spart. Der Nachteil ist, dass Sie sich an Standards halten und Ihre Funktionen kommentieren müssen (die Sie jedoch in einer Arbeitsumgebung tun müssen).

Die Einhaltung eines Standards verlangsamt Ihre Arbeit am Anfang, beschleunigt jedoch die Arbeit aller anderen (und auch Ihrer) danach. In der Tat wird dies zu einem unvermeidlichen Bedürfnis, wenn die Anzahl der beteiligten Personen ansteigt. Selbst wenn ich zum Beispiel der Meinung bin, dass die globalen Variablen global und nicht in einer Funktion definiert werden müssen, kann ich einen Standard verstehen, der sie in einer Funktion mit dem declare_variables()Namen always in der ersten Zeile der main()einen initialisiert.

Nicht zuletzt sollten Sie die Möglichkeit in modernen Quellcode-Editoren nicht unterschätzen, einzelne Routinen selektiv ein- oder auszublenden ( Code Folding ). Dies hält den Code kompakt und spart dem Benutzer erneut Zeit.

Bildbeschreibung hier eingeben

Hier oben können Sie sehen, wie es nur die Funktion entfaltetwalk_into_bar() . Selbst wenn die anderen jeweils 1000 Zeilen lang waren, konnten Sie den gesamten Code auf einer einzigen Seite unter Kontrolle halten. Beachten Sie, dass auch der Abschnitt, in dem Sie die Variablen deklarieren / initialisieren, geklappt ist.


1

Abgesehen von den in anderen Antworten genannten Gründen:

  1. Psychologie: Ein Programmierer, dessen Produktivität in Codezeilen gemessen wird, hat einen Anreiz, unnötig ausführlichen Code zu schreiben. Je mehr sich das Management auf Codezeilen konzentriert, desto größer ist der Anreiz für den Programmierer, seinen Code mit unnötiger Komplexität zu erweitern. Dies ist unerwünscht, da eine erhöhte Komplexität zu erhöhten Wartungskosten und einem erhöhten Aufwand für die Fehlerbehebung führen kann.

Es ist keine so schlechte Antwort, wie die Downvotes sagen. Hinweis: agc sagt, auch das ist eine Möglichkeit und ja, das ist es. Er sagt nicht, dass dies die einzige Möglichkeit wäre, und beschuldigt niemanden, sondern nennt nur die Fakten. Obwohl ich denke, dass heutzutage eine direkte "Codezeile" -> "$$" - Auftragsart kaum zu hören ist, ist es auf indirekte Weise durchaus üblich, dass ja die Masse des produzierten Codes von den Leitern / Chefs gezählt wird.
user259412

0

Ein weiterer Grund, der oft übersehen wird, ist die Syntaxanalyse von bash:

set -eu

echo "this shouldn't run"
{
echo "this shouldn't run either"

Dieses Skript enthält offensichtlich einen Syntaxfehler und Bash sollte es überhaupt nicht ausführen, oder? Falsch.

~ $ bash t1.sh
this shouldn't run
t1.sh: line 7: syntax error: unexpected end of file

Wenn wir den Code in eine Funktion einbinden würden, würde dies nicht passieren:

set -eu

main() {
  echo "this shouldn't run"
  {
  echo "this shouldn't run either"
}

main
~ $ bash t1.sh
t1.sh: line 10: syntax error: unexpected end of file
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.