Einstieg in die Bash-Shell - Teil 3
Der ASCII-Code (American Standard Code for Information Interchange) ist ein (alter) Standard um Zeichen zu kodieren. ASCII beschreibt einen Sieben-BitCode. D.h. es können $2^7 = 128$ Zeichen mit ASCII-Code dargestellt werden. Steuerzeichen (wie z.B.: Zeilenvorschub) sind im ASCII-Code ebenfalls erhalten.
Beispiel: Buchstabe A : 1000001
Durch Hinzunahme des 8-ten Bit (des Bytes) können nochmal 128 Zeichen dargestellt werden. Diese können fur länderspezifische Zeichen genutzt werden, wie z.B. ISO 8859-1 (Latin-1). In unterschiedlichen Ländern sind unterschiedliche Standards üblich. Dies führt zu Inkompatibilitäten zwischen den verschiedenen Ländercodierungen bzw. Computersystemen (z.B. bei deutschen Umlauten).
Unicode ist ein internationaler Standard, mit dem Ziel spezielle Zeichen aller Sprachen (Schriftkulturen) zu umfassen. Unicode weist jedem Zeichen eine eindeutige Nummer (Unicode-Wert) zu. Durch standardisierte Kodierungen der Unicode-Werte, wie UTF-8, sollen die Inkompatibilitäten (falsche Darstelllungen) vermieden werden.
Für den Unterschied zwischen Unicode und Implementierungen wie UTF-8, siehe z.B. https://stackoverflow.com/questions/643694/what-is-the-difference-between-utf-8-and-unicode
Text-Daten können als Dateien (Files) im ASCII-Code oder UTF-8 auf einem Rechner gespeichert werden. Diese Dateien können ohne größeren Schwierigkeiten zwischen verschiedenen Computersystemen ausgetauscht werden und dort angezeigt und weiterverarbeitet werden. Dabei ist darauf zu achten, dass die Kodierung richtig erkannt bzw. eingestellt wird. Desweiteren können jedoch Probleme bei der Handhabung des Zeilenendes auftreten. Windows verwendet zur Markierung des Zeilenendes die Kombination Wagenrücklauf-Zeilenvorschub. Unix und Linux nutzen nur den Zeilenvorschub.
Beispiele für Text-Dateien: CSV, Emails, HTML-Webseiten.
file
command¶Das file
Kommando führt mehrere Tests durch, um den Typ einer Datei zu klassifizieren. Dabei wird auch
versucht die Kodierung von Textdateien genauer zu bestimmen. Mehr siehe man file
.
file countries.txt
echo
file ~/bin/backup.sh
file ../bash_3.ipynb
echo
file /home/chris/tmp/side_by_side_without_boarder_nearest.png
MIME ist ein Standard zur Deklaration von Dateiinhalten (bzw. Dateitypen), der ürsprünglich für Emails entwickelt wurde, mehr hierzu z.B. in https://wiki.ubuntuusers.de/MIME-Typ/
file --mime ../bash_3.ipynb
echo
file -i /home/chris/tmp/side_by_side_without_boarder_nearest.png
Wählen Sie einen Kommandozeilen-Text-Editor und arbeiten Sie sich in diesen ein. Recherchieren Sie hierzu ein geeignetes Tutorial. Gängige Text-Editoren für die Kommandozeile sind:
emacs
und drücken Sie STRG&h und danach t.-nw
(no window), startet emacs im Terminal(-Fenster). Shell-Skripte sind Programmcode gespeichert in Text-Dateien für die Shell-Umgebung. Shell-Skripte werden interpretiert, nicht kompiliert. D.h. während der Ausführung liest der Interpreter das Skript Zeile für Zeile und führt die Komandos aus. Ein Kompiler dagegen übersetzt ein Programm in ausführbaren Maschinencode (aber auch für virtuelle Maschinen wie bei Java), d.h. in eine Form, die vom Computer direkt ausgeführt werden kann.
Vorteile von Shell Skripen:
Nachteile:
Für komplexere Skripte und mehr eignen daher sich andere Skriptsprachen, wie Python, deutlich besser.
Shell-Skripe bestehen im einfachsten Fall aus einer Folge von Befehlen. Aber es sind auch Kontrollstrukturen (Schleifen, Bedingungen usw.) und Strukturierungen durch Funktionen etc. möglich.
Beachten Sie, dass es aber gute Praxis ist, den Shebang mit dem env
-Kommando zu verwenden.
Mit env
läuft ein Programm in einer modifierten Umgebung. Typischerweise liegt env
im Verzeichnis /usr/bin/
, sodass folgende Beispiele für den Shebang in der ersten Zeile eines Skriptes sinnvoll sind:
#!/usr/bin/env bash
- Bash-Skripte#!/usr/bin/env sh
- POSIX konforme Shell-Skripte#!/usr/bin/env python
- Python Skripteenv
verwendet die PATH
Umgebungsvariable (siehe unten), um den passenden Interpreter (genauer: das Interpreterprogramm) zu finden.
Beispielskript "Hallo Welt":
echo '#!/usr/bin/env bash
echo Hallo Welt!' > hw.sh
chmod u+x hw.sh
./hw.sh
Shell-Skripte haben oft die Endung .sh
. Dies ist aber optional.
Folgende spezielle Variablen gibt es in Bash bzw. Bash-Skripten
$0
Aufruf Name des Skripts (inkl. Pfad)$1
bis $9
sind Argumente des Skripts oder der Funktion (beim Aufruf übergeben).$@
sind alle Argumente als Liste (Listen/Array werden weiter unten behandelt)$#
Anzahl der Argumente$?
- Rückgabecode des vorherigen Kommandos$$
- Prozeß-ID (PID) des laufenden Skiptes (siehe unten)!!
- Vollständiges letztes Kommando inkl. der Argumente. Eine gängige Anwendung ist, falls das letze Kommando aufgrund von fehlenen root-Rechten nicht ausgeführt werden konnte: sudo !!
$_
- Letztes Argument des letzten Kommandos. In einer interaktiven Shell bekommt man dies auch über die Tastenkombination Esc gefolgt von ..$!
beinhaltet die Prozess ID, des zuletzt im Hintergrund ausgeführten Prozesses. Beispiele:
wc not-present 2>/dev/null # fehlermeldung wird unterdrückt
echo The exit code of last command is: $?
Beachten Sie, dass Fehlermeldungen von Kommandos (Programmen) mit Zahlen codiert werden. Dabei steht die Null für "Kein-Fehler":
ls >/dev/null
echo The exit code of last command is: $?
sleep 3 & # & bewirkt Ausführung im Hintergrund
echo $!
# Hier werden Prozess IDs ausgegeben - siehe unten
# Hinweis: Wir müssen die $-Zeichen im here-Document escapen
# damit die bash keine variable substitution vornimmt!
cat > example_script.sh << EOF
#!/usr/bin/env bash
echo Der Name diese Skripts mit Pfad beim Aufruf: \${0}
echo Mit folgendem ersten Argument: \${1}
echo Das zweite Argument ist: \${2}
echo Die Anzahl aller Argumente ist \$#
EOF
chmod u+x example_script.sh # ausführbar machen!
./example_script.sh foo bar
Hinweis: Prozesse werden später ausführlicher behandelt.
# So erhält man die PID des Bash-Prozesses der Shell
echo -e "shell:\n" '$='$$ 'BASHPID='$BASHPID
Beachte (aus https://unix.stackexchange.com/questions/484442/how-can-i-get-the-pid-of-a-subshell)
$$
: Expands to the process ID of the shell. In a () subshell, it expands to the process ID of the current shell, not the subshell.BASHPID
: Expands to the process ID of the current bash process. This differs from $$ under certain circumstances, such as subshells that do not require bash to be re-initialized.#subshells
echo -e $(echo "subshell\n" '$='$$ 'BASHPID='$BASHPID)
Wenn ein Shell-Skript ausgeführt wird, wird ein neuer Prozess gestartet. Die aufrufende Shell ist der Eltern-Prozess (parent process) und der Prozess des Shell-Skriptes der Kind-Prozess (child process).
# Auch der Shell-Prozess hat ein Parent
# So erhält man die PID des Parent, die PPID
echo $PPID
Lesen Sie 1.9 Vom Shellscript zum Prozess.
Umgebungsvariable (Environment Variables) sind besondere Variablen, die von Prozessen für unterschiedliche Dinge verwendet werden können (Beispiele später). Umgebungsvariablen werden bei der Prozess-Erzeugung an die Kinderprozesse vererbt. Die Kinderprozesse erhalten eine Kopie der Umgebung des Elternprozesses.
Das Kommando printenv
dient dazu die gesetzen Umgebungsvariablen anzuzeigen.
printenv | grep -i python
Mit dem Kommando printenv
können Sie direkt die Werte von Umgebungsvariablen erhalten, z.B.
printenv VIRTUAL_ENV # zeigt nur env-var an!
#die Werte von env-var sind natürlich auch per parameter substitution erhältlich:
#echo $VIRTUAL_ENV
Man kann die Kommandozeile als Umgebung sehen, die shell-Code ausführt. Kommandos sind
dann shell build-ins oder weitere Programme, die ausgeführt werden können, d.h. in der Regel weitere Prozesse ergeben.
Diese Programme, d.h. die ausführbaren Dateien, muss die shell finden. Dazu dient die Umgebungsvariable PATH
.
So kann man sich anzeigen lassen, wo die shell nach den ausführbaren Programmen sucht:
printenv PATH
In der Variablen SHELL
ist die Art des Shell-Interpreters (der laufenden Shell) gespeichert:
printenv SHELL
export
¶Shell Skripte laufen in einem eigenen Prozess (erkennbar an der anderen PID).
Wenn in der aufrufenden Shell eine Variable gesetzt wird, dann ist diese
im Shell Skript nicht verfügbar (anderes als bei Subshells via $(...)
oder Pipes). Das Skript muss ja kein Bash-Skript sein und Bash-Skripte werden hier nicht besonderes behandelt.
Variablen, die mit export
gesetzt werden, sind dagegen (in den Child-Prozessen und somit auch) im Skript-Prozess gesetzt.
echo PID der aufrufenden Shell: $$
my_normal_var=10
export my_env_var=11
echo '#!/bin/bash
echo Die PID dieses Skriptes ist $$
echo Der Parent-Prozess des Skriptes hat die PID $PPID # $(ps -o ppid= $$)
echo my_normal_var ist nicht sichtbar: $my_normal_var
echo my_env_var dagegen schon: $my_env_var
' > example_script.sh
chmod u+x example_script.sh
./example_script.sh
Mit export
setzt man also Variablen, die in allen Child-Prozessen des
Parent-Prozess (hier der Shell) verfügbar sind.
Diese Variablen sind somit in der Umgebung gesetzt. Dies sieht man auch mittels:
env | grep my_env_var
Passen Sie den Prompt (Umgebungsvaribale PS1
) nach Ihrem Geschmack an. Wie dies geht, finden Sie hier:
https://wiki.ubuntuusers.de/Bash/Prompt/
Hilfreich sind dabei auch
Konfigurationsdateien erlauben das Verhalten der Shell zu konfigurieren. Die wichtigsten sind
.bashrc
und .bash_profile
; siehe z.B. https://wiki.ubuntuusers.de/Bash/bashrc/
.bash_profile
wird bei login Shells (beim Start der Shell) ausgeführt.
.bashrc
wird bei interaktiven non-login Shells ausgeführt.
Hinweis: Die Dateien mit einem Punkt im Namen als erstes Zeichen sind sogenannte versteckte Dateien.
Diese werden mit standardmäßig nicht angezeigt, z.B. ist bei ls
die Option -a
nötig, um diese auch
aufzulisten.
Den Standardeditor können Sie mit Hilfe der Umgebungsvariablen EDITOR
setzen.
Am besten setzt man dies in der Konfigurationsdatei .bashrc
.
Setzen Sie Ihren Lieblingseditor in der .bashrc
. Wie lautet die entsprechende Befehlszeile?
beim Editor nano, z.B.:
export EDITOR=/usr/bin/nano
Setzen Sie die Sprache der Bash (z.B. zur besseren Internetrecherche bei Fehlermeldungen etc.) auf amerikanisches Englisch. In der Datei .bashrc
müssen Sie hierzu die Variable LANG
auf en_US.UTF-8
setzen (export
nicht vergessen!).
Mittels des Alias-Mechanismus können Sie sich Abkürzungen von Befehlen (inkl. Argumenten) erstellen.
Mit dem Befehl alias
(ohne Argumente) bekommen Sie die definierten Aliase angezeigt:
alias
Ein (etwas sinnloses) Beispiel für die Definition eines Aliases:
# myls ist der Alias für "ls -lrth *.txt"
alias myls_txt='ls -lrth *.txt'
# Jetzt kann der Alias verwendet werden:
myls_txt
Typischerweise werden die Aliase in der regulären Datei ~/.bash_aliases
definiert, sodass
die Aliase dann beim Öffnen der Kommandozeile verfügbar sind.
Dazu wird typischerweise diese Aliasdatei automatisch von einem bash-Konfigurationsskript (siehe unten)
ausgeführt mittels source ~/.bash_aliases
; zum Befehl (shell buildin) source
(Abkürzung .
) siehe help source
oder help .
.
mehr zum Alias-Mechanismus siehe https://de.wikibooks.org/wiki/Linux-Praxisbuch/_Bourne_Again_Shell#Der_Alias-Mechanismus
Im Folgenden werden kurz Funktionen, (assoziative) Arrays und Kontrollstrukturen für Shell-Skripte vorgestellt. Damit ist es möglich richtige Programme in bash zu entwickeln.
Die Struktur einer Funktion ist folgende:
funktionsname () {
kommando_1
kommando_2
...
}
Für die Argumente gilt das gleiche wie bei den Skripten, d.h. es gibt $#
, $1
, etc.
zum Beispiel (Funktionen kann man auch direkt auf der Kommandozeile definieren):
# Beachte: Die Argumente sind nicht zwischen den Klammern ( ... ) explizit gelistet.
cd_ls () {
cd $1 # das erste Argument des Funktion ist (wie beim Skript) $1 usw.
echo "Das Verzeichnis $(pwd) hat folgenden Inhalt:"
ls -l
}
cd_ls ../work
# Bei Funktionen kann man auch explizit das Schlüsselwort "function" angeben:
function silly_example () {
echo "das ausführende 'Programm' bzw. Skript heißt $0"
echo "Zahl der Argumente $#"
}
silly_example bla blub
type function
my_array=("first" "second" "third")
# oder bei dem Inhalte aus einer Variablen mit Leerzeichen im Inhalt
#my_array=($var)
# die Zählung beginnt bei 0
echo drittes Element: "${my_array[2]}"
echo Anzahl der Elemente "${#my_array[@]}"
# Man kann auch rückwärts indizieren:
echo drittes Element: "${my_array[-3]}"
# Alle Elemente ausgeben:
echo ${my_array[@]}
Hinzufügen eines neuen Elementes erfolgt folgendermaßen. Beachten Sie dabei die runden Klammern:
my_array+=("fourth")
echo ${my_array[@]}
Alternativ kann ein leeres Indexed Array per declare -a my_array
deklariert werden.
Außerdem wird ein Index-Array automatisch erzeugt, mittels name[subscript]=value
. Hier wird subscript
als ein arithmetischer Ausdruck interpretiert, d.h. er muss eine Zahl sein oder zu ener Zahl evaluiert werden können.
Assoziative Arrays werden mittels declare -A name
erzeugt.
Mittels assoziative Arrays kann eine Schlüssel (key) auf einen Wert (value) abgebildet werden:
declare -A capital_cities
capital_cities["Frankreich"]="Paris"
capital_cities["Spanien"]="Madrid"
capital_cities["Deutschland"]="Berlin"
echo ${capital_cities["Spanien"]}
echo
# all keys - note the order is undefined!
echo ${!capital_cities[@]}
echo
# can be used in a loop
for k in ${!capital_cities[@]}; do
echo "${capital_cities[$k]} ist die Hauptstadt von ${k}."
done
Mehr zu Arrays siehe die entsprechende Seite im Bash Gnu Manual.
Schreiben Sie eine shell-Funktion denen Sie zwei Argumente übergeben:
cd
s als Kommando), soll
nach Beenden der Funktion das ursprüngliche Arbeitsverzeichnis wieder gelten. Wie kann das einfach realisiert werden? Beispiel:
comment_and_execute "Das wird bei ls -l ausgegeben:" "ls -l"
Das wird bei ls -l ausgegeben:
total 572
drwxr-.....
.....
comment_and_execute 'Das wird bei ls -l ausgegeben:' 'ls -l'
comment_and_execute 'Das wird bei cd ~ und ls ausgegeben' 'cd ~ ; ls'
Schreiben Sie eine shell-Funktion, der Sie zwei Argumente übergeben:
Beispiel:
first="Hallo Welt"
second="Hello World"
third="Hola Mundo"
my_array=(first second third)
show_variable myarray 1
Hello World.
bzw.
show_variable my_array 0
Hallo Welt
Hinweis: Indirekte Expansion von Variablen.
first="Hallo Welt"
second="Hello World"
third="Hola Mundo"
my_array=(first second third)
show_variable my_array 2
show_variable my_array 1
show_variable my_array 0
Mit Tests kann man überprüfen, ob Variablen gesetzt oder leer sind bzw. sind Werte- und Stringvergleiche möglich. Dateien können auch überprüft werdem, z.B. ob sie vorhanden sind bzw. welchen Typen sie haben.
Die Tests werten zu Wahr (true
) oder Falsch (false
) aus.
So können damit Programm-Abzweigungen oder bedingte Ausführungen realisiert werden.
&&
: Und-Operator (And)||
: Oder-Operator (Or)!
: NegationBeide werden bedingt ausgewertet (Kurzschlussauswertung, short-circuit evaluation):
true || echo wird nicht geschieben
false && echo wird nicht geschieben
# ist das gleiche wie
! true && echo wird nicht geschieben
true && echo wird geschieben
false || echo wird geschieben
Beispielsweise kann mit test
überprüft werden, ob Variablen gesetzt bzw. leer sind:
a='Hallo Welt'
test "$a" || echo Variable ist leer oder nicht gesetzt!
true || echo Variable ist leer oder nicht gesetzt!
Beachten Sie: -
true
. false
Typischerweise werden dafür aber eckige Klammern verwendet: [ ]
.
Die einfachen eckigen Klammern sind eine Referenz auf test
.
[ "$a" ] || echo Variable ist leer oder nicht gesetzt!
In Bash (nicht POSIX-konform) gibt es auch doppelte eckige Klammern [[
:
[[ $a ]] || echo Variable ist leer oder nicht gesetzt!
Zum Unterschied zwischen [
und [[
siehe http://mywiki.wooledge.org/BashFAQ/031
type [
type [[
rm /etc/some_file.conf || echo "I couldn't remove the file"
-z
kann überprüft werden, ob die Variable leer/nicht-gesetzt ist.
Liefert im Fall leer/nicht-gesetzt true
zurück.-n
kann überprüft werden, ob die Variable nicht-leer/gesetzt ist.
Liefert im Fall nicht-leer/gesetzt true
zurück.Für eine Liste der Vergleichsoperatoren siehe man test
oder z.B. https://tldp.org/LDP/abs/html/comparison-ops.html
a="a"
unset a
[[ -z $a ]] && echo Variable ist leer oder nicht gesetzt!
# dasgleiche wie
[[ ! $a ]] && echo Variable ist leer oder nicht gesetzt!
[[ -n $a ]] || echo Variable ist leer oder nicht gesetzt!
# dasgleiche wie
[[ $a ]] || echo Variable ist leer oder nicht gesetzt!
Zur Unterscheidung von leer vs. gesetzt können Sie folgenden Code-Snipsel verwenden, von https://serverfault.com/questions/7503/how-to-determine-if-a-bash-variable-is-empty
a=""
# unset a
if [[ -z ${a+x} ]]; then
echo Variable nicht gesetzt
else
echo "Variable gesetzt (kann auch leer sein)"
fi
b="$a"
#b="Hola Mundo"
# = ist hier ein Vergleich
[[ $a = $b ]] && echo beide Variablen sind gleich.
# es geht aber auch ==
[[ $a == $b ]] && echo beide Variablen sind gleich.
[[ $a != $b ]] && echo beide Variablen sind ungleich.
a=3
b=5
[[ $a -eq $b ]] && echo a gleich b
[[ $a -ne $b ]] && echo a ungleich b
[[ $a -gt $b ]] && echo a größer b
[[ $a -lt $b ]] && echo a kleiner b
[[ $a -ge $b ]] && echo a größer-gleich b
[[ $a -le $b ]] && echo a kleiner-gleich b
# Es geht aber auch mit einer arithmetischen Umgebung (siehe unten)
[[ (($a < $b )) ]] && echo a kleiner b
[[ (($a > $b )) ]] && echo a größer b
Für weitere Vergleichsoperatoren für Variablen siehe https://tldp.org/LDP/abs/html/comparison-ops.html
Da man die Shell typischerweise nutzt, um Dateien zu maniplieren, existieren etliche Vergeleiche für Dateien, wie z.B.
-e
Datei existiert-f
ist eine reguläre Datei-s
Datei hat nicht Größe Null-d
Datei ist ein Verzeichnis [[ -e "./countries.txt" ]] && echo "countries.txt existiert im Arbeitsverzeichnis"
[[ -d "." ]] && echo "Das Arbeitsverzeichnis '.' ist wenig überraschend ein Verzeichnis."
Für weitere Vergleichsoperatoren für Dateien siehe https://tldp.org/LDP/abs/html/fto.html
Erklären Sie den Fehler des folgenden Code-Schnipsels und beseitigen Sie die Fehlermeldung (aber richtiger Vergleich):
meinName='Christian Herta'
deinName='Jemand anderes'
[ $meinName = $deinName ] || echo zwei unterschiedliche Personen
# example from https://program-script.com/bash/control-structures/if-then-else.php
function if_then_else(){
number=$1
if [ $number -gt 0 ]; then
echo "Zahl ist positiv."
elif [ $number -lt 0 ]; then
echo "Zahl ist negativ."
else
echo "Zahl ist Null."
fi
}
if_then_else -1
Die Rückgabewerte von Kommandos (Programmen) werden oft in bash-Skripten etc. als boolesche Werte interpretiert.
Wenn ein Programm keinen Fehler zurückgibt, ist der Returnwert 0.
Beispielsweise wird in C die main-Funktion in diesem Fall mit return 0
beendet.
ls * 1>/dev/null; echo $?
ls this-file-does-not-exits 2>/dev/null; echo $?
Vergleichen Sie dies mit:
ls * 1>/dev/null && echo "Das Kommando war erfolgreich (exit code = 0)"
ls this-file-does-not-exits 2>/dev/null || echo "Das Kommando war nicht erfolgreich (exit code != 0)"
Das bedeutet true
ist 0 und false
ist 1
im Gegensatz zu den meisten Programmiersprachen!
true; echo true entspricht $?
false; echo false entspricht $?
if who | grep chris > /dev/null
then
echo exit code ist $?. Das ist auch der boolesche Wert für true.
echo 'Chris ist eingeloggt.'
else
echo exit code ist ${?}.Das ist auch der boolesche Wert für false.
echo 'Chris ist nicht eingeloggt.'
fi
# simple regEx
VAR="Tesus"
if [[ $VAR =~ Th?esus ]] # =~ regex-check
then
echo match
fi
# grouping:
VAR="IP address is 192.168.0.1"
regEx="[[:alpha:]]*([[:digit:]]+)\.([[:digit:]]+)\.([[:digit:]]+)\.([[:digit:]]+)"
if [[ $VAR =~ $regEx ]] # =~ regex-check
then
j=0
for i in "${BASH_REMATCH[@]}"; do
echo '${BASH_REMATCH['$j'] =' $i # ${BASH_REMATCH[$j]}
((j++)) # Arithmetic Operationen siehe unten
done
fi
Sehen Sie sich die Funktionen der regEx-Übung an und analysieren Sie diese.
Es verschiedene Möglichkeiten arithmetische Berechnungen durchzuführen, wie expr
, let
, declare
und arithmetic Expansions. Dabei ist letzteres für einfache ganzzahlige Operatoren zu bevorzugen:
$((...))
Arithmetische Expansion mit eventueller Änderung der Shell-Variablen mit Rückgabewert.((...))
Compound Command für arithmetische Ausdrücke (arithmetic expressions), d.h. arithmetische Berechnungen mit eventueller Änderung der Shell-Variablen (ohne Rückgabewert).echo 3 + 4 ist $((3+4))
a=5
((a++)) # Beachte: kein $ vor der Variablen
echo $a
((a*=2))
echo $a
((a=4+3)); echo $a
echo $((a=4+4)) ist $a
Kompliziertere mathematische Operationen können z.B. mit bc
(math-library mit -l
) ausgeführt werden
(siehe auch man bc
):
whatis bc
echo
a=5
x=`echo "e(.6+$a)" | bc -l`
echo "$x"
Erklären Sie folgenden Codeschnipsel in Hinsicht auf die booleschen Werte:
number=1
[ $number -gt 0 ]; echo $?
[ $number -eq 0 ]; echo $?
[ $number -lt 0 ]; echo $?
echo ------------------
((0)); echo $?
((1)); echo $?
((number==1)); echo $?
((number==2)); echo $?
a=4
$(( a++ )) # Ziel erhöhe a um Eins
echo $a
a=4
(( a++ )) # Ziel erhöhe a um Eins
echo $a
z=1
while [[ $z -le 5 ]]
do
echo $z
((z++))
done
z=1
until [ $z -gt 5 ]
do
echo $z
((z++))
done
break
- Loop verlassencontinue
- Beende laufende Iteration# ein ziemlich überkompliziertes Beispiel
# in sehr schlechtem Stil
z=0
while true
do
[[ $z -eq 10 ]] && break
((z++))
if (($z % 2)); then
continue # das ist nur zur Demonstration von continue und so nicht sinnvoll!
fi
echo "$z"
done
Beispiel mit den Aufruf-Argumenten eines Skriptes
echo '#!/usr/bin/env bash
echo das script $0 wurde mit $# Argumenten aufgerufen.
for i in $@ # Loop over all arguments
do
echo $i
done
' > example_script.sh
./example_script.sh # Loop over all arguments
# erinnerung (( ... )) für arithmetiche Ausdrücke
for ((c=1; c<=5; c++)); do
echo "Welcome $c times"
done
Beachten Sie: hier wurde das do
in dieselbe Zeile des for
gesetzt. Dafür muss
ein Semikolon als Trenner verwendet werden.
Man kann auch alles in einer Zeile schreiben, wie hier:
for c in {1..6} ; do echo "Welcome $c times"; done
# Range
echo {1..6}
# geht auch rückwärts mit anderer Schrittweite
echo {10..0..2}
Hinweis: Mit select
für einen Loop lässt sich ein einfache Eingabe-Menü realisieren, siehe
https://ryanstutorials.net/bash-scripting-tutorial/bash-loops.php#select
Mittels bash Skripten kann man auch einfach Dateien in Verzeichnissen für die weitere Verarbeitung iterieren.
for file in ./*.txt
do
echo $file
done
Beachte Sie, dass das Globbing ./*.txt
von der Shell aufgelöst wird, sodass
effektiv dort ein Liste von Strings steht. So ähnlich, wie hier gezeigt:
for file in a.txt b.txt c.txt
do
echo $file
done
Falls das Globbing nicht ausgelöst wird, d.h. keine Dateien auf das Glob-Muster zutreffen, wird das Glob-Muster direkt verwendet. Dies kann zu Fehlermeldungen führen, wie hier demonstriert:
for file in ./*.texte
do
ls $file
done
In der Bash kann das Verhalten des Globbings mit dem Befehl shopt
geändert werden.
shopt
kann das Verhalten der Shell geändert werden (und somit auch des Globbings),
mehr siehe https://www.gnu.org/software/bash/manual/html_node/The-Shopt-Builtin.html.
Mit shopt -s nullglob
wird ein Globmuster leer ersetzt, falls es auf nichts zutrifft.
shopt -s nullglob # set (-s) von nullglob
for file in ./*.texte
do
ls "$file"
done
Hier setzen wird das Standardverhalten wieder zurück:
# unset (-u) von nullglob
shopt -u nullglob
Mit Hilfe eines Tests (Datei existiert?) und continue
lässt sich das Problem auch beseitigen:
for file in ./*.texte
do
[ -f "$file" ] || continue
ls "$file"
done
Mittels read
(siehe help read
) kann man eine Datei zeilenweise einlesen:
cat countries.txt | while read line; do
echo $line
done
Typischerweise sieht das zeilenweise Einlesen aus einer Datei in einem Skript folgendermaßen aus:
#!/bin/bash
input="/path/to/txt/file"
while IFS= read -r line
do
echo "$line"
done < "$input"
Was bedeutet hier IFS=
?
Dies bewirkt, dass der internal field separator (IFS
) auf Nichts eingestellt wird. So kann verhindert werden, dass führende und abschließende Leerzeichen nicht mitgelesen werden. D.h. man will in der Regel die führende und abschließende Leerzeichen in der Laufvariable (hier line
) dabei haben.
Probieren Sie obiges Skript aus und löschen Sie auch mal das IFS=
weg. Erzeugen Sie hierzu eine entsprechende Textdatei mit führenden und abschließenden Leerzeichen.
Mittels des Tools getopts
lassen sich Argumente des Skripts in Kurzform, wie z.B. -f
, einfach auswerten. Beispiel:
#!/bin/bash
OPTIND=1 # Reset in case getopts has been used previously in the shell.
# Initialize our own variables:
verbose=0
function show_help {
echo "Usage: ${0} [options]"
echo "Options:"
echo " -t"
echo " test run to check the configuration."
echo " -d"
echo " distributed training."
echo " -c"
echo " path to configuration file (should not be used together with -d)."
exit
}
# "h?vtdc:" are the possible arguments
# "c:" means that the switch "-c" is followed by an argument
while getopts "h?vtdc:" opt; do
case "$opt" in
h|\?)
show_help
exit 0
;;
v) verbose=1
;;
t) test='--test'
;;
d) distributed=1
;;
c) config=$OPTARG
esac
done
Das Skript kann z.B. mittels ./skript.sh -t -c myconfig.txt
aufgerufen werden.
Für ein ausführlicheres getopts
-Tutorial, siehe z.B. https://wiki.bash-hackers.org/howto/getopts_tutorial
Für weitere Möglichkeiten (neben getopts
) siehe z.B.
https://stackoverflow.com/questions/192249/how-do-i-parse-command-line-arguments-in-bash
Hilfreich ist es für Skripte den Strict-Mode einzuschalten. Dazu beginnt man ein bash-Skript mit folgenden Zeilen:
#!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'
Dies vermeidet subtile Fehler zur Laufzeit. Lesen Sie hierzu: unofficial-bash-strict-mode
Das Setzen des IFS
erfolgt hier mit ANSI-C-Quoting
Es gibt Checker-Werkzeuge, wie shellcheck, die Ihnen helfen Fehler zu finden.
Schreiben Sie ein Skript, das alle MP3 Dateien in einem Ordner mit einer Zahl am Anfang, so umbenennt, dass die Zahl immer 3 Stellen hat. Hintergrund: Ein MP3-Player spielt die Dateien meist nach String-Sortierung ab.
1_Einleitung.mp3
die Datei 001_Einleitung.mp3
14_Das_Erwachen.mp3
die Datei 014_Das_Erwachen.mp3
Erzeugen Sie zum Testen Ihres Skriptes mit touch
und brace expansion hundert Dateien, mit dem passenden Muster.