ubuntuusers.de

Shell/Bash-Skripting-Guide für Anfänger

Status: Ungelöst | Ubuntu-Version: Nicht spezifiziert
Antworten |
Dieses Thema ist die Diskussion des Artikels Shell/Bash-Skripting-Guide_für_Anfänger.

wxpte

Anmeldungsdatum:
20. Januar 2007

Beiträge: 1388

Hallo BillMaier,

die Ergänzung habe ich angesichts dieser Kritik (Punkt 2) vorgenommen. Das war mir vorher so auch nicht bewusst, daher hielt ich es für angebracht, im Wiki-Artikel darauf hinzuweisen.

BillMaier Team-Icon

Supporter

Anmeldungsdatum:
4. Dezember 2008

Beiträge: 6497

Hallo wxpte, danke für deine Erklärung. Beim genauen Lesen verstehe ich jetzt, wie das gemeint ist. Es geht um die Ausgabe STOUT bzw. STDERR, die man definitiv nicht im Skript verarbeiten sollte. find mit dem Parameter exec dagegen lässt sich super skripten. Ich bin gerade nur am Überlegen, wie man das jetzt im Artikel abgrenzt, denn so liest es sich beim nicht-genauen Hinschauen, als ob find nicht geeignet für Skripting wäre (auch wenn es anders da steht).

track

Avatar von track

Anmeldungsdatum:
26. Juni 2008

Beiträge: 7174

Wohnort: Wolfen (S-A)

Die Warnbox erläutert argumentativ ja ausdrücklich die Probleme mit ls . - Von daher ist der simple Zusatz "und find" tatsächlich nicht ganz klar und korrekt.

Vielleicht könnte man besser für find unten ein Postskript einfügen ? Sowas vielleicht:

Achtung!

Es ist verführerisch, die Ausgabe des Befehls ls im Skript zu verarbeiten. Das wäre aber gleich in mehrerer Hinsicht gefährlich:

  • Leerzeichen und Zeilenwechsel in Dateinamen verursachen oft Probleme, da sie als Ende des Dateinamens interpretiert werden oder weil man die Eingabe zeilenweise verarbeitet und so den Dateinamen durchschneidet.

  • Abhängig vom locale und einigen anderen Einstellungen ersetzt ls manche Zeichen durch ein oder mehrere "?". Dies ist aber bei bash der Platzhalter für "jedes Zeichen" und führt leicht zu Mehrdeutigkeiten, ebenso wie der '*', sofern er im Dateinamen vorkommt.

Entsprechend würde man auch beim Durchsuchen von Verzeichnisbäumen die Ausgabe von find nicht parsen, sondern die gewünschten Befehle mit der Option -exec in einem Unterskript ausführen:
find [Suchmuster] -exec bash -c '# hier; kommen; die; Befehle' {} \;

Jetzt ist es natürlich die Frage, ob solch ein Codevorschlag an dieser Stelle nicht ein zu weiter Vorgriff auf "fortgeschrittene Techniken" ist ...
aber deutlicher wäre es auf jeden Fall.

- ach ja, rein formal: ein Codeblock in der Warnbox funktioniert nicht. Daran verschluckt sich der Inyoka-Parser !

LG,

track

BillMaier Team-Icon

Supporter

Anmeldungsdatum:
4. Dezember 2008

Beiträge: 6497

Danke für den Vorschlag, track!

Habe das jetzt so ergänzt:

Entsprechend wird man auch die Ausgabe von find nicht parsen, sondern die entsprechenden Befehle mit der Option -exec in einem Unterskript ausführen. Beispiele dazu findet man im Artikel find.

Wenn du die Beispiele dort ergänzen möchtest → gerne!

Den Abschnitt über ls habe ich jetzt nicht kontrolliert - falls ich da noch Änderungsvorschläge übersehen habe, bitte nochmal kurze Info.

user_unknown

Avatar von user_unknown

Anmeldungsdatum:
10. August 2005

Beiträge: 17621

Wohnort: Berlin

