KAPITEL 9

Textdateien wirksam einsetzen

Einfacher (»Plain«) Text ist auf vielen Linux-Systemen das gebräuchlichste Datenformat. Der Inhalt, der in den meisten Pipelines von Befehl zu Befehl geschickt wird, ist Text. Die Quellcodedateien von Programmierern, die Systemkonfigurationsdateien in /etc sowie HTML- und Markdown-Dateien sind Textdateien. E-Mail-Nachrichten sind Text; selbst Anhänge werden intern zum Transport als Text gespeichert. Sie könnten sogar alltägliche Dateien wie Einkaufslisten und persönliche Notizen als Text speichern.

Betrachten Sie demgegenüber einmal das heutige Internet, einen Mischmasch aus Streaming-Audio und -Video, Social-Media-Posts, im Browser vorliegenden Dokumenten in Google Docs und Office 365, PDFs und anderen Rich-Media-Daten. (Ganz zu schweigen von den Daten, die von mobilen Apps verarbeitet werden, die das Konzept einer »Datei« vor einer ganzen Generation versteckt haben.) Vor diesem Hintergrund wirken einfache Textdateien geradezu altmodisch.

Dennoch kann jede Textdatei zu einer reichhaltigen Datenquelle werden, die Sie mit sorgfältig gestalteten Linux-Befehlen ausbeuten können, vor allem wenn der Text strukturiert ist. Jede Zeile in der Datei /etc/passwd zum Beispiel repräsentiert einen Linux-Benutzer und besitzt sieben Felder, einschließlich des Benutzernamens, einer numerischen Benutzer-ID, des Home-Verzeichnisses und mehr. Die Felder sind durch Doppelpunkte getrennt, sodass die Datei leicht mithilfe von cut -d: oder awk -F: analysiert werden kann. Hier ist ein Befehl, der alle Benutzernamen (das erste Feld) alphabetisch ausgibt:

$ cut -d: -f1 /etc/passwd | sort

avahi

backup

daemon

...

Und hier ist einer, der menschliche Benutzer anhand ihrer numerischen Benutzer-IDs von Systemkonten trennt und den Benutzern eine Willkommens-E-Mail schickt. Bauen wir uns diesen Einzeiler Schritt für Schritt zusammen. Zuerst verwenden Sie awk, um die Benutzernamen (Feld 1) auszugeben, wenn die numerische Benutzer-ID (Feld 3) 1000 oder größer ist:

$ awk -F: '$3>=1000 {print $1}' /etc/passwd

jones

smith

Dann werden die Grüße erzeugt, und zwar mittels einer Pipeline an xargs:

$ awk -F: '$3>=1000 {print $1}' /etc/passwd \

| xargs -I@ echo "Hallo, @!"

Hallo, jones!

Hallo, smith!

Danach generieren Sie Befehle (Strings), um die einzelnen Begrüßungen in einer Pipeline an den Befehl mail zu leiten, der eine E-Mail mit einer bestimmten Betreffzeile (-s) an einen bestimmten Benutzer sendet:

$ awk -F: '$3>=1000 {print $1}' /etc/passwd \

| xargs -I@ echo 'echo "Hi there, @!" | mail -s Gruesse @'

echo "Hallo, jones!" | mail -s Gruesse jones

echo "Hallo, smith!" | mail -s Gruesse smith

Schließlich leiten Sie die generierten Befehle mit einer Pipeline an die bash, um die E-Mails zu senden:

$ awk -F: '$3>=1000 {print $1}' /etc/passwd \

| xargs -I@ echo 'echo "Hallo, @!" | mail -s Gruesse @' \

| bash

echo "Hallo, jones!" | mail -s Gruesse jones

echo "Hallo, smith!" | mail -s Gruesse smith

