Warum ein ganzes Bash-Skript in Funktionen schreiben?

Warum ein ganzes Bash-Skript in Funktionen schreiben?

Bei der Arbeit schreibe ich häufig Bash-Skripte. Mein Vorgesetzter hat vorgeschlagen, das gesamte Skript in Funktionen aufzuteilen, ähnlich wie im 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 dafür einen anderen Grund als die „Lesbarkeit“, die meiner Meinung nach mit ein paar zusätzlichen Kommentaren und etwas Zeilenabstand ebenso gut hergestellt werden könnte?

Läuft das Skript dadurch effizienter (eigentlich würde ich eher das Gegenteil erwarten) oder ist es einfacher, den Code über das oben erwähnte Lesbarkeitspotenzial hinaus zu ändern? Oder ist es wirklich nur eine stilistische Vorliebe?

Bitte beachten Sie, dass die „Ausführungsreihenfolge“ der Funktionen in unseren tatsächlichen Skripten sehr linear ist, obwohl das Skript dies nicht gut veranschaulicht. Sie walk_into_barhängt von Dingen ab, die i_am_fooausgeführt wurden, und do_bazwirkt auf Dinge ein, die von eingerichtet wurden walk_into_bar. Daher würden wir die Ausführungsreihenfolge normalerweise nicht beliebig ändern. Sie würden beispielsweise nicht plötzlich declare_variablesnach einfügen walk_into_bar, da dies zu Problemen führen würde.

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

Antwort1

Lesbarkeitist eine Sache. Aber es gibt noch mehr zuModularisierungals nur das. (Teilmodularisierungist für Funktionen vielleicht korrekter.)

In Funktionen können Sie einige Variablen lokal halten, was erhöhtZuverlässigkeit, wodurch die Wahrscheinlichkeit verringert wird, dass etwas schiefgeht.

Ein weiteres Plus der Funktionen istWiederverwendbarkeit. Sobald eine Funktion codiert ist, kann sie im Skript mehrfach angewendet werden. Sie können sie auch in ein anderes Skript portieren.

Ihr Code ist jetzt vielleicht linear, aber in Zukunft betreten Sie vielleicht den Bereich vonMultithreading, oderMehrfachverarbeitungin der Bash-Welt. Wenn Sie erst einmal gelernt haben, Dinge in Funktionen zu tun, sind Sie für den Schritt in die Parallele gut gerüstet.

Noch ein Punkt. Wie Etsitpab Nioliv im Kommentar unten bemerkt, ist es einfach, von Funktionen als zusammenhängende Einheit umzuleiten. Aber es gibt noch einen weiteren Aspekt bei Umleitungen mit Funktionen. Die Umleitungen können nämlich entlang der Funktionsdefinition festgelegt werden. Beispiel:

f () { echo something; } > log

Nun sind keine expliziten Umleitungen durch die Funktionsaufrufe mehr nötig.

$ f

Dadurch können viele Wiederholungen vermieden werden, was wiederum die Zuverlässigkeit erhöht und dabei hilft, die Ordnung zu wahren.

Siehe auch

Antwort2

Ich habe begonnen, denselben Stil der Bash-Programmierung zu verwenden, nachdem ich gelesen hatteKfir Lavis Blogbeitrag „Defensive Bash Programming“Er nennt eine ganze Reihe guter Gründe, aber persönlich finde ich diese am wichtigsten:

  • Prozeduren werden beschreibend: Es ist viel einfacher herauszufinden, was ein bestimmter Teil des Codes tun soll. Statt einer Wand aus Code sehen Sie „Oh, die find_log_errorsFunktion liest diese Protokolldatei nach Fehlern“. Vergleichen Sie das damit, mitten in einem langen Skript eine ganze Menge awk/grep/sed-Zeilen zu finden, die Gott weiß, welche Art von regulären Ausdrücken verwenden – Sie haben keine Ahnung, was dort geschieht, es sei denn, es gibt Kommentare.

  • Sie können Funktionen debuggen, indem Sie sie in set -xund einschließen set +x. Wenn Sie wissen, dass der Rest des Codes einwandfrei funktioniert, können Sie diesen Trick verwenden, um sich auf das Debuggen nur dieser bestimmten Funktion zu konzentrieren. Natürlich können Sie Teile des Skripts einschließen, aber was ist, wenn es sich um einen längeren Abschnitt handelt? Es ist einfacher, so etwas zu tun:

       set -x
       parse_process_list
       set +x
    
  • Verwendung mit drucken cat <<- EOF . . . EOF. Ich habe es ziemlich oft verwendet, um meinen Code viel professioneller zu gestalten. Darüber hinaus ist die Funktion parse_args()mit getoptsziemlich praktisch. Auch dies hilft bei der Lesbarkeit, anstatt alles als riesige Textwand in das Skript zu stopfen. Es ist auch praktisch, diese wiederzuverwenden.

Und natürlich ist dies für jemanden, der C oder Java oder Vala kennt, aber nur begrenzte Bash-Erfahrung hat, 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 verwenden:

nice -10 resource_hungry_function

Im Vergleich zum Aufrufen von „nice“ für jede einzelne Codezeile verringert dies den Tippaufwand UND kann bequem verwendet werden, wenn Sie nur einen Teil Ihres Skripts mit niedrigerer Priorität ausführen möchten.

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

Einige Beispiele, in denen ich diesen Stil verwendet habe:

Antwort3

In meinem Kommentar habe ich drei Vorteile von Funktionen genannt:

  1. Sie lassen sich leichter testen und ihre Richtigkeit ü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 Bedeutung der Zahl 3.

Ich möchte noch auf einen weiteren Punkt eingehen:

... daher würden wir die Ausführungsreihenfolge normalerweise nicht beliebig ändern. Sie würden beispielsweise nicht plötzlich declare_variablesnach einfügen wollen walk_into_bar, das würde Dinge kaputt machen.

Um den Vorteil der Aufteilung des Codes in Funktionen zu nutzen, sollte man versuchen, die Funktionen so unabhängig wie möglich zu machen. Wenn walk_into_bareine Variable erforderlich ist, die nicht anderswo verwendet wird, sollte diese Variable in definiert und lokal für gemacht werden walk_into_bar. Der Prozess der Aufteilung des Codes in Funktionen und der Minimierung ihrer gegenseitigen Abhängigkeiten sollte den Code klarer und einfacher machen.

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

Antwort4

Sie zerlegen den Code aus demselben Grund in Funktionen, aus dem Sie das auch für C/C++, Python, Perl, Ruby oder Code in einer beliebigen Programmiersprache tun würden. Der tiefere Grund ist die Abstraktion – Sie kapseln Aufgaben auf niedrigerer Ebene in Primitive (Funktionen) auf höherer Ebene, sodass Sie sich nicht darum kümmern müssen, wie die Dinge erledigt werden. Gleichzeitig wird der Code lesbarer (und wartbarer) und die Programmlogik wird klarer.

Wenn ich mir jedoch Ihren Code anschaue, finde ich es ziemlich merkwürdig, dass es eine Funktion zum Deklarieren von Variablen gibt; das lässt mich wirklich die Augenbrauen hochziehen.

verwandte Informationen