Beim letzten Edit ist was schiefgelaufen - mitten im Code steht eine Art Kommentar:

Um in einer while-Schleife sinnvoll Arrays zu verwenden, muss man noch ein wenig anders vorgehen:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
while read record
do
for z in {1..n}
do
array[$z]=$(echo $record | cut -f$z -d\;)
done
Befehle unter Einbeziehung der Felder ${array[1]} bis ${array[n]}
Felder, die Leerzeichen enthalten, müssen gequotet werden,
z. B. "${array[5]}"
done < Datenliste.csv

Die Einleitung finde ich schon unklar. Das Beispiel macht ja was anderes, als das Beispiel davor - wie kann man da davon reden, dass man ein wenig anders vorgehen muss?

Hilfreich wäre es auch, eine Beispiel-CSV-Datei zu zeigen und das Ergebnis dieses Einlesens.

Die Zeile for z in {1..n} funktioniert nicht - weder, wenn es Variable n gibt, noch wenn es die nicht gibt. Beispielcode sollte man ausprobieren, bevor man in postet.

Ein Array würde man auch mit readarray/mapfile einlesen:

1
readarray -d ";" -t  array < Datenliste.csv 

Bitte auch die Vorschau nutzen, um solch offensichtlichen Fehler selbst zu sehen.

Und shellcheck für Scripte drüberlaufen lassen, das findet auch gerne mal was.

noisefloor Team-Icon

Anmeldungsdatum:
6. Juni 2006

Beiträge: 29567

Hallo,

Danke für den Hinweis. Habe die Revision aufgrund der div. Fehler zurück gesetzt.

Gruß, noisefloor

kB Team-Icon

Supporter, Wikiteam
Avatar von kB

Anmeldungsdatum:
4. Oktober 2007

Beiträge: 9729

Wohnort: Münster

Detail zur Variablenzuweisung ergänzt und sachlichen Fehler beim case-Statement korrigiert.

user_unknown

Avatar von user_unknown

Anmeldungsdatum:
10. August 2005

Beiträge: 17621

Wohnort: Berlin

Die While-Schleife, die prüft, ob man 2+2 addieren kann, habe ich umgeschrieben, und die 4 als Abbruchbedingung eingetragen, was die Semantik des Codes perfekt widerspiegelt und dafür das exit rausgeworfen.

Bei der For-Schleife mit dem "Hallo" "du" "Welt" ... habe ich den Anführungszeichen einen Sinn verliehen, indem ich es zu

1
for wort in Hallo "du Welt" "da draussen" 

geändert habe.

Dieser Absatz macht mich noch krank:

Die Syntax für die for-Schleife sieht folgendermaßen aus.

1
2
3
4
5
6
for variable in "Parameterliste"
  do
    Befehl1
    Befehl2
    # usw.
done

So sieht die Syntax der For-Schleife nicht aus.

Es gibt 3 Formen der For-Schleife, die man freilich in ein Syntaxdiagramm packen kann, das dann aussieht wie im Anhang.

Das Wort Parameterliste zu verwenden ist Humbug, denn auch wenn es so heißt, ist es keine Parameterliste. Gänzlich konterkariert wird die Idee einer Liste durch die umschließenden Anführungsstriche, die den Sinn einer Schleife selbst dann aufhöben, wenn die Parameterliste eine Variable wäre.

Jetzt ist mir eine deutliche Verbesserung eingefallen, die man vornehmen kann:

1
2
3
4
5
params="a b c"
for z in $params
do
    echo $z
done 

Output:

1
2
3
a
b
c

Das Casebeispiel, welches auf ein echo $name hinauslief, habe ich auch ersetzt.

Eigentlich denke ich, dass der Pseudocode oben mit wert1 bis wert4 und Befehl... keinen Vorteil hat gegenüber der Erklärung am konkreten Beispiel. Ich würd's rauswerfen, und die Erklärung ans echte Beispiel anpassen.

Motto: Kürze=Würze

Das Shift-Beispiel ist auch kaputt:

1
2
3
4
5
6
7
8
#!/bin/bash