Die gezeigten Lösungen beginnen, wie so viele andere in diesem Buch, mit einer vorhandenen Textdatei und manipulieren deren Inhalt mit Befehlen. Es wird Zeit, diesen Ansatz umzukehren und bewusst neue Textdateien zu entwerfen, die gut mit Linux-Befehlen zusammenspielen.1 Es ist eine Gewinnerstrategie, um wirklich effizient auf einem Linux-System zu arbeiten. Alles, was nötig ist, sind vier Schritte:

  1. Ein zu lösendes Geschäftsproblem erkennen, das mit Daten zu tun hat.
  2. Speichern der Daten in einer Textdatei in einem passenden Format.
  3. Erfinden von Linux-Befehlen, die diese Datei verarbeiten, um das Problem zu lösen.
  4. (optional) Festhalten dieser Befehle in Skripten, Aliasen oder Funktionen, um sie einfacher ausführen zu können.

In diesem Kapitel konstruieren Sie eine Vielzahl an strukturierten Textdateien und erzeugen Befehle, um diese zu verarbeiten und damit verschiedene Probleme zu lösen.

Ein erstes Beispiel: Dateien finden

Nehmen wir einmal an, Ihr Home-Verzeichnis enthielte Zehntausende von Dateien und Unterverzeichnissen und Sie könnten sich mal wieder nicht daran erinnern, wohin Sie etwas gespeichert haben. Der Befehl find findet eine Datei anhand des Namens, also etwa animals.txt:

$ find $HOME -name animals.txt -print

/home/smith/Work/Writing/Books/Lists/animals.txt

Allerdings ist find langsam, weil es Ihr gesamtes Home-Verzeichnis durchsucht und Sie regelmäßig Dateien finden müssen. Dies ist Schritt 1, Erkennen eines Geschäftsproblems, das mit Daten zu tun hat: schnelles Auffinden von Dateien anhand ihres Namens in Ihrem Home-Verzeichnis.

Schritt 2 besteht darin, die Daten in einem passenden Format in einer Textdatei zu speichern. Führen Sie einmal find aus, um eine Liste all Ihrer Dateien und Verzeichnisse mit einem Dateipfad pro Zeile zu erstellen. Speichern Sie diese Liste in einer verborgenen Datei:

$ find $HOME -print > $HOME/.ALLFILES

$ head -n3 $HOME/.ALLFILES

/home/smith

/home/smith/Work

/home/smith/Work/resume.pdf

...

Nun haben Sie die Daten: einen zeilenweisen Index Ihrer Dateien. In Schritt 3 geht es darum, Linux-Befehle zu erfinden, mit denen sich die Suche nach Dateien beschleunigen lässt. Dazu verwenden wir grep. Es ist viel schneller, mit grep durch eine große Datei zu gehen, als find in einem großen Verzeichnisbaum auszuführen:

$ grep animals.txt $HOME/.ALLFILES

/home/smith/Work/Writing/Books/Lists/animals.txt

Schritt 4 besteht darin, die Ausführung des Befehls zu erleichtern. Schreiben Sie ein einzeiliges Skript namens ff für find files, das grep mit vom Benutzer vorgegebenen Optionen und einem Suchstring ausführt, wie in Beispiel 9-1 gezeigt.

Beispiel 9-1: Das ff-Skript

#!/bin/bash

# $@ bedeutet alle Argumente, die dem Skript übergeben werden.

grep "$@" $HOME/.ALLFILES

Machen Sie das Skript ausführbar und legen Sie es in einem Verzeichnis in Ihrem Suchpfad ab, also etwa in Ihrem persönlichen bin-Unterverzeichnis:

$ chmod+xff

$ echo $PATH Überprüfen Sie Ihren Suchpfad.

/home/smith/bin:/usr/local/bin:/usr/bin:/bin

$ mv ff ~/bin

Führen Sie immer dann ff aus, wenn Sie schnell Dateien finden wollen, sich aber nicht erinnern können, wo Sie sie abgelegt haben.

$ ff animal

/home/smith/Work/Writing/Books/Lists/animals.txt

$ ff -i animal | less Groß-/Kleinschreibung spielt bei diesem grep keine Rolle.

/home/smith/Work/Writing/Books/Lists/animals.txt

/home/smith/Vacations/Zoos/Animals/pandas.txt

/home/smith/Vacations/Zoos/Animals/tigers.txt

...

$ ff -i animal | wc -l Wie viele Treffer?

16

