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

Schreibe einen Kommentar