while [ "$1" != '' ]
  do
    [ $1 == "-b" ] && BACKUP="yes" && echo "Ok. Do a backup!" && shift
    [ $1 == "-r" ] && RESTORE="yes" && echo "Ok. Do a restore!" && shift
    [ $1 == "-c" ] && CLEAN="yes" && echo "Ok. Tidy up!" && shift
done

Gibt der User etwas anderes als -[brc] ein, hängt er in einer Endlosschleife fest. Außerdem leuchtet nicht ein, dass man bacckup + restore unmittelbar hintereinander machen will und mehr als 9 Parameter sind es auch nicht.

Mein Vorschlag wäre linecalc.sh:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#!/bin/bash
#
op=$1

case $op in
        "add")
            erg=0
            op='+'
            ;;
	"mul")
	    erg=1
	    op='*'
            ;;
        *)
            echo unhandelt operator "$op" && \
            echo -e "\tUsage: $0 add 1 3 5 2 7 9 22 1\n"  \
            "\tor:    $0 mul 1 3 5 2 7 9 22 1\n"  && exit 1
            ;;
esac

shift

for n
do
	erg=$((erg $op n))
done

echo $erg
Bilder

kB Team-Icon

Supporter, Wikiteam
Avatar von kB

Anmeldungsdatum:
4. Oktober 2007

Beiträge: 9729

Wohnort: Münster

user_unknown schrieb:

Die While-Schleife, die prüft, ob man 2+2 addieren kann, habe ich umgeschrieben, und die 4 als Abbruchbedingung eingetragen, was die Semantik des Codes perfekt widerspiegelt und dafür das exit rausgeworfen. […]

Ehrlich gesagt, empfinde ich das eher als Verschlechterung des Artikels. Nach dem Titel „Bash-Skripting-Guide für Anfänger“ geht es hier nicht um die Qualität der Beispielskripten, sondern um die Erklärung der Systaxkonstrukte für Anfänger. Dafür können aber Trivialbeispiele oft didaktisch wertvoller sein als raffinierte Programme. Trivialbeispiele legen den Fokus auf das jeweils zu erklärende Syntaxelement, während raffiniertere Formulierungen davon ablenken.

Deine Programme sind zwar technisch bessere Programme als die vorherigen trivialen Beispiele, aber in diesem Artikel didaktisch schlechter. Dies gilt im gleichen Sinne auch für Deine weiteren Änderungen bis auf:

Das Shift-Beispiel ist auch kaputt […] Endlosschleife […]

Endlosschleifen sollten man nicht als Beispiel benutzen, das sehe ich auch so. Das ist aber einfacher zu reparieren als Dein Radikalumbau: Man muss nur den Befehl shift aus den Verbund-Statements entfernen und als einzelnen Befehl von done notieren.

user_unknown

Avatar von user_unknown

Anmeldungsdatum:
10. August 2005

Beiträge: 17621

Wohnort: Berlin

kB schrieb:

user_unknown schrieb:

Die While-Schleife, die prüft, ob man 2+2 addieren kann, habe ich umgeschrieben, und die 4 als Abbruchbedingung eingetragen, was die Semantik des Codes perfekt widerspiegelt und dafür das exit rausgeworfen. […]

Ehrlich gesagt, empfinde ich das eher als Verschlechterung des Artikels. Nach dem Titel „Bash-Skripting-Guide für Anfänger“ geht es hier nicht um die Qualität der Beispielskripten, sondern um die Erklärung der Systaxkonstrukte für Anfänger.

Genau. Eine Endlosschleife ist dabei eine besondere, degenerierte Form der Schleife, und kann durchaus Sinn machen, aber nicht, wenn das Abbruchkriterium so auf der Hand liegt. Eine Endlosschleife mit if/exit zu verwenden ist um die Ecke gedacht, und könnte man wg. Phantasielosigkeit durchgehen lassen, wenn das Ziel des Abschnitts die Darstellung von Endlosschleifen wäre.