Führen Sie immer wieder mal den Befehl find aus, um den Index zu aktualisieren. (Oder besser noch: Erzeugen Sie mit cron einen Job, der regelmäßig ausgeführt wird; siehe »Lernen Sie cron, crontab und at« auf Seite 215.) Voilà – Sie haben sich aus zwei kleinen Befehlen ein schnelles, flexibles Dateisuchwerkzeug gebaut. Linux-Systeme bieten auch andere Anwendungen, die schnell Dateien indizieren und suchen, wie etwa den Befehl locate und die Suchhilfsprogramme in GNOME, KDE Plasma und anderen Desktopumgebungen, aber das ist hier egal. Schauen Sie einfach, wie leicht es war, selbst etwas zu bauen. Und der Schlüssel zum Erfolg bestand in einer Textdatei in einem einfachen Format.

Das Ablaufdatum von Domains prüfen

Für das nächste Beispiel wollen wir einmal annehmen, dass Sie einige Internet-Domainnamen besitzen und im Auge behalten wollen, wann diese ablaufen, damit Sie sie rechtzeitig erneuern können. Das ist Schritt 1, Identifizieren des Geschäftsproblems. Schritt 2 ist es, eine Datei mit diesen Domainnamen zu erzeugen, also etwa domains.txt, mit einem Domainnamen pro Zeile:

example.com

oreilly.com

efficientlinux.com

...

In Schritt 3 sollen Befehle erfunden werden, die diese Textdatei nutzen, um die Ablaufdaten zu ermitteln. Beginnen Sie mit dem Befehl whois, der eine Registrierungsstelle nach Informationen über eine Domain abfragt:

$ whois example.com | less

Domain Name: EXAMPLE.COM

Registry Domain ID: 2336799_DOMAIN_COM-VRSN

Registrar WHOIS Server: whois.iana.org

Updated Date: 2021-08-14T07:01:44Z

Creation Date: 1995-08-14T04:00:00Z

Registry Expiry Date: 2022-08-13T04:00:00Z

...

Dem Ablaufdatum ist der String Registry Expiry Date vorangestellt, den Sie mit grep und awk isolieren können:

$ whois example.com | grep 'Registry Expiry Date:'

Registry Expiry Date: 2022-08-13T04:00:00Z

$ whois example.com | grep 'Registry Expiry Date:' | awk '{print $4}'

2022-08-13T04:00:00Z

Verbessern Sie die Lesbarkeit des Datums mit dem Befehl date --date, der einen Datumsstring aus einem Format in ein anderes konvertieren kann:

$ date --date 2022-08-13T04:00:00Z

Sat Aug 13 00:00:00 EDT 2022

$ date --date 2022-08-13T04:00:00Z +'%Y-%m-%d' Jahr-Monat-Tag-Format

2022-08-13

Benutzen Sie eine Befehlssubstitution, um den Datumsstring aus whois an den date-Befehl zu übergeben:

$ echo $(whois example.com | grep 'Registry Expiry Date:' | awk '{print $4}')

2022-08-13T04:00:00Z

$ date \

--date $(whois example.com \

| grep 'Registry Expiry Date:' \

| awk '{print $4}') \

+'%Y-%m-%d'

2022-08-13

Sie haben nun einen Befehl, der eine Registrierungsstelle abfragt und ein Ablaufdatum ausgibt. Stellen Sie ein Skript check-expiry her, gezeigt in Beispiel 9-2, das den vorhergehenden Befehl ausführt und das Ablaufdatum, ein Tab und den Domainnamen ausgibt:

$ ./check-expiry example.com

2022-08-13 example.com

Beispiel 9-2: Das Skript check-expiry

#!/bin/bash

expdate=$(date \

--date $(whois "$1" \

| grep 'Registry Expiry Date:' \

| awk '{print $4}') \

+'%Y-%m-%d')

echo "$expdate $1" # Zwei Werte, getrennt durch ein Tab.

Prüfen Sie mithilfe einer Schleife alle Domains in der Datei domains.txt. Erzeugen Sie ein neues Skript, check-expiry-all, wie in Beispiel 9-3 zu sehen.

Beispiel 9-3: Das Skript check-expiry-all

