Es muss nicht immer sed, awk oder cut sein: auch die Bash bietet interessante Features um Strings zu manipulieren.
Ausgangslage: Dokument users.json
Die nachfolgenden Beispiele orientieren sich am json-Dokument users.json. Die Formatierung wurde hier absichtlich “maschinen-freundlich” gehalten. Ungefähr so sind die meisten json Dokumente in freier Wildbahn strukturiert.
[
{ "user_id": "chz", "last_name": "Parlato", "first_name": "Caren", "age": 35 },
{ "user_id": "Ogt", "last_name": "Villota", "first_name": "Lilly", "age": 50 },
{ "user_id": "5sI", "last_name": "Alsmann", "first_name": "Tammi", "age": 33 }
]
Nun möchte ich den Wert eines Feldes extrahieren, in diesem Fall das Alter einer bestimmten User-Id
field="age"
user_id="Ogt"
j=`grep $user_id users.json`;
j=${j#*$field\":}; j=${j%%,*};
j=${j//\"/}; j=${j/\}/};j=${j//\ /}
echo ${j:=<undef>}
50
Aber was ist hier passiert?
Zeichenketten abschneiden
Alle Werte links von Muster abschneiden
Zeichenketten können basierend auf einer Position beschnitten werden, oder aufgrund eines Musters. Bei obigem Beispiel ist $j nach dem grep:
{ "user_id": "Ogt", "last_name": "Villota", "first_name": "Lilly", "age": 50 },
Um den Wert des gewünschten Feldes “age” zu erhalten, wird alles links von age abgeschnitten. Dies bewirkt “j=${j#*$field\”:}”
j=`grep $user_id users.json`; j=${j#*$field\":}
echo $j
50 },
Dabei steht #* für die kürzeste Suche. Das angegeben Muster $field also “age” wird ebenfalls abgeschnitten. In diesem Fall hätte ebenfalls der gierige Operator ## für ” eingesetzt werden können, da “age” im json-File das letzte Feld mit Apostrophen ist:
j=`grep $user_id users.json`; j=${j##*\":}
echo $j
50 },
Natürlich ist die zweite Variante in diesem Beispiel nur für das allerletzte Feld im json-File brauchbar.
Alle Werte rechts von Muster abschneiden
Genauso wie links von einem gefundenen Muster abgeschnitten werden kann, kann auch rechts abgeschnitten werden. In diesem Fall könnte auch auf den gierigen Operator verzichtet werden, und mit einem einfachen “%” der erste Treffer (das letzte Komma bei “50 },”, aber der Code soll ja für jedes Feld gelten:
j=${j%%,*};
echo $j
50 }
Muster ersetzen
Nun möchte ich die überflüssigen Zeichen Whitespace, } und ” loswerden. Dies kann ich mit 3 Substituierungen erledigen:
j=${j//\"/}; j=${j/\}/};j=${j//\ /}
echo $j
50
Setze ich “last_name” als $field so erhalte ich
echo $j
Villota
Auch hier gibt es gierige Operatoren. Doppelte Slash “//” sind gierige, einfache Slash “/” ersetzen nur einmal:
j=${j//\"/}; # gierig
j=${j//\ /}; # gierig
j=${j/\}/}; # ersetze nur einmal
Strings nach Position zerlegen
Die Shell kann auch Strings nach Position der Characters zerlegen:
syslg="Nov 15 18:38:08 srvhpnb awesome[1651]: poll(2) failed due to: Resource temporarily unavailable."
# Die Characters von Position 16 bis 23 auslesen:
echo ${syslg:16:7}
srvhpnb
Länge von Strings ermitteln
Ich kann die Länge eines Strings ermitteln mit:
j="dieser Satz ist ein Beispiel"
echo ${#j}
28
Defaultwerte
Defaultwerte bieten die Möglichkeit einen Wert für eine Variable festzulegen, auch wenn diese eigentlich leer ist. Für eine Fehlersuche kann dies sehr hilfreich sein. Was, wenn ich einen Fehler gemacht habe und “0gt” statt “Ogt” als user_id angegeben habe?
echo ${j:-<undef>}
<undef>
Mit ${<Variable>:=”String”} kann ich einen Defaultwert zurückgeben auch wenn die Variable nicht definiert oder leer ist (j=””)
weitere Variablensubstitutionen
# Defaultwert wird zurückgegeben wenn Variable
# nicht definiert ist
echo ${j-<undef>}
# Defaultwert wird zurückgegeben wenn Variable
# definiert aber leer ist
echo ${j+<undef>}
# Defaultwert wird zurückgegeben wenn Variable
# definiert und nicht leer ist
echo ${j:+<undef>}
# Defaultwert wird j zugewiesen wenn Variable
# nicht definiert oder leer ist
echo ${j:=<undef>}
# Defaultwert wird zugewiesen, wenn Variable
# nicht definiert ist
echo ${j=<undef>}
# Script wird beendet, wenn Variable nicht definiert
# oder leer ist
echo ${j:?<undef>}
# Script beenden, wenn Variable nicht definiert ist
echo ${j?<undef>}
Typisierte Variablen
Mit declare kann ich den Typ einer Variable festlegen. Im Beispiel mit dem Feld “age” wäre dies
declare -i age
age=$j
Wäre nun ein Eintrag Fehlerhaft und würde statt 50 ein Wert wie 50a stehen, bricht das Script mit einem Fehler ab:
echo $j
50a
declare -i age
age=$j
bash: 50a: value too great for base (error token is "50a")
Weitere Variablentypen definieren
# Variable i wird readonly gesetzt
declare -r i
# Variable i wandelt alle Werte in Kleinschreibung um
declare -l i
# Variable in wandelt alle Werte in Grossschreibung um
declare -u i
Arrays
Bash kennt auch Arrays. Diese könne unterschiedlich definiert werden
declare -a j
j[0]="eins"
j[1]="zwei"
j[2]="drei"
echo ${j[@]}
eins zwei drei
# oder schneller
j=(eins zwei drei)
echo ${j[@]}
eins zwei drei
Einige Dinge die man mit Arrays tun kann:
# Lösche Elemente
unset j[2]
# Zeige Anzahl Elemente an
echo ${#j[@]}
Assoziative Arrays
Auch assoziative Arrays können gebildet werden. In Perl heissen sie Has, in Python Dictionaries:
declare -a j
j[eins]="erstes Element"
j[zwei]="zweites Element"
echo ${j[zwei]}
zweites Element
Strings und Arrays
Äusserst praktisch ist das Umwandeln von Strings in Arrays um den String in Felder aufzuteilen:
j="das ist ein Satz"
i=($j)
echo ${i[2]}
ein
Setzen von Feldtrenner IFS
Manchmal ist Space nicht der korrekte Feldtrenner. Mit IFS kann er separat gesetzt werden:
IFS=$':'
j=`tail -n1 /etc/passwd`
i=($j)
echo ${i[0]}
ansible