Seit dem hl. Urvater Turing sind wir aber froh, wenn unsere Programme anständig und vorhersehbar terminieren und die Endlosschleife ist nach meinem Dafürhalten nicht die erste Schleife, die man unterrichtet, sondern die letzte. Und das Abbruchkriterium drängt sich wie gesagt auf.

Dafür können aber Trivialbeispiele oft didaktisch wertvoller sein als raffinierte Programme.

(( answer != 4 )) ist nicht raffiniert. Das ist einer der simplesten Ausdrücke, der sich denken und formulieren lässt.

Trivialbeispiele legen den Fokus auf das jeweils zu erklärende Syntaxelement, während raffiniertere Formulierungen davon ablenken.

Welche raffinierte Formulierung? Alternativ musst Du in die Kontrollstruktur eine weitere Kontrollstruktur einbauen und das Schlüsselwort exit dazuholen. Das ist ja für jeden sichtbar die komplexere Problemlösung.

Deine Programme sind zwar technisch bessere Programme als die vorherigen trivialen Beispiele, aber in diesem Artikel didaktisch schlechter. Dies gilt im gleichen Sinne auch für Deine weiteren Änderungen

Das müsstest Du konkret am Beispiel zeigen.

Ich glaube, ein Beispiel mit konkretem Fleisch, das einen Kontext in den Raum stellt, in dem eine Struktur einen Sinn macht, ist besser verständlich, als ein

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
case $name in 
   Heiner)
       echo Hallo Heiner;;
   Waltraud) 
       echo Hallo Waltraud;;
   "Monika und Moritz")
       echo hallo "Monika und Moritz";;
   *) 
       echo hallo Fremder;;
esac

Es gibt keinen Kontext, in dem man solch einen Code schreiben würde, weil man

1
2
3
4
5
6
case $name in 
   Heiner|Waltraud|"Monika und Moritz")
       echo hallo $name;;
   *) 
       echo hallo Fremder;;
esac

schreiben würde.

bis auf:

Das Shift-Beispiel ist auch kaputt […] Endlosschleife […]

Endlosschleifen sollten man nicht als Beispiel benutzen, das sehe ich auch so. Das ist aber einfacher zu reparieren als Dein Radikalumbau: Man muss nur den Befehl shift aus den Verbund-Statements entfernen und als einzelnen Befehl von done notieren.

Was ist denn der Sinn, backup und restore unmittelbar nacheinander aufzurufen, oder 2x backup, 3x restore? Es macht keinen Sinn. Semantisch würde man ein Programm kaum so schreiben.

Es stimmt aber insoweit, als meine Alternative, nachdem die Operation isoliert wurde (add, mul) die einzelnen Parameter auch nicht über shift nach und nach einzieht, sondern zur for-Schleife wechselt, die ebenfalls mit mehr als 9 Parametern zurechtkommt. Ursprünglich wollte ich die alle qua shift addieren/multiplizieren, aber das schien mir dann unnötig kompliziert, und als Lernender will ich ja wissen, welchen Prototyp an Problem ich mit einem Syntaxkonstrukt elegant lösen kann. Den ersten Parameter, der vom Typ nicht zum Rest der Liste passt, zu konsumieren, ist so ein Problem. Ein anderes Beispiel wäre sowas wie

1
move TARGETDIR file1 file2 file3...

Gleiches Muster: Eine unbestimmt lange Liste an Elementen, die alle nach Schema F verarztet werden, aber (mindestens) ein Paramter, der aus dem Schema rausfällt. Würde man aber auch über 1x shift + for param lösen.

Das Beispielscript lässt sich ohne Endlosschleife und shift eleganter so lösen:

1
2
3
4
5
6
7
8
#!/bin/bash

for param
do
    [ $param == "-b" ] && BACKUP="yes" && echo "Ok. Do a backup!"
    [ $param == "-r" ] && RESTORE="yes" && echo "Ok. Do a restore!"
    [ $param == "-c" ] && CLEAN="yes" && echo "Ok. Tidy up!"
done

Vergleich:

1
2
3
4
5
6
7
8
9
#!/bin/bash

