Fischertechnik
AVR
Raspberry Pi
Elektronik
Netzwerk
Sonstiges


















Impressum

/bin/bash

Bis vor wenigen Monaten habe ich mir viel Mühe gegeben bei der shell-Programmierung auf Erweiterungen zu verzichten. Ziel war portabler Code für nahezu beliebige Plattformen. Mit diesem Bestreben stand ich am Ende recht einsam da. Einerseits weil immer mehr Systeme unter Linux laufen und andererseits weil die allseits sehr geschätzte bash (bourne-again shell) von den Sysadmins sofort nachinstalliert wird, sollte sie irgendwo fehlen. Diese shell bietet sehr umfangreiche Möglichkeiten und erspart viele Aufrufe von awk, sed oder auch bc. Hier zum Nachschlagen der Ausschnitt an Funktionen, die ich zunehmend nutze.

Text (string) Operationen

satz="the quick brown fox jumps over the lazy dog"
BeispielErgebnisErklärung
${}${satz}the quick brown fox jumps over the lazy dogDer Inhalt der Variablen
${#}${#satz}43Die Länge des Inhalts der Variablen
Substring
${:n}${satz:4}quick brown fox jumps over the lazy dogsubstring; schneide links vier Zeichen ab.
${::-m}${satz::-4}the quick brown fox jumps over the lazysubstring; schneide rechts vier Zeichen ab.
${:n:m}${satz:4:11}quick brownsubstring ab Position vier elf Zeichen; n beginnt links mit 0
${:n:-m}${satz:4:-4}quick brown fox jumps over the lazysubstring ab Position vier und schneide rechts ebenfalls vier Zeichen ab.
Ersetzen und Löschen. "?" steht hier für genau ein beliebiges Zeichen; "*" steht für mehrere.
${//}${satz/fox/cat}the quick brown cat jumps over the lazy dogErsetze das erste Auftreten von fox durch cat
${///}${satz//the/a}a quick brown fox jumps over a lazy dogErsetze alle the durch a
${satz//the /}quick brown fox jumps over lazy dogLösche alle "the "
${/#/}${satz/#the/a}a quick brown fox jumps over the lazy dogErsetze nur an Anfang des Strings the durch a
${/%/}${satz/%dog/cat}the quick brown fox jumps over the lazy catErsetze nur am Ende des Strings dog durch cat
${%%}${satz%% over*}the quick brown fox jumpsLösche nur am Ende des Strings ab " over"
${##}${satz##*brown }fox jumps over the lazy dogLösche nur am Anfang des Strings bis "brown "
Beispiele mit "positional parameters"
${##}${0##*/}scriptname ohne PfadLösche Pfadangaben bis zum letzten "/" aus $0
${:n}set the quick brown fox jumps over the lazy dog; echo ${3:2}ownSetze erst die "positional parameters" und nehme vom dritten dann ab Zeichen Nummer drei (lösche 2)

Zahlen Operationen

Wertebereich -(1<<63)..(1<<63) - 1
(-9223372036854775808..+9223372036854775807)
Oktale Zahlen mit einer führenden 0 und nur Ziffern 0..7
Hexadezimal durch vorangestelltes "0x" und Ziffern 0..9a..fA..F
BeispielErgebnisErklärung
$((+))$(( 4+6 ))10Addition
$((-))$(( 4-6 ))-2Subtraktion
$((*))$(( 4*6 ))24Multiplikation
$((/))$(( 25/6 ))4Ganzzahlige Division
$((%))$(( 25%6 ))1Modulo (Rest bei Division)
$((**))$(( 5**3 ))125Potenz
$((<<))$(( 3<<2 ))12Linksschieben; Priorität unter Addition/Subtraktion
$((>>))$(( 128>>3 ))16Rechtsschieben; Priorität unter Addition/Subtraktion
$((|))$(( 4|6 ))6Bitweise oder (OR)
$((&))$(( 4&6 ))4Bitweise und (AND)
$((^))$(( 4^6 ))2Bitweise exklusiv oder (XOR)
$RANDOM$RANDOM716Zufallszahl aus dem Bereich 0..32767 (je einschließlich)
$(( $RANDOM>>5 ))716Zufallszahl aus dem Bereich 0..1023 (je einschließlich)
$(( 1+$RANDOM>>5 ))716Zufallszahl aus dem Bereich 0..1023 (je einschließlich)
$(( 1+($RANDOM>>5) ))717Zufallszahl aus dem Bereich 1..1024 (je einschließlich)
$((#))$(( 36#A -9 ))1Zahl im anderen Zahlensystem; hier 36er-System; Beispiel zur Bestimmung der Position eines Buchstabens im Alphabet
$(( 36#Z -9 ))26

Listen und Felder / Arrays

Die Shell unterstützt eindimensionale Felder. Die Adressierung der Elemente erfolgt, wie in vielen anderen Sprachen auch, mit eckigen Klammern. Der Index zählt ab 0 und es müssen nicht alle Feldelemente fortlaufend belegt sein. a[7]="TESA"; a[12]="FILM"; echo ${#a[*]}; echo ${a[*]} gibt zuerst zwei als Anzahl der Feldelemente aus und dann TESA FILM als einzige belegte Feldelemente. Bisher kenne ich keine Möglichkeit abzufragen, welche Elemente belegt sind.
BeispielErgebnisErklärung
()a=( 1 2 3 )a[0]=1
a[1]=2
a[2]=3
Die Listenelemente werden fortlaufend in das angebene Feld a[] geschrieben.
${[n]}${a[0]}1Zugriff auf ein einzelnes Feldelement
${[*]}${a[*]}1 2 3Liste aller Elemente. Das Ergebnis ist nur ein Wert.
a=(1 2 3);
for i in "${a[*]}";do echo $i;done
1 2 3Nur ein Schleifendurchlauf
${[@]}${a[@]}1 2 3Liste aller Elemente als einzelne Werte.
a=(1 2 3);
for i in "${a[@]}";do echo $i;done
1
2
3
Pro Element ein Durchlauf.
a=(the quick brown fox jumps over the lazy dog);
for i in "${a[@]}";do echo $i;done
the
quick
brown
fox
jumps
over
the
lazy
dog
Wie sonst auch sind die Variablen typfrei und können Text enthalten.
${#[*]}a=(the quick brown fox jumps over the lazy dog);
echo ${#a[*]}
9Anzahl der Feldelemente
${#[@]}a=(the quick brown fox jumps over the lazy dog);
echo ${#a[@]}
9Anzahl der Feldelemente; mir ist kein Unterschied zur vorherigen Variante bekannt.
{..}{a..d}a b c dFortlaufende Liste a bis d
hex=( {0..9} {a..f} ); echo ${hex[11]}bBeispiel zur Anwendung; hexadezimale Darstellung der Zahlen 0 bis 15

Reguläre Ausdrücke / regex

In doppelten eckigen Klammern kann die Shell auch reguläre Ausdrücke auswerten. Erst mit der Feld-Variablen BASH_REMATCH wird die Mächtigkeit deutlich.
AusdruckBeispielErgebnisErklärung
[[ =~ ]][[ "Hallo" =~ Ha..o ]] ; echo $?;0 (true)Der Punkt steht für ein einzelnes Zeichen. Der Ausdruck auf der rechten Seite darf nur unter strengen Bedingungen in Anführungsstrichen stehen. Leerzeichen erfüllen diese Bedingungen.
[[ =~ ]][[ "Hallo" =~ Hall ]] ; echo $?;0 (true)Auch hier passt der Ausdruck, da "Hall" in "Hallo" enthalten ist.
[[ =~ $ ]][[ "Hallo" =~ Hall$ ]] ; echo $?;1 (false)Textende wird mit "$" angegeben: "Hallo" endet nicht mit "Hall".
[[ =~ ^ ]][[ "Hallo" =~ ^Hall ]] ; echo $?;0 (true)Textanfang wird mit "^" angegeben: "Hallo" beginnt mit "Hall".
[[ =~ [ .. ] ]][[ "Hallo" =~ H[a-z]ll ]] ; echo $?;0 (true)Zeichenbereiche können angegeben werden.
[[ =~ [^ .. ] ]][[ "Hallo" =~ H[^A-Z]ll ]] ; echo $?;0 (true)nicht erlaubte Zeichenbereiche können angegeben werden.
[[ =~ {} ]][[ "Hallo" =~ Hal{2}o ]] ; echo $?;0 (true)Zeichenwiederholungen können angegeben werden.
[[ =~ {,} ]][[ "Hallo" =~ Hal{2,3}o ]] ; echo $?;0 (true)Mindestens 2, höchstens 3 Vorkommen
[[ =~ () ]][[ "2016-12-24 23:00" =~ ([0-9]{4})-([0-9]{2})-([0-9]{2})\ ([0-9]{2}):([0-9]{2}) ]];
for i in {1..5}; do
  echo ${BASH_REMATCH[i]};
done
2016
12
24
23
00
Werden Teile des regulären Ausdrucks in runde Klammern eingeschlossen, können die passenden Textstücke über das Feld BASH_REMATCH[] weiterverabeitet werden. Das Element [0] enthält den gesamten Teil des Textes, der durch den Ausdruck abgedeckt wurde.
[[ "Timestamp 2016-12-24 23:00:00" =~ ([0-9]{4})-([0-9]{2})-([0-9]{2})\ ([0-9]{2}):([0-9]{2}) ]] ;
for i in {0..9}; do
  echo $i:${BASH_REMATCH[i]};
done;
echo Anzahl=${#BASH_REMATCH[@]}
0:2016-12-24
23:00
1:2016
2:12
3:24
4:23
5:00
6:
7:
8:
9:
Anzahl=6
Das Element [0] enthält enthält hier weder den Anfang "Timestamp ", noch die abschließenden Sekunden ":00". Die Anzahl der Feldelemente/Teilausdrücke steht in ${#BASH_REMATCH[@]}. Achtung [0] zählt mit.
[[ "Hallo" =~ Ha*llo ]] ; echo $?;0 (true)Null oder beliebig viele Wiederholungen
[[ "Hallo" =~ Ha+llo ]] ; echo $?;0 (true)Mindestens ein Vorkommen

Undefinierte Variablen ersetzen

unset a; echo ${a-"Hallo"};a="Test"; echo "${a-"Hallo"}"
Hallo
Test
Nur wenn die Variable (hier ${a}) nicht existiert, wird die Ersetzung gewählt. Dieses ist sinnvoll, wenn zum Beispiel Parameter beim Funktionsauf mit sinnvollen Default-Werten belegt werden sollen, wenn sie nicht angegeben wurden.

Rechnen mit IP-Adressen

Innerhalb der bash ist das Rechnen mit IP-Adressen erstaunlich einfach. Im folgenden Skript führe ich die wesentliche Operationen mit 32-Bit durch; die beiden ersten Funktionen dienen deshalb der Umwandlung in dieses Format ip2dec und wieder zurück dec2ip. Seit vielen Jahren wird überwiegend die CIDR-Notation angewendet. Classless Inter-Domain Routing (CIDR) ist im Gegensatz zu früheren Denken in Class-A, Class-B und Class-C frei in der Anzahl der Subnetz-Bits. In der zugehörigen Notation wird die Anzahl den Netz-Bits einfach an die IP-Adresse angehÃĪngt. 192.168.178.1/24 beschreibt also die erste nutzbare Adresse im Netz 192.168.178.0-192.168.178.255. Die letzte Adresse entspricht der Broadcast-Adresse im Netz; die erste der Netz-Adresse.
Die dritte Funktion snbits2dec wandelt die "/24" in die zur Rechnung geeignete 32-Bit-Zahl (hier 4294967040) um, die vierte Funktion snbits2ip in die besser lesbare Subnetzmaske (hier 255.255.255.0).
Die folgende Funktion cidr2net berechnet aus einer Adresse in CIDR-Notation die Netz-Adresse und die Funktion cidr2broadcast die zugehörige Broadcast-Adresse.
Die Funktion cidrMatchIP erwartet zwei Parameter. Der erste beschreibt ein Netz in CIDR-Notation, der zweite eine einfache IP-Adresse. Wenn die IP-Adresse innerhalb des Netze liegt, wird true ($?=0) zurückgegeben.
Für kleine Netze eignet sich die Funktion cidrListIP. Sie zeigt alle Netzadressen zu einem Netz in CIDR-Notation an.
Wer häufig mit Netzadressen hantieren muss, kann sich die acht Funktionen in seine Profile-Datei übernehmen und hat sie damit jederzeit zur Verfügung.

Beispiel-Funktionen zum Rechnen mit IP-Adressen in der bash


#!/bin/bash
function ip2dec {
  local ip=(${1//./ });
  echo $(( (${ip[0]}<<24) | (${ip[1]}<<16) | (${ip[2]}<<8) | (${ip[3]}) ));
};

function dec2ip {
  echo $(($1>>24&255)).$(($1>>16&255)).$(($1>>8&255)).$(($1&255))  
}

function snbits2dec {
  echo $(( 0xffffffff ^ ( ( 1<<(32-$1) ) -1 ) ))
}

function snbits2ip {
  dec2ip $(snbits2dec $1)
}

function cidr2net {
  [[ $1 =~ ^([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)\/([0-9]+)$ ]];
  dec2ip $(($(ip2dec ${BASH_REMATCH[1]}) & $(snbits2dec ${BASH_REMATCH[2]}) ))
}

function cidr2broadcast {
  [[ $1 =~ ^([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)\/([0-9]+)$ ]];
  dec2ip $(($(ip2dec ${BASH_REMATCH[1]}) | $(( (1<<(32-${BASH_REMATCH[2]}))-1 )) ))
}

function cidrMatchIP {
  [[ $1 =~ ^([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)\/([0-9]+)$ ]];
  local maskdec=$(snbits2dec ${BASH_REMATCH[2]});
  local netA=$(($(ip2dec ${BASH_REMATCH[1]}) & $maskdec ));
  local netB=$(($(ip2dec ${2}              ) & $maskdec ));
  [ $netA -eq $netB ]
}

function cidrListIP {
  local erste=$(ip2dec $(cidr2net $1));
  local letzte=$(ip2dec $(cidr2broadcast $1));
  for (( i=$erste; i<=$letzte; i++ ));
  do
    dec2ip $i;
  done
}


Testaufrufe


ip=192.168.1.77;
dec2ip $(($(ip2dec $ip) & $(ip2dec 255.255.255.0) ))
dec2ip $(snbits2dec 24)
dec2ip $(snbits2dec 14)
snbits2ip 8
snbits2ip 31
snbits2ip 32
cidr2net 192.168.2.77/31
cidr2net 192.168.2.77/24
cidr2net 192.168.2.77/16
cidr2net 192.168.2.77/8
cidr2broadcast 192.168.2.77/31
cidr2broadcast 192.168.2.77/24
cidr2broadcast 192.168.2.77/16
cidr2broadcast 192.168.2.77/8
cidrMatchIP 192.168.2.77/24 192.168.2.8 && echo ok 192.168.2.77/24 192.168.2.8
cidrMatchIP 192.168.2.77/24 192.168.3.8 && echo ok 192.168.2.77/24 192.168.3.8


Ausgabe der Testaufrufe


192.168.1.0
255.255.255.0
255.252.0.0
255.0.0.0
255.255.255.254
255.255.255.255
192.168.2.76
192.168.2.0
192.168.0.0
192.0.0.0
192.168.2.77
192.168.2.255
192.168.255.255
192.255.255.255
ok 192.168.2.77/24 192.168.2.8

FunktionBeispielErgebnisErklärung
ip2decip2dec 192.168.2.773232236109Die IP-Adresse 192.168.2.77 als 32-Bit-Zahl
dec2ipdec2ip 3232236109192.168.2.77Rückwandlung in eine lesbare IP-Adresse
dec2ip $(($(ip2dec 192.168.1.77) & $(ip2dec 255.255.255.0) ))192.168.1.0Die Verrechnung (AND) der IP-Adresse 192.168.1.77 mit der Netzmaske 255.255.255.0 ergibt die Netzadresse 192.168.1.0
snbits2ipsnbits2ip 30255.255.255.252Netzmaske für ein /30-Netz.
cidr2netcidr2net 192.168.100.197/30192.168.100.196Netz-Adresse im angegebenen /30-Netz.
cidr2broadcastcidr2broadcast 192.168.100.197/30192.168.100.199Broadcast-Adresse im angegebenen /30-Netz.
cidrListIPcidrListIP 192.168.100.197/30192.168.100.196
192.168.100.197
192.168.100.198
192.168.100.199
Liste der IP-Adressen im angegebenen /30-Netz. Wirklich nutzbar sind hier meist nur die zweite (.197) und die dritte (.198), da Netz- und Broadcast-Adressen reserviert sind.
cidrMatchIPcidrMatchIP 192.168.100.197/30 192.168.100.199trueDie IP-Adresse 192.168.100.199 liegt im angegebenen /30-Netz.
cidrMatchIP 192.168.100.197/30 192.168.100.200falseDie IP-Adresse 192.168.100.200 liegt nicht im angegebenen /30-Netz.