#!/bin/bash

cat domains.txt | while read domain; do

./check-expiry "$domain"

sleep 5 # Seien Sie freundlich zum Server der Registrierungsstelle.

done

Führen Sie das Skript im Hintergrund aus, da es eine Weile dauern kann, sofern Sie viele Domains haben, und leiten Sie alle Ausgaben (Standardausgabe und Standardfehlerausgabe) in eine Datei um:

$ ./check-expiry-all &> expiry.txt &

Wenn das Skript fertig ist, enthält die Datei expiry.txt die gewünschten Informationen:

$ cat expiry.txt

2022-08-13 example.com

2022-05-26 oreilly.com

2022-09-17 efficientlinux.com

...

Hurra! Aber hören Sie hier nicht auf. Die Datei expiry.txt ist selbst ganz gut strukturiert und eignet sich mit ihren zwei Spalten, die durch Tabs getrennt sind, für eine Weiterverarbeitung. Sortieren Sie zum Beispiel die Datumsangaben und suchen Sie die Domain, die als Nächstes erneuert werden muss:

$ sort -n expiry.txt | head -n1

2022-05-26 oreilly.com

Oder nutzen Sie awk, um Domains zu finden, die abgelaufen sind oder am heutigen Tag ablaufen – das heißt, ihr Ablaufdatum (Feld 1) ist kleiner oder gleich dem heutigen Datum (ausgegeben mit date +%Y-%m-%d):

$ awk "\$1<=\"$(date +%Y-%m-%d)\"" expiry.txt

Einige Hinweise zu dem gerade gezeigten awk-Befehl:

Mit etwas mehr Aufwand könnten Sie Datumsberechnungen in awk durchführen, um Ablaufdaten zum Beispiel zwei Wochen im Voraus zu melden und dann einen regelmäßig ausgeführten Job einzurichten, der das Skript jede Nacht ausführt und Ihnen per E-Mail einen Bericht zuschickt. Experimentieren Sie ruhig ein wenig. Entscheidend ist hier wieder, dass Sie mit nur einer Handvoll von Befehlen ein nützliches Hilfsmittel zusammengebaut haben, das auf einer Textdatei beruht.

Eine Vorwahldatenbank bauen