while [ "$1" != '' ]
do
    [ $1 == "-b" ] && BACKUP="yes" && echo "Ok. Do a backup!" 
    [ $1 == "-r" ] && RESTORE="yes" && echo "Ok. Do a restore!" 
    [ $1 == "-c" ] && CLEAN="yes" && echo "Ok. Tidy up!" 
    shift
done

kB Team-Icon

Supporter, Wikiteam
Avatar von kB

Anmeldungsdatum:
4. Oktober 2007

Beiträge: 9729

Wohnort: Münster

user_unknown schrieb:

[…] Das Beispielscript lässt sich ohne Endlosschleife und shift eleganter so lösen

Da hast Du unbestritten Recht. Aber es geht in diesem Artikel nicht um Eleganz (ich habe das Wort „raffiniert“ benutzt), sondern darum, Anfängern die umfangreichen Möglichkeiten von bash zu erläutern. Dazu muss man auswählen und auch kleinere Ungenauigkeiten und Unvollständigkeiten in Kauf nehmen. Die Beispiele sollen die Befehle und die Syntax erläutern und sind nicht dazu gedacht, selbst in der Praxis angewendet zu werden. Trivialbeispiele können in einem anderen Sinn, nämlich didaktisch optimaler sein als Deine funktional optimierten Programme. Für diesen Artikel ist aber der didaktische Aspekt wichtiger als der funktionale. Du optimiertest hier in die falsche Richtung und das ist m.E. schlecht für diesen Artikel. An anderer Stelle, z.B. im Forum „Shell und Programmieren“ wäre ich dagegen mit Deinem Ansatz und Deinen Lösungen vollständig einverstanden.

user_unknown

Avatar von user_unknown

Anmeldungsdatum:
10. August 2005

Beiträge: 17621

Wohnort: Berlin

kB schrieb:

user_unknown schrieb:

[…] Das Beispielscript lässt sich ohne Endlosschleife und shift eleganter so lösen

Da hast Du unbestritten Recht. Aber es geht in diesem Artikel nicht um Eleganz (ich habe das Wort „raffiniert“ benutzt), sondern darum, Anfängern die umfangreichen Möglichkeiten von bash zu erläutern.

Das ist mir bekannt und auch mein Anliegen, wie bereits geschrieben.

Dazu muss man auswählen und auch kleinere Ungenauigkeiten und Unvollständigkeiten in Kauf nehmen.

Behauptung ohne Beleg.

Es ist nicht so, dass ich nicht bereit wäre Ungenauigkeiten und Unvollständigkeiten in Kauf zu nehmen, wenn es dafür eine Begründung gibt.

Die Beispiele sollen die Befehle und die Syntax erläutern

Welche Syntax? Um welchen Kontext geht es? Was gehört da hin, was gehört da eher nicht hin. Man könnte Gott und die Welt erklären, vom Hölzchen aufs Stöckchen und wieder zurück.

Es geht um Kontrollstrukturen, erst Verzweigungen, if und case, dann Schleifen, for und while. Besteht soweit Einigkeit, auch dass das eine sinnvolle Struktur ist?

Bisher ging der Text (der Autor, die Autoren) mehrfach so vor, etwas, was als Syntax benannt wurde, in halb verallgemeinerter Form vorzustellen, mit

1
2
3
4
5
6
while [ Test-Bedingung ]
do
    Befehl1
    Befehl2
    # usw.
done

Um dann Beispiele nachzuschieben, in denen die Testbedingung und Befehl1, Befehl2, usw. mit Leben gefüllt wurden.

Und hier hat man die Testbedingung, die sich aus dem Beispiel aufdrängt - ist die Summe 4 oder ist sie nicht 4 - das ist der einfache Geradeausweg. Man muss doch hier um die Ecke denken, um zu sagen, "Hey, wir machen hier eine Endlosschleife hin, so als wüssten wir keine gescheite Abbruchbedingung, und dann fällt sie uns doch ein, die Abbruchbedingung, aber statt unsere Struktur, die wir eigentlich hier erklären wollen, zu nutzen, pfuschen wir den Abbruch über ein unsauberes break ein.

