Programmieren mit der Unix-Shell ================================ Axel Pemmann, 08.01.2026, Version 0.3 Shells ------ Seit der Unix-Geburt am 01.01.1970 wurde die ursprüngliche Schnittstelle zwischen Mensch und Maschine stetig weiterentwickelt, sie bekam Komfort-Funktionen und wichtige Steuerungsmechanismen für den Programmablauf beigebracht. Hier ein kleiner Überblick: - sh (Bourne-Shell, einfache Shell, keine Komfort-Funktion) - ksh (Korn-Shell, bei Sun Solaris und anderen großen Unixen zu finden) - csh (C-Shell, Syntax der C-Sprache) - bash (= DEFAULT, Bourne-Again-Shell, = verbesserte sh, viele Features und Komfort-Funktionen) - ash (Almquist-Shell, leicht verbesserte, sichere sh, z.B. bei Alpine Linux) - fish (die neue *User friedly Shell*, sehr empfehlenswert, aber fürs Programmieren nicht so ideal) Grundlegendes ------------- Zur Automatisierung von Aufgaben werden übliche Kommandozeilen in eine nomale Textdatei geschrieben. Diese Shell-Skripte - In Frage kommende Editoren (Office-Programme erzeugen unsichtbare Metadaten!!): nano, vim, micro, mcedit, kate, mousepad - sollten mit einem bestimmten Interpreter ausgeführt werden, z.B. ``#!/bin/bash`` (Shebang, ist optional), - sollten das eXecute-Recht erhalten (Cron-Skripte benötigen beides: Shebang und Ausführungsrecht), - dienen typischen Admin-Aufgaben wie Backups oder das Starten von Anwendungen, - führen die Kommando im einfachsten Fall nur im Batch-Betrieb aus (Stapelverarbeitung), - sind normale Kommandozeilen, die man mit Kontrollstrukturen versieht. Hierbei können wir natürlich die grundlegenden Möglichkeiten der Shell einsetzen: - Umleitungen (``>``, ``<``, ``>>``, ``<<``, ``&>``, ``2>&1``) - ACHTUNG Datenverlust! Mit der stdout-Umleitung ``>`` wird eine Datei zuerst leer gemacht, danach folgt die Befüllung!) - Pipes (``|``, Kommando ``tee``) - Variablen, wir unterscheiden: - Umgebungsvariablen (besitzen die export-Eigenschaft, werden automatisch vererbt. z.B. ``$HOME``), aufzulisten mit ``env`` - Shell-Variablen (werden nicht vererbt, können aber exportiert werden, z.B. ``myname="Hans im Glück"``), aufzulisten mit ``set`` - Jobverwaltung (ans Kommando das Ampersand-Zeichen ``&`` anhängen, außerdem ``bg``, ``fg``, ``jobs``) - Kommandozeilen ohne Erfolgsprüfung mit Semikolon nacheinander ausführen (z.B. ``date ; df -h /``) - Aliase, z.B. ``alias rm='rm -i'`` Die stdin-Umleitung mit ``<`` kann vom Prinzip her ebenfalls anhängend arbeiten, gern in Setup-Skripten eingesetzt. Die zwei ``< /tmp/new-hosts <<\ABC > 127.0.0.1 localhost > 192.168.2.252 windows11 > ABC tux@deb13:~$ tux@deb13:~$ cat /tmp/new-hosts 127.0.0.1 localhost 192.168.2.252 windows11 tux@deb13:~$ Siehe zu grundlegender Shell-Arbeitsweise auch: - https://pemmann.de/doc-html/\_build/html/grdl/linux.html#kommandozeile - https://pemmann.de/doc/LPI-101-Handout/lpi-101-vorbereitung.html#_topic_103_4_use_streams_pipes_and_redirects - https://pemmann.de/doc/LPI-102-Handout/lpi-102-vorbereitung.html#_shells_skripte_und_datenverwaltung Maskierung von Sonderzeichen ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Das auch als Quoting bezeichnete Schützen von der Interpretation von Metazeichen: 1. Zeichenkette deklarieren, wobei mehrere Ersetzungen möglich sind: in Double-Tickmarks einschließen: ``mkdir "Eigene Dateien für $LOGNAME"`` (``ls -l`` zeigt u.a. ``Eigene Dateien für root``) 2. Zeichenkette deklarieren, wobei **KEINE** Ersetzungen möglich sein sollen: in Simple-Tickmarks einschließen (dt.: Hochkomma, Apostroph): ``mkdir 'Eigene Dateien für $LOGNAME'`` (``ls -l`` zeigt u.a. ``Eigene Dateien für $LOGNAME``) 3. Nur das eine, auf einen Backslash folgende Zeichen schützen, es dürfen ebenfalls **KEINE** Ersetzungen stattfinden: ``echo "Mein Heimatverzeichnis steht in der Variablen \$HOME und hat den Inhalt $HOME"`` Zeichenkettenschutz kann verschachtelt werden, außen herum Double-Tickmarks verwenden, innerhalb müssen dann aber Simple-Tickmarks verwendet werden (oder umgekehrt). Die Shell muss die Strings erkennen können. Substitution, Expansion ~~~~~~~~~~~~~~~~~~~~~~~ Neben komplexen neuen Möglichkeiten wie der arithmetischen Expansion gibt es drei grundlegende Verfahren: - Dateinamensexpansion (Dateijoker, Wildcard) - Variablenersetzung - Kommandozeilensubstitution Diese drei Verfahren werden wir nun in den folenden Abschnitten besprechen. Dateinamensexpansion (Dateijoker, Wildcard) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - Stern (``*``): beliebig lange Zeichenkette beliebiger Zeichen, auch kein Zeichen - Fragezeichen (``?``): nur ein beliebiges Zeichen, aber **eins** muss sein! - Eckige Klammern für Zeichenklassen, ist ein Platzhalter für nur **ein einzelnes** Zeichen (z.B. alle Buchstaben, groß oder klein geschrieben: ``[a-zA-Z]``) Beispiele: - Alle Dateien unterhalb /etc auflisten, die mit “ho” beginnen: ``ls /etc/ho*`` (trifft hier die /etc/hostname und die /etc/hosts) - Alle Dateien unterhalb /etc auflisten, die mit “passwd” beginnen, worauf ein beliebiges Zeichen folgen muss: ``ls /etc/passwd?`` (Achtung: nicht löschen, ist ein Datenbank-File!) - Alle Dateien unterhalb /etc auflisten, die typische Endungen für Konfigurationsdateien aufweisen: ``ls /etc/*[nf][fg]`` Variablenersetzung ^^^^^^^^^^^^^^^^^^ - Für den Login werden Umgebungsvariablen benötigt, siehe Kommando ``env``, z.B. ``echo $HOME`` oder ``echo $PATH`` - Eigene Variablen erzeugen, die anderes als die Umgebungsvariablen die Export-Eigenschaft aufweisen (sind in Sub-Shells nicht vorhanden!), siehe: :: #!/bin/bash myname="Axel Pemmann in $HOME" echo $myname declare -p myname # kein "x" zu sehen, d.h. die Export-Eigenschaft fehlt export myname declare -p myname # zeigt jetzt "-x" # Beim Erstellen einer Variable den Typ "Integer" mitgeben: declare -i zahl=4711 # echo $zahl # es kommt 4711 zum Vorschein # zahl="Hallo" # Fläschlicherweise ein Update des Inhaltes mit einer Zeichenkette versuchen # echo $zahl # es kommt 0 zum Vorschein (die Zeichenkette wurde nicht akzeptiert, als Inhalt wurde eine Null gesetzt) In der DOS-Shell setzt man übrigens die Variablen in %-Zeichen, so kann der eigene Benutzername auf diese Weise ausgegeben werden: ``echo %USERNAME%`` Kommandozeilensubstitution ^^^^^^^^^^^^^^^^^^^^^^^^^^ Es gibt zwei Varianten, um anzugeben, was zuerst ausgeführt werden soll (und dann der Rest der Zeile ringsherum): - Althergebracht, nicht verschachtelbar, mit Back-Tickmarks: :: echo `date +%F` # Wir erhalten "2025-11-21" - Neue Methode, verschachtelbar: :: echo $(date +%F) # Wir erhalten wiederum "2025-11-21" # Ein Backup von /etc mit Zeitstempel im Dateinamen erzeugen: tar czf etc-archiv-$(date +%F_%H-%M).tar.gz /etc Funktionen ~~~~~~~~~~ Eine Funktion kann als eine Art Unterprogramm betrachtet werden, bietet allerdings nicht so viele Möglichkeiten, wie die von Hochsprachen. Hier ein paar Merkmale: - Sie gestatten die Übergabe von Parametern (Shell-Aliase können das nicht!) - Man kann den Return-Code (Fehlercode) auswerten - Es lässt sich damit doppelter Code vermeiden, Skripte werden damit lesbarer - Sie können beim Login von Usern hilfreich sein (in rc-Skripte integrierbar) :: tux@deb12:~ $ function hilfe(){ > echo Benutzung: hilfe > echo "Können wir helfen?" > echo "Heute ist $(date +%A)." > } tux@deb12:~ $ tux@deb12:~ $ declare -f hilfe hilfe () { echo Benutzung: hilfe; echo "Können wir helfen?"; echo "Heute ist $(date +%A)." } tux@deb12:~ $ tux@deb12:~ $ # Bei Bedarf rufen wir sie wie ein Kommando auf: tux@deb12:~ $ hilfe Benutzung: hilfe Können wir helfen? Heute ist Freitag. tux@deb12:~ $ Nun testen wir aber mal die Parameterübergabe einer Funktion, ``$1`` ist der erste Übergabeparameter, den wir ihr mitgeben. :: tux@deb12:~$ ll -bash: ll: Kommando nicht gefunden. tux@deb12:~$ # >>> schön, sowas gibt es hier NOCH nicht. tux@deb12:~$ tux@deb12:~$ function ll () { ls -ltrc $1 | tail -5; } tux@deb12:~$ tux@deb12:~$ declare -f ll ll () { ls --color=auto -ltrc $1 | tail -5 } tux@deb12:~$ Es gibt insgesamt drei Syntaxvarianten, einmal kann man das Schlüsselwort “function” weglassen und zum anderen die runden Klammern: :: tux@deb13:~/bin$ function hallo1(){ echo Hallo Nr 1...;} tux@deb13:~/bin$ hallo2(){ echo Hallo Nr 2...;} tux@deb13:~/bin$ function hallo3 { echo Hallo Nr 3...;} Hinweise für solche Einzeiler: - Nach der öffnenden geschweiften Klammer muss ein Leerzeichen stehen - Vor der schließenden geschweiften Klammer muss ein Semikolon stehen - In der dritten Variante ohne runde Klammern muss ein Leerzeichen nach dem Funktionsnamen stehen Parameterübergabe ----------------- Übergabe-/Positionsparameter ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Einem Skript können Parameter übergeben werden, die man auch Positionsparameter nennt. Sie werden beim Aufruf des Skriptes mittels Variablen intern eingelesen. An erster Position steht $1, an zweiter $2, an dritter $3 und so weiter. Der Name des Skriptes steht in der Variable $0. :: tux@deb12:~ $ nano kopiere tux@deb12:~ $ tux@deb12:~ $ cat kopiere #!/bin/bash cp -a $1 $3 tux@deb12:~ $ tux@deb12:~ $ chmod +x kopiere tux@deb12:~ $ tux@deb12:~ $ echo Hallo Hallo tux@deb12:~ $ echo INHALT > content.txt tux@deb12:~ $ tux@deb12:~ $ # Der Hinweis zur Benutzung kommt, weil der dritte Übergabeparameter fehlt, der zweite aber nicht ausgewertet wird (sein Inhalt geht verloren): tux@deb12:~ $ kopiere content.txt /tmp Command line: cp -a content.txt BusyBox v1.31.1 (2023-03-09 18:36:28 UTC) multi-call binary. Usage: cp [OPTIONS] SOURCE... DEST Copy SOURCE(s) to DEST -a Same as -dpR -R,-r Recurse -d,-P Preserve symlinks (default if -R) -L Follow all symlinks -H Follow symlinks on command line -p Preserve file attributes if possible -f Overwrite -i Prompt before overwrite -l,-s Create (sym)links -T Treat DEST as a normal file -u Copy only newer files tux@deb12:~ $ tux@deb12:~ $ tux@deb12:~ $ # Jetzt sind es 3 Parameter: tux@deb12:~ $ kopiere content.txt nach /tmp tux@deb12:~ $ tux@deb12:~ $ ls -l /tmp/content.txt -rw-r--r-- 1 tux users 7 May 23 12:05 /tmp/content.txt tux@deb12:~ $ Per Kommando read ----------------- :: axl@bookw:bin$ whatis read read (2) - read from a file descriptor axl@bookw:bin$ axl@bookw:bin$ help read read: read [-ers] [-a Feld] [-d Begrenzer] [-i Text] [-n Zeichenanzahl] [-N Zeichenanzahl] [-p Prompt] [-t Zeitlimit] [-u fd] [Name ...] Read a line from the standard input and split it into fields. Reads a single line from the standard input, or from file descriptor FD if the -u option is supplied. The line is split into fields as with word splitting, and the first word is assigned to the first NAME, the second word to the second NAME, and so on, with any leftover words assigned to the last NAME. Only the characters found in $IFS are recognized as word delimiters. By default, the backslash character escapes delimiter characters and newline. If no NAMEs are supplied, the line read is stored in the REPLY variable. Options: -a array assign the words read to sequential indices of the array variable ARRAY, starting at zero -d delim continue until the first character of DELIM is read, rather than newline -e use Readline to obtain the line -i text use TEXT as the initial text for Readline -n nchars return after reading NCHARS characters rather than waiting for a newline, but honor a delimiter if fewer than NCHARS characters are read before the delimiter -N nchars return only after reading exactly NCHARS characters, unless EOF is encountered or read times out, ignoring any delimiter -p prompt output the string PROMPT without a trailing newline before attempting to read -r do not allow backslashes to escape any characters -s do not echo input coming from a terminal -t timeout time out and return failure if a complete line of input is not read within TIMEOUT seconds. The value of the TMOUT variable is the default timeout. TIMEOUT may be a fractional number. If TIMEOUT is 0, read returns immediately, without trying to read any data, returning success only if input is available on the specified file descriptor. The exit status is greater than 128 if the timeout is exceeded -u fd read from file descriptor FD instead of the standard input Exit Status: The return code is zero, unless end-of-file is encountered, read times out (in which case it's greater than 128), a variable assignment error occurs, or an invalid file descriptor is supplied as the argument to -u. axl@bookw:bin$ axl@bookw:bin$ axl@bookw:bin$ read -p "Passwort eingeben: " -s Passwort eingeben: axl@bookw:bin$ axl@bookw:bin$ axl@bookw:bin$ echo $REPLY 1234567 axl@bookw:bin$ axl@bookw:bin$ axl@bookw:bin$ read a b c Wert1 Wert2 Wert3 Wert4 Wert5 axl@bookw:bin$ axl@bookw:bin$ echo $a Wert1 axl@bookw:bin$ axl@bookw:bin$ echo $b Wert2 axl@bookw:bin$ axl@bookw:bin$ echo $c Wert3 Wert4 Wert5 axl@bookw:bin$ axl@bookw:bin$ # 'read' vergisst nichts: die letzte Variable beinhaltet den Rest! Kontrollstrukturen ------------------ Richtig spannend wird es nun mit Hilfe einer Ablaufsteuerung, die auf Ereignisse reagieren kann. Bedingte Ausführung ~~~~~~~~~~~~~~~~~~~ Die erste Art der Ablaufsteuerung beschränkt sich auf einfache Erfolgsprüfung. - Nur bei Erfolg: mit ``&&`` werden Kommandozeilen verkettet, wobei nur weitergearbeitet wird, wenn die vorherige Zeile keinen Fehler brachte. - Nur im Fehlerfall: mit ``||`` werden Kommandozeilen verkettet, wobei nur weitergearbeitet wird, wenn die vorherige Zeile einen Fehler verursachte. - Mit ``||`` werden Kommandozeilen verkettet, wobei nur weitergearbeitet wird, wenn die vorherige Zeile Erfolg hat. :: tux@deb12:~/bin$ test -u /usr/bin/passwd && echo "Datei hat das SUID-Bit." || echo "Datei hat das SUID-BIT nicht." Datei hat das SUID-Bit. tux@deb12:~/bin$ tux@deb12:~/bin$ test -u /usr/bin/date && echo "Datei hat das SUID-Bit." || echo "Datei hat das SUID-BIT nicht." Datei hat das SUID-BIT nicht. tux@deb12:~/bin$ Konstrukte zur Ablaufsteuerung ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Jetzt wird es komplexer, mit den folgenden Konstrukten lassen sich umfangreiche Programme schreiben. Es gibt Verzweigungen: - if-then-else-fi - case in …;; esac und Schleifen: - for-in-do-done (Zählschleife, über eine Menge von Elementen gleiten…) - while--do-down (kleine Services schreiben, Überwachungsaufgaben) - select …; done (eine Sonderform, die zur Menübildung eingesetzt werden kann) Verzweigungen: ^^^^^^^^^^^^^^ 1. Die **if-Verzweigung** ist die universellste. Hier ein einfaches Beispiel, wobei zur Entscheidungfindung das weiter unten beschriebene Kommando ``test`` verwendet wird; zu besseren Lesbarkeit wird gern die Form mittels eckiger Klammern benutzt, wobei nach der öffnenden und vor der schließenden eckigen Klammer ein Leerzeichen stehen muss: :: if [ -f /etc/hosts ] then echo "Ja, es ist eine Datei, die vorhanden ist." else echo "Sorry, Objekt nicht gefunden oder keine Datei!" ls -l /etc/hosts fi Das Kommando ’test vergleicht im nächsten Beispiel, ob die Zahl 1 kleiner als die Zahl 2 (*lt* = less then): :: if [ 6 -lt 7 ] then echo "Passt." else echo "Passt nicht!" fi 2. Die **case-Verzweigung** ist speziell für den Mustervergleich gedacht. Im folgenden Beispiel eines Grundgerüstes für eine iptables-Firewall werden zwei Funktionen definiert, die in dann im Kommandoblock der Verzweigung aufgerufen werden. Damit das Skript nicht mit root-Rechten getestet werden muss, wurde dem Kommando bei der Variablen-Deklaration in Zeile 2 ein ``echo`` vorangestellt: :: #!/bin/bash fwcmd="echo /sbin/iptables" close(){ $fwcmd -P INPUT DROP $fwcmd -P FORWARD DROP $fwcmd -P OUTPUT DROP $fwcmd -A INPUT -i lo -j ACCEPT $fwcmd -A OUTPUT -o lo -j ACCEPT } open(){ $fwcmd -P INPUT ACCEPT $fwcmd -P FORWARD ACCEPT $fwcmd -P OUTPUT ACCEPT $fwcmd -F $fwcmd -Z } case "$1" in close|c) close ;; open|o) open ;; status|s) $fwcmd -vnL ;; *) echo "AUFRUF: $0 {close|open|status}" ;; esac Schleifen: ^^^^^^^^^^ 1. Die **for-Schleife**, auch Zählschleife genannt, untersucht im folgenden Beispiel alle Dateien unter /etc, die mit “host” beginnen, hinsichtlich ihres Dateityps: :: for i in /etc/host* do file $i sleep 2 done 2. Die **while-Schleife** des folgenden Beispiels läuft erst los, wenn ein Prozess gefunden wird. Nach dem Eingeben von ``sleep 3m`` kann sie getestet werden. Die Bedingung, dass ein sleep-Prozess gefunden wird, realisiert die Zeile ``pgrep $prozess > /dev/null``, die in eine Kommandozeilensubstitution eingebettet ist. Die Entscheidung trifft die Shell ohne weitere Hilfswerkzeuge allein aufgrund des Exit-Codes von ``pgrep``: :: prozess="sleep" while $(pgrep $prozess > /dev/null) do echo Prozess gefunden... sleep 3 done Hilfswerkzeuge -------------- Neben den üblichen Textfiltern wie cat, head, tail, grep, sed, awk, sort und anderen Tools wie netcat, nmap, lsof, ss, tcpdump usw. gibt es speziell für die Programmierung erforderliche Werkzeuge. Davon sollen ein paar vorgestellt werden. Kommando test ~~~~~~~~~~~~~ Fürs Testen auf verschiedene Dateitypen und -eigenschaften sowie für den Vergleich von Zeichenketten und Zahlen ist ``test`` ein wichtiges Werkzeug. Es wird gern zur Definition von Bedingungen in Verzweigungen und Schleifen eingesetzt. Anstelle von ``test`` kann auch die Klammerschreibweise mit ``[ ... ]`` verwendet werden, dabei müssen aber nach der öffnenden eckigen Klammer und vor der schließenden eckigen Klammer Leerzeichen gesetzt werden. LPI-relevante Optonen für Dateitests: :: -e = existent -x = existent und eXecutable Hier ein paar Beispiele: :: tux@deb12:~/bin$ test -e /etc/vfstab ; echo $? 1 tux@deb12:~/bin$ # >>> Fehler tux@deb12:~/bin$ tux@deb12:~/bin$ test -e /etc/fstab ; echo $? 0 tux@deb12:~/bin$ # >>> Erfolg tux@deb12:~/bin$ tux@deb12:~/bin$ [ -x /etc/fstab ]; echo $? 1 tux@deb12:~/bin$ # >>> Fehler, d.h. die Datei mag zwar existieren, sie ist aber nicht eXecutable! tux@deb12:~/bin$ tux@deb12:~/bin$ tux@deb12:~/bin$ [ -x /usr/bin/date ]; echo $? 0 tux@deb12:~/bin$ # >>> Erfolg, d.h. die Datei ist exististent UND auch eXecutable tux@deb12:~/bin$ tux@deb12:~/bin$ tux@deb12:~/bin$ tux@deb12:~/bin$ tux@deb12:~/bin$ # Ein bissl wie if-then-else: mit "&&" wird nur weitergearbeitet, wenn die vorherige Zeile tux@deb12:~/bin$ # erfolgreich war: tux@deb12:~/bin$ test -g /usr/bin/wall && echo 'Ja, das "file" hat das SGID-Bit.' tux@deb12:~/bin$ tux@deb12:~/bin$ ls -l /usr/bin/wall -rwxr-xr-x 1 root tty 39224 23. Mär 2023 /usr/bin/wall tux@deb12:~/bin$ tux@deb12:~/bin$ ls -l /usr/bin/write -rwxr-sr-x 1 root tty 22848 23. Mär 2023 /usr/bin/write tux@deb12:~/bin$ tux@deb12:~/bin$ test -g /usr/bin/wall && echo 'Ja, das "file" hat das SGID-Bit.' tux@deb12:~/bin$ echo $? 1 tux@deb12:~/bin$ # >>> "1" bedeutet "Fehler" tux@deb12:~/bin$ tux@deb12:~/bin$ tux@deb12:~/bin$ # Genau so mit if-then-else machbar: mit "||" wird nur weitergearbeitet, wenn die vorherige Zeile einen Fehler geliefert hat: tux@deb12:~/bin$ test -g /usr/bin/wall || echo 'Ja, das "file" hat das SGID-Bit.' Ja, das "file" hat das SGID-Bit. tux@deb12:~/bin$ echo $? 0 tux@deb12:~/bin$ Kommando seq ~~~~~~~~~~~~ Das Tool erleichtert die Erzeugung von Zahlenfolgen. :: axl@bookw:bin$ seq 3 1 2 3 axl@bookw:bin$ axl@bookw:bin$ seq 2 6 2 3 4 5 6 axl@bookw:bin$ axl@bookw:bin$ seq 2 2 6 2 4 6 axl@bookw:bin$ axl@bookw:bin$ axl@bookw:bin$ seq 1 5 20 1 6 11 16 axl@bookw:bin$ # >>> LPI-relevante Frage: es wird addiert! axl@bookw:bin$ # 1 + 5 = 6 axl@bookw:bin$ # 6 + 5 = 11 axl@bookw:bin$ # 11 + 5 = 16 axl@bookw:bin$ axl@bookw:bin$ seq 5 -0.5 2 5,0 4,5 4,0 3,5 3,0 2,5 2,0 axl@bookw:bin$ Kommando shift ~~~~~~~~~~~~~~ Damit kann man die Liste der Postitionsparameter verschieben, sie vom Prinzip her von links nach rechts Stück für Stück abarbeiten. :: axl@bookw:bin$ cat > shifttest shift echo $1 axl@bookw:bin$ axl@bookw:bin$ # >>> Strg - d zum Speichern axl@bookw:bin$ axl@bookw:bin$ bash shifttest axl@bookw:bin$ bash shifttest Par1 axl@bookw:bin$ bash shifttest Par1 Par2 Par2 axl@bookw:bin$ bash shifttest Par1 Par2 Par3 Par2 axl@bookw:bin$ bash shifttest Par1 Par2 Par3 Par4 Par2 axl@bookw:bin$ Jetzt wollen wir die Parameterliste gleich um drei Werte verschieden: :: axl@bookw:bin$ vi shifttest axl@bookw:bin$ cat shifttest shift 3 echo $1 axl@bookw:bin$ axl@bookw:bin$ bash shifttest Par1 Par2 Par3 Par4 Par5 Par4 axl@bookw:bin$ axl@bookw:bin$ # >>> 'shift 3' überspringt die ersten drei Parameter axl@bookw:bin$ axl@bookw:bin$ axl@bookw:bin$ axl@bookw:bin$ # In der Praxis oft in der Übergabe von zusammengesetzten Optionen benutzt: axl@bookw:bin$ whatis getopt getopt (1) - Befehlsoptionen auswerten (erweitert) getopt (3) - Parse command-line options axl@bookw:bin$ Siehe dazu https://davetang.org/muse/2023/01/31/bash-script-that-accepts-short-long-and-positional-arguments/ Kommando exit ~~~~~~~~~~~~~ Man kann dem Kommando einen eigenen Wert mit geben (z.B. ``exit 99``), so dass man in der aufrufenden Shell den Exit-Code des Skriptes auswerten kann. Kommando nohup ~~~~~~~~~~~~~~ Das Kommando löst Prozesse vom aufrufenden Elternprozess ab. Möchte der Admin z.B. ein länger andauerndes Update durchführen, kann er ``nohup apt-get -y upgrade &`` ausführen und sich dann gefahrlos ausloggen, ohne dass sich ``apt-get`` beendet. Nützlich ist das Kommando auch, wenn ein zweites Skript unabhängig vom ersten, aus dem es gestartet wurde, weiterlaufen soll. Arithmetik ---------- Increments und Decrements in Schleifen ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Mit folgenden Beispielen kann eine Zahlenreihe hochgezählt werden (Increment), soll abwärts gezählt werden, ersetzt man die Plus-Zeichen durch Minus-Zeichen (Decrement). Die Ausdrücke mit den doppelten runden Klammern sind eine andere Schreibweise für die Verwendung des internen bash-Befehls ‘let’ Siehe dazu ‘man bash’, Suche nach ‘((’ durchführen. :: x=$((x+1)) ; echo $x let x++ ; echo $x ((x++)) ; echo $x Aus performancetechnischen Gründen sollte das externe Kommando ‘expr’ nicht mehr eingesetzt werden: :: x=$(expr $x + 1) ; echo $x x=`expr $x + 1` ; echo $x Beispiel Countdown-Zähler: :: #!/bin/bash x=$1 while [ $x -ge 1 ] do ((x--)) echo $x done Kommando factor ~~~~~~~~~~~~~~~ Für Primzahlen gibt es das Kommando ``factor``: :: tux@deb12:~$ whatis factor factor (1) - Zahlen in Faktoren zerlegen tux@deb12:~$ tux@deb12:~$ factor 8 8: 2 2 2 tux@deb12:~$ factor 7 7: 7 tux@deb12:~$ tux@deb12:~$ Empfehlenswerte Seite zur Vertiefung der mathematischen Möglichkeiten der Bash: https://phoenixnap.com/kb/bash-math