Das nächste Beispiel nutzt eine Datei mit drei Feldern, die Sie auf vielerlei Art verarbeiten können. Die Datei mit dem Namen areacodes.txt enthält Telefonvorwahlen für die Vereinigten Staaten von Amerika. Sie können sie aus dem Zusatzmaterial (https://efficientlinux.com/examples) zu diesem Buch im Verzeichnis chapter09/build_area_code_database beziehen oder Ihre eigene Datei anlegen, etwa aus der Wikipedia (https://oreil.ly/yz2M1):2

201 NJ Hackensack, Jersey City

202 DC Washington

203 CT New Haven, Stamford

...

989 MI Saginaw

image

Tipp

Ordnen Sie die Felder mit den vorhersehbaren Längen zuerst an, sodass die Spalten ordentlich aussehen. Schauen Sie einmal, wie unordentlich die Datei wirkt, wenn Sie die Städtenamen in die erste Spalte setzen:

 

Hackensack, Jersey City 201 NJ

Washington 202 DC

...

Wenn diese Datei erst einmal an Ort und Stelle ist, können Sie eine Menge damit anfangen. Suchen Sie Vorwahlen mit grep anhand des Staats heraus. Fügen Sie dabei die Option -w hinzu, um nur vollständige Wörter zu erfassen (falls anderer Text zufällig »NJ« enthält):

$ grep -w NJ areacodes.txt

201 NJ Hackensack, Jersey City

551 NJ Hackensack, Jersey City

609 NJ Atlantic City, Trenton, southeast and central west

...

oder schlagen Sie Städte anhand der Vorwahlen:

$ grep -w 202 areacodes.txt

202 DC Washington

oder nach irgendeinem String in der Datei nach:

$ grep Washing areacodes.txt

202 DC Washington

227 MD Silver Spring, Washington suburbs, Frederick

240 MD Silver Spring, Washington suburbs, Frederick

...

Zählen Sie die Vorwahlen mit wc:

$ wc -l areacodes.txt

375 areacodes.txt

Suchen Sie den Staat mit den meisten Vorwahlen (Gewinner ist Kalifornien mit 38):

$ cut -f2 areacodes.txt | sort | uniq -c | sort -nr | head -n1

38 CA

Konvertieren Sie die Datei in das CSV-Format, um sie in eine Tabellenkalkulation zu importieren. Geben Sie das dritte Feld in doppelte Anführungszeichen eingeschlossen aus, um zu verhindern, dass seine Kommata als CSV-Trennzeichen interpretiert werden:

$ awk -F'\t' '{printf "%s,%s,\"%s\"\n", $1, $2, $3}' areacodes.txt \

> areacodes.csv

$ head -n3 areacodes.csv

201,NJ,"Hackensack, Jersey City"

202,DC,"Washington"

203,CT,"New Haven, Stamford"

Fassen Sie alle Vorwahlen für einen bestimmten Staat auf einer einzigen Zeile zusammen:

$ awk '$2~/^NJ$/{ac=ac FS $1} END {print "NJ:" ac}' areacodes.txt

NJ: 201 551 609 732 848 856 862 908 973

oder fassen Sie sie für jeden Staat zusammen, wobei Sie Arrays und for-Schleifen nutzen wie in »Den Duplikate-Detektor verbessern« auf Seite 109:

$ awk '{arr[$2]=arr[$2] " " $1} \

END {for (i in arr) print i ":" arr[i]}' areacodes.txt \

| sort

AB: 403 780

AK: 907

AL: 205 251 256 334 659

...

WY: 307

Verwandeln Sie die gerade gezeigten Befehle je nach Vorliebe in Aliase, Funktionen oder Skripte. Ein einfaches Beispiel ist das Skript areacode in Beispiel 9-4.

Beispiel 9-4: Das Skript areacode

#!/bin/bash

if [ -n "$1" ]; then

grep -iw "$1" areacodes.txt

fi

Das Skript areacode sucht nach ganzen Wörtern in der Datei areacodes.txt, wie etwa der Vorwahl, der Abkürzung des Staats oder dem Namen der Stadt:

$ areacode 617

617 MA Boston

Einen Passwortmanager bauen

Lassen Sie uns in einem letzten, ausführlichen Beispiel Benutzernamen, Passwörter und Notizen in einer verschlüsselten Textdatei abspeichern, und zwar in einem strukturierten Format für eine einfache Abfrage auf der Kommandozeile. Der resultierende Befehl ist ein einfacher Passwortmanager – eine Anwendung, die einem die Last abnimmt, sich viele komplizierte Passwörter merken zu müssen.

image

Warnung

Die Passwortverwaltung ist ein komplexes Thema in der Computersicherheit. Dieses Beispiel erzeugt einen extrem einfachen Passwortmanager zu Übungs- und Lehrzwecken. Verwenden Sie ihn nicht für wichtige Anwendungen mit echtem Sicherheitsbedarf.

Die Passwortdatei mit dem Namen vault besitzt drei Felder, die jeweils durch ein Tab-Zeichen getrennt sind:

Erzeugen Sie die Datei vault und fügen Sie die Daten hinzu. Die Datei ist noch nicht verschlüsselt, sodass Sie momentan nur unechte Passwörter einfügen sollten:

$ touch vault Erzeugt eine leere Datei.

$ chmod 600 vault Setzt die Dateiberechtigungen.

$ emacs vault Bearbeitet die Datei.

$ cat vault

sally fake1 google.com account

ssmith fake2 dropbox.com account for work

s999 fake3 Bank of America account, bankofamerica.com

smith2 fake4 My blog at wordpress.org

birdy fake5 dropbox.com account for home

Speichern Sie vault an einem bekannten Ort:

$ mkdir ~/etc

$ mv vault ~/etc

Die Idee ist, ein Pattern-Matching-Programm wie grep oder awk zu benutzen, um Zeilen auszugeben, die einem bestimmten String entsprechen. Diese einfache, aber leistungsstarke Technik kann jeden Teil einer Zeile erfassen, nicht nur Benutzernamen oder Websites, zum Beispiel:

$ cd ~/etc

$ grep sally vault Erfasst einen Benutzernamen.

sally fake1 google.com account

$ grep work vault Erfasst die Anmerkungen.

ssmith fake2 dropbox.com account for work

$ grep drop vault Erfasst mehrere Zeilen.

ssmith fake2 dropbox.com account for work

birdy fake5 dropbox.com account for home

Halten Sie diese einfache Funktionalität in einem Skript fest. Anschließend wollen wir sie Schritt für Schritt verbessern und die Datei vault am Ende verschlüsseln. Nennen Sie das Skript pman für Passwort Manager und erzeugen Sie die einfache Version aus Beispiel 9-5.

Beispiel 9-5: pman, Version 1: So einfach es geht

#!/bin/bash

# Einfach nur passende Zeilen ausgeben.

grep "$1" $HOME/etc/vault

Speichern Sie das Skript in Ihrem Suchpfad:

$ chmod 700 pman

$ mv pman ~/bin

Probieren Sie das Skript aus:

$ pman goog

sally fake1 google.com account

$ pman account

sally fake1 google.com account

ssmith fake2 dropbox.com account for work

s999 fake3 Bank of America account, bankofamerica.com

birdy fake5 dropbox.com account for home

$ pman facebook (erzeugt keine Ausgabe)

Die nächste Version in Beispiel 9-6 enthält dann schon eine kleine Fehlerüberprüfung und einige sinnvolle Variablennamen.

Beispiel 9-6: pman, Version 2: Fügt eine Fehlerüberprüfung hinzu

#!/bin/bash

# Festhalten des Skriptnamens.

# $0 ist der Pfad auf das Skript, und basename gibt den endgültigen Dateinamen aus.

PROGRAM=$(basename $0)

# Ort der Passwortdatei vault.

DATABASE=$HOME/etc/vault

# Sicherstellen, dass wenigstens ein Argument an das Skript übergeben wurde.

# Der Ausdruck >&2 leitet echo auf die Standardfehlerausgabe um

# statt auf die Standardausgabe.

if [ $# -ne 1 ]; then

>&2 echo "$PROGRAM: Passwörter nach String suchen"

>&2 echo "Verwendung: $PROGRAM String"

exit 1

fi

# Speichert das erste Argument in einer freundlichen, benannten Variablen.

searchstring="$1"

# Durchsucht vault und gibt eine Fehlermeldung aus, falls nichts passt.

grep "$searchstring" "$DATABASE"

if [ $? -ne 0 ]; then

>&2 echo "$PROGRAM: keine Treffer für '$searchstring'"

exit 1

fi

Führen Sie das Skript aus:

$ pman

pman: Passwörter nach String suchen

Verwendung: pman String

$ pman smith

ssmith fake2 dropbox.com account for work

smith2 fake4 My blog at wordpress.org

$ pman xyzzy

pman: keine Treffer für 'xyzzy'

Ein Nachteil dieser Technik besteht darin, dass sie nicht skaliert. Wenn vault Hunderte von Zeilen enthält und grep 63 von ihnen erfasst und ausgibt, müssen Sie diese »durch scharfes Hinschauen« durchsuchen, um das Passwort zu finden, das Sie brauchen. Verbessern Sie das Skript, indem Sie einen einmaligen Schlüssel (einen String) zu jeder Zeile in der dritten Spalte hinzufügen und pman so anpassen, dass es zuerst nach diesem einmaligen Schlüssel sucht. Die Datei vault sieht nun so aus (die dritte Spalte wurde fett gedruckt):

sally fake1 google google.com account

ssmith fake2 dropbox dropbox.com account for work

s999 fake3 bank Bank of America account, bankofamerica.com

smith2 fake4 blog My blog at wordpress.org

birdy fake5 dropbox2 dropbox.com account for home

Beispiel 9-7 zeigt das aktualisierte Skript, das awk anstelle von grep einsetzt. Außerdem verwendet es eine Befehlssubstitution, um die Ausgabe zu erfassen und zu überprüfen, ob sie leer ist (der Test -z bedeutet »String der Länge null«). Falls Sie übrigens nach einem Schlüssel suchen, der in vault nicht existiert, fällt pman in sein ursprüngliches Verhalten zurück und gibt alle Zeilen aus, die dem Suchstring entsprechen.

Beispiel 9-7: pman, Version 3: Zuerst wird nach dem Schlüssel in der dritten Spalte gesucht

#!/bin/bash

PROGRAM=$(basename $0)

DATABASE=$HOME/etc/vault

if [ $# -ne 1 ]; then

>&2 echo "$PROGRAM: Passwörter suchen"

>&2 echo "Verwendung: $PROGRAM String"

exit 1

fi

searchstring="$1"

# Suchen nach exakten Treffern in der dritten Spalte.

match=$(awk '$3~/^'$searchstring'$/' "$DATABASE")

# Falls der Suchstring keinem Schlüssel entspricht, werden alle Treffer gesucht.

if [ -z "$match" ]; then

match=$(awk "/$searchstring/" "$DATABASE")

fi

# Falls es immer noch keinen Treffer gibt, wird eine Fehlermeldung ausgegeben,

# und das Skript wird beendet.

if [ -z "$match" ]; then

>&2 echo "$PROGRAM: keine Treffer für '$searchstring'"

exit 1

fi

# Ausgeben des Treffers.

echo "$match"

Führen Sie das Skript aus:

$ pman dropbox

ssmith fake2 dropbox dropbox.com account for work

$ pman drop

ssmith fake2 dropbox dropbox.com account for work

birdy fake5 dropbox2 dropbox.com account for home

Die einfache Textdatei vault ist ein Sicherheitsrisiko. Verschlüsseln Sie sie deshalb mit dem Linux-Standardverschlüsselungsprogramm GnuPG, das als gpg aufgerufen wird. Falls Sie GnuPG bereits für den Einsatz eingerichtet haben, ist das großartig. Falls nicht, richten Sie es mit dem folgenden Befehl ein, bei dem Sie Ihre E-Mail-Adresse angeben:3

$ gpg --quick-generate-key Ihre_E-Mail-Adresse default default never

Sie werden nach einer Passphrase für den Schlüssel gefragt (zweimal). Geben Sie eine starke Passphrase an. Wenn gpg fertig ist, können Sie die Passwortdatei mit einer Public-Key-Verschlüsselung (Verschlüsselung mit öffentlichem Schlüssel) verschlüsseln. Dabei entsteht die Datei vault.gpg:

$ cd ~/etc

$ gpg -e -r Ihre_E-Mail-Adresse vault

$ ls vault*

vault vault.gpg

Entschlüsseln Sie als Test die Datei vault.gpg auf die Standardausgabe:4

$ gpg -d -q vault.gpg

Passphrase: xxxxxxxx

sally fake1 google google.com account

ssmith fake2 dropbox dropbox.com account for work

...

Aktualisieren Sie dann Ihr Skript, sodass es die verschlüsselte Datei vault.gpg statt der einfachen Textdatei vault verwendet. Das bedeutet ein Entschlüsseln von vault.gpg auf die Standardausgabe und ein Weiterleiten seines Inhalts in einer Pipeline an awk für einen Vergleich, wie in Beispiel 9-8 gezeigt.

Beispiel 9-8: pman, Version 4: Verwenden einer verschlüsselten vault-Datei

#!/bin/bash

PROGRAM=$(basename $0)

# Use the encrypted file.

DATABASE=$HOME/etc/vault.gpg

if [ $# -ne 1 ]; then

>&2 echo "$PROGRAM: Passwörter suchen"

>&2 echo "Verwendung: $PROGRAM String"

exit 1

fi

searchstring="$1"

# Speichern des entschlüsselten Texts in einer Variablen.

decrypted=$(gpg -d -q "$DATABASE")

# Suchen nach exakten Treffern in der dritten Spalte.

match=$(echo "$decrypted" | awk '$3~/^'$searchstring'$/')

# Falls der Suchstring nicht zu einem Schlüssel passt, werden alle Treffer gesucht.

if [ -z "$match" ]; then

match=$(echo "$decrypted" | awk "/$searchstring/")

fi

# Falls es immer noch keinen Treffer gibt, wird eine Fehlermeldung ausgegeben,

# und das Skript wird beendet.

if [ -z "$match" ]; then

>&2 echo "$PROGRAM: keine Treffer für '$searchstring'"

exit 1

fi

# Ausgeben des Treffers

echo "$match"

Das Skript zeigt nun die Passwörter aus der verschlüsselten Datei an:

$ pman dropbox

Passphrase: xxxxxxxx

ssmith fake2 dropbox dropbox.com account for work

$ pman drop

Passphrase: xxxxxxxx

ssmith fake2 dropbox dropbox.com account for work

birdy fake5 dropbox2 dropbox.com account for home

Es sind nun alle Teile für Ihren Passwortmanager vorhanden. Ein paar letzte Schritte fehlen aber noch:

decrypted=$(gpg -d -q "$DATABASE" | grep -v '^#')

Das Ausgeben von Passwörtern auf der Standardausgabe ist aus Sicht der Sicherheit keine so großartige Idee. »Den Passwortmanager verbessern« auf Seite 208 passt das Skript so an, dass Passwörter kopiert und eingefügt statt ausgegeben werden.

Verschlüsselte Dateien direkt bearbeiten

Um eine verschlüsselte Datei zu verändern, wäre die direkteste, lästigste und unsicherste Methode das Entschlüsseln, Bearbeiten und erneute Verschlüsseln der Datei.

$ cd ~/etc

$ gpg vault.gpg Entschlüsseln.

Passphrase: xxxxxxxx

$ emacs vault Benutzen Sie Ihren Lieblingstexteditor.

$ gpg -e -r Ihre_E-Mail-Adresse vault Selbst wieder verschlüsseln.

$ rm vault

Zum einfacheren Bearbeiten der Datei vault.gpg besitzen sowohl emacs als auch vim Modi zum Bearbeiten GnuPG-verschlüsselter Dateien. Fügen Sie zuerst diese Zeile einer bash-Konfigurationsdatei hinzu und laden Sie sie in allen damit verbundenen Shells neu:

export GPG_TTY=$(tty)

Für emacs richten Sie das EasyPG-Paket ein, das eingebaut ist. Fügen Sie die folgenden Zeilen der Konfigurationsdatei $HOME/.emacs hinzu und starten Sie emacs neu. Ersetzen Sie den String GnuPG ID here durch die E-Mail-Adresse, die mit Ihrem Schlüssel verknüpft ist, also etwa smith@example.com:

(load-library "pinentry")

(setq epa-pinentry-mode 'loopback)

(setq epa-file-encrypt-to "GnuPG ID here")

(pinentry-start)

Bearbeiten Sie dann die verschlüsselte Datei. emacs fragt Sie nach Ihrer Passphrase und entschlüsselt die Datei zur Bearbeitung in einen Puffer. Beim Speichern verschlüsselt emacs den Inhalt des Puffers.

Für vim nutzen Sie das Plug-in vim-gnupg (https://oreil.ly/mnwYc) und fügen diese Zeilen in die Konfigurationsdatei $HOME/.vimrc ein:

let g:GPGPreferArmor=1

let g:GPGDefaultRecipients=["GnuPG ID here"]

Sie sollten sich überlegen, einen Alias anzulegen, um die Passwortdatei bequem bearbeiten zu können. Verwenden Sie dazu die Technik aus dem Abschnitt »Häufig bearbeitete Dateien mit einem Alias bearbeiten« auf Seite 72:

alias pwedit="$EDITOR $HOME/etc/vault.gpg"

Zusammenfassung

Dateipfade, Domainnamen, Vorwahlen und Login-Daten sind nur einige Beispiele für Daten, die gut in einer strukturierten Textdatei funktionieren. Wie wäre es mit:

Auf diese Weise können Sie sich ein Ökosystem aus zeitsparenden Befehlen zusammenstellen, die für Sie persönlich bedeutsam oder für Ihre Arbeit wichtig sind. Ihrer Fantasie sind hier keine Grenzen gesetzt.