Der saubere Weg bedarf doch keiner Rechtfertigung, sondern der unsaubere. Aber Du bringst kein Argument.

und sind nicht dazu gedacht, selbst in der Praxis angewendet zu werden. Trivialbeispiele können in einem anderen Sinn, nämlich didaktisch optimaler sein als Deine funktional optimierten Programme.

Da ist nichts funktional optimiertes, an dem Programm, sondern die sich aufdrängende Abbruchbedingung, hat der User die Zahl 4 richtig errechnet und eingegeben, wird benutzt, so wie es die Syntax der while-Loop anbietet.

Der unsaubere Umweg über eine Endlosschleife mit break bedarf doch hier der Begründung. Der ist auch nicht trivialer hier, sondern komplexer.

Für diesen Artikel ist aber der didaktische Aspekt wichtiger als der funktionale. Du optimiertest hier in die falsche Richtung und das ist m.E. schlecht für diesen Artikel. An anderer Stelle, z.B. im Forum „Shell und Programmieren“ wäre ich dagegen mit Deinem Ansatz und Deinen Lösungen vollständig einverstanden.

Ja, was ist der didaktische Aspekt, der hier wichtig ist? Dass man die Abbruchbedingung versteckt? Dass man ein nicht erklärtes break verwendet? Eine Endlosschleife? Beides ist nicht Thema. Break ist schlechter Stil. (UPDATE: Anfangs hatte ich von exit, nicht von break geschrieben, aber so schlimm war es nicht. Dennoch ist es so, dass Schleifen eine Technik der strukturierten Programmierung sind, während das break ein Rückfall in den Spaghetticode ist. Siehe Dijkstra: GoTo-Statement considered harmful.

Ist der Lösungsweg Dein Steckenpferd, willst Du unbedingt ein break da behandeln, oder geht es Dir darum, en passant Endlosschleifen zu zeigen? Was ist der didaktische Punkt, auf den Du hinaus willst? Ich sehe ihn nicht.

Heinrich_Schwietering Team-Icon

Wikiteam
Avatar von Heinrich_Schwietering

Anmeldungsdatum:
12. November 2005

Beiträge: 11335

Wohnort: Bremen

Hi!

Bezgl. der letzten Änderung: für mcedit gibt es IMHO keinen Wiki-Artikel bei uns. Ist https://www.mcedit.net/ gemeint? Oder eher https://www.mcedit-unified.net/ ? Oder mcedit aus mc-data?

so long
hank

user_unknown

Avatar von user_unknown

Anmeldungsdatum:
10. August 2005

Beiträge: 17621

Wohnort: Berlin

Letzteres.

Doc_Symbiosis

Avatar von Doc_Symbiosis

Anmeldungsdatum:
11. Oktober 2006

Beiträge: 4453

Wohnort: Göttingen

Hi!

Sollte man in dem Artikel vielleicht noch folgende Konstrukte aufnehmen?

${var:-text}
${var:+text}
${var:=text}
${#var}
${var:offset:laenge}

Vielleicht könnte man den Abschnitt "Schnitt" umbennen in "String-Operationen" und das dort mit aufnehmen?

Ich finde den Abschnitt "Schnitt" ja schon sehr hilfreich und war jetzt in der aktuellen ct (4/22) über eine schöne Tabelle dazu gestolpert, wo eben noch die anderen Möglichkeiten gelistet sind.

Gerade die ersten drei hab ich in letzter Zeit schon als sehr hilfreich empfunden, um z.B. Standardwerte für Parameter zu setzen.

EDIT: Und einen Abschnitt zum Härten von Skripten mit "set -eou pipefail" würde ich auch noch hinzufügen. Das verwende ich mittlerweile in fast jedem Skript, seit ich das entdeckt habe.

Da muss man seine Skripte natürlich schon anders bauen, wenn man das verwendet, aber i.A. finde ich das äu0erst hilfreich...