linux_ws2122/chapters/08-textprocessing.tex

650 lines
27 KiB
TeX
Raw Normal View History

2021-10-13 17:25:16 +00:00
% <#>---------------------------------------------------------------------------<#>
\section{Text Processing}%
\label{sec:text-processing}
Unter Text Processing versteht man das Einlesen, Filtern, Modifizieren und
Umleiten von Dateien oder Streams. Dieses Kapitel behandelt daher die gängigen
Programme, welche ein Linux Systemadministrator oder Entwickler als sein
Handwerkszeug betrachten würde.
Bevor jedoch die Programme erklärt werden, sind die Grundlagen von Umleitungen
und Befehlsverkettungen notwendig.
% <#>---------------------------------------------------------------------------<#>
\subsection{Umleitungen und Befehlsverkettungen}
\label{sec:text-processing.redirections}
Nachfolgend wird der Begriff Befehlsverkettung oder auch Kommandoverkettung
anhand von Syntax Beispielen näher erläutert.
\begin{itemize}[label={},itemsep=0pt]
\item \bashinline{cmd1 | cmd2} \\
Die \textit{Pipe} verbindet die Standard-Ausgabe (\textit{stdout}) eines
Programms mit der Eingabe (\textit{stdin}) eines anderen Programms.
\item \bashinline{cmd1 ; cmd2} \\
Führt erst das Programm \textit{cmd1} aus und anschließend das Programm
\textit{cmd2} - ganz egal, ob Programm \textit{cmd1} einen Fehler geworfen hat
oder nicht.
\item \bashinline{cmd1 && cmd2} \\
Jedes Programm übergibt zuletzt an das Betriebssystem einen sogenannten
Returncode (auch Errorlevel oder Exitlevel genannt). Bei einem Returncode 0
ist alles in Ordnung, bei einem Wert ungleich 0 trat ein Fehler auf. In diesem
Fall der Kombination wird \textit{cmd2} nur ausgeführt, wenn der Returncode
von \textit{cmd1} gleich 0 war.
\item \bashinline{cmd1 || cmd2} \\
Dies ist genau das Gegenteil. Das Programm \textit{cmd2} wird nur ausgeführt,
wenn der Returncode von \textit{cmd1} ungleich 0 war.
\item \bashinline{cmd1 & cmd2} \\
Das \&-Zeichen am Ende einer Befehlszeile veranlasst, dass das Programm in den
Hintergrund verlagert wird und ihnen die Prompt wieder zur Verfügung gestellt
wird. Dadurch ist es möglich über eine Session mehrere Befehle auszuführen.
Nichts anderes passiert hier. Das Programm \textit{cmd1} wird in den
Hintergrund verlagert. Das Programm \textit{cmd2} wird nach der Verlagerung
ausgeführt.
\end{itemize}
Neben den Befehlsverkettungen gibt es auch noch Umleitungen der Ein- und
Ausgabekanäle (\textit{stdin}, \textit{stdout}, \textit{stderr}).
\begin{itemize}[label={},itemsep=0pt]
\item \bashinline{cmd > /tmp/output.log} oder auch \bashinline{cmd 1> /tmp/output.log} \\
Die Standard-Ausgabe \textit{stdout} wird in die Datei \textit{/tmp/output.log}
geschrieben.
\item \bashinline{cmd >> /tmp/output.log} \\
Die Standard-Ausgabe \textit{stdout} wird an die Datei \textit{/tmp/output.log}
angehängt.
\item \bashinline{cmd 2> /tmp/output.log} \\
Die Standard-Fehlerausgabe \textit{stderr} wird an die Datei \textit{/tmp/error.log}
geschrieben.
\item \bashinline{cmd &> /tmp/complete.log} \\
Die Standard-Ausgabe \textit{stdout} und die -Fehlerausgabe \textit{stderr}
werden in die Datei \textit{/tmp/complete.log} geschrieben. geschrieben.
\item \bashinline{cmd < /tmp/input} \\
Die Standard-Eingabe \textit{stdin} erfolgt nicht durch die Tastatur, sondern
aus der Datei \textit{/tmp/input}.
\item \bashinline{cmd << EOT} \\
Die Standard-Eingabe wird nur bis zu der frei wählbaren Zeichenfolge EOT
gelesen und anschließend beendet. Man nennt diese Konstruktion HERE-Dokument.
Dazu später mehr.
\end{itemize}
% #>-----------------------------------------------------------------------------<#
\subsection{cat}
\label{sec:text-processing.cat}
Das Programm \textit{cat} konkateniert mehrere Dateien und schreibt diese auf
die Standard-Ausgabe \textit{stdout}. Nachfolgend einige Beispiele.
Die Ausgabe der Datei \textit{/etc/passwd}. Die Zeilen wurden gekürzt.
\begin{bashcode}
$ cat /etc/passwd
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
...
\end{bashcode}
Die Datei \textit{/etc/passwd} und \textit{/etc/group} werden zusammengeführt
und nach \textit{/tmp/user-and-groups} umgeleitet.
\begin{bashcode}
$ cat /etc/passwd /etc/group > /tmp/user-and-groups
\end{bashcode}
In dem folgenden Beispiel wird ein HERE-Dokument genutzt, um die Eingabe in eine
Datei umzuleiten.
\begin{bashcode}
$ cat > /tmp/my-here-document <<EOF
Hello,
this is my first here document.
EOF
\end{bashcode}
% #>-----------------------------------------------------------------------------<#
\subsection{wc}
\label{sec:text-processing.wc}
Mit Programm \textit{wc} lassen sich die Wörter, Zeilen oder Buchstaben von
einer Standard-Eingabe oder Dateien zählen. Das folgende Beispiel addiert die
Zeilen der Datei \textit{/etc/passwd} und \textit{/etc/group}.
\begin{bashcode}
$ wc --lines /etc/passwd /etc/group
42 /etc/passwd
62 /etc/group
104 total
\end{bashcode}
\begin{itemize}[label={},itemsep=0pt]
\item \textbf{Aufgabe 1a:} Zählen Sie die Buchstaben und Wörter der Datei
\textit{/etc/services}. Definieren Sie die Quelle einmal per Pfad zur Datei und
ein anderes mal per Befehlsverkettung - \textit{Pipe}.
\end{itemize}
% #>-----------------------------------------------------------------------------<#
\subsection{date}
\label{sec:text-processing.date}
Mit dem Programm \textit{date} können Daten, basierend auf dem aktuellen oder
einem vordefinierten Datum, berechnet werden. Zusätzlich kann das Format anhand
eines Formatstrings bestimmt werden. Die einzelnen Formatattribute sind in der
Dokumentation von \textit{date} beschrieben. Schauen Sie sich für weitere
Informationen die Dokumentation an: \textit{man date}.
Nachfolgend einige Beispiele ein Datum basierend auf den Anforderungen zu
berechnen.
\begin{bashcode}
$ # Gibt das aktuelle Datum und die Uhrzeit aus
$ date '+%Y-%m-%d %H:%M:%S'
2020-11-14 16:35:07
$ # Gibt das Datum des letzten Montags aus
$ date --date 'last monday' '+%Y-%m-%d'
2020-11-09
$ # Berechnet das Datum des letzten Montags vor zwei Wochen
$ date --date 'last monday + 14 day ago' '+%Y-%m-%d'
2020-10-26
$ # Gibt das Datum des kommenden Montags aus
$ date --date 'next monday' '+%Y-%m-%d'
2020-11-16
$ # Berechnet das Datum des nächsten Montags in zwei Wochen
$ date --date 'next monday + 14 day' '+%Y-%m-%d'
2020-11-30
$ # Berechnet das Datum des letzten Montags in zwei Wochen
$ date --date 'last monday + 14 day' '+%Y-%m-%d'
2020-11-23
$ # Gibt das Datum und die Uhrzeit für die Zeitzone
$ # America/New_York aus
$ TZ=America/New_York date '+%Y-%m-%d %H:%M:%S %z'
2020-11-14 10:41:39 -0500
$ # Gibt das Datum, die Uhrzeit, Kalenderwoche und den Tag aus
$ date '+%A, %Y-%m-%d %H:%M:%S, KW %V'
Samstag, 2020-11-14 16:42:44, KW 46
$ # Addiert zwei Tage auf den 3. Oktober 2020
$ date --date '03 OCT 2020 + 2 day' '+%Y-%m-%d'
2020-10-05
$ # Addiert zwei Tage auf den 3. Oktober vor 5 Jahren.
$ date --date "03 OCT $(date --date '5 year ago' '+%Y') + 2 day" '+%Y-%m-%d'
2015-10-05
\end{bashcode}
\begin{itemize}[label={},itemsep=0pt]
\item \textbf{Aufgabe 2a:} Berechnen Sie das Datum des kommenden Sonntags.
Verwenden Sie folgendes Format: \textit{2020-05-31}. Erkundigen Sie sich in
der Dokumentation über weitere Formatattribute.
\item \textbf{Aufgabe 2b:} Berechnen Sie das Datum des letzten Dienstags vor
einem Monat. Verwenden Sie folgendes Format: \textit{Dienstag, 2020-05-31, KW
22}
\item \textbf{Aufgabe 2c:} Berechnen Sie das Datum des Mittwochs der 44 KW vor
12 Jahren für die Zeitzone Africa/Mogadishu. \textit{KW 22, Dienstag, 15
November 2008}.
\end{itemize}
% #>-----------------------------------------------------------------------------<#
\subsection{sed}
\label{sec:text-processing.sed}
Das Programm \textit{sed} ist ein stream Editor, welcher zum Filtern,
Modifizieren und Löschen von Textstreams geeignet ist.
Das Programm bietet einen sehr großen Umfang an zusätzlichen Optionen an. Nicht
alle Optionen werden behandelt. Der Fokus liegt ausschließlich auf den gängigen
Optionen, welche öfters in der Praxis verwendet werden. Weitere Informationen zu
allen möglichen Optionen können über die Dokumentation abgerufen werden:
\textit{man sed}.
In dem folgenden Beispiel wird der Inhalt der Datei \textit{/etc/passwd} durch
\textit{sed} modifiziert und auf der Standard-Ausgabe ausgegeben. Die
Zeichenkette \textit{root} wird durch \textit{nobody} ersetzt. Vergleichen Sie
die ersten Zeilen der Ausgabe mit der ursprünglichen Datei.
Für komplexere Kriterien können Reguläre Ausdrücke verwendet werden. Falls Ihnen
Reguläre Ausdrücke fremd sind, informieren Sie sich bitte im Internet über
Reguläre Ausdrücke.
\begin{bashcode}
$ sed 's/root/nobody/g' /etc/passwd
nobody:x:0:0:nobody:/nobody:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
...
\end{bashcode}
Möchte man Reguläre Ausdrücke verwenden kann das Flag bzw. die Option
\textit{-e, --expression} verwendet werden. Allerdings hat diese Option den
Nachteil, dass Zeichen für Reguläre Ausdrücke maskiert werden müssen. Anders ist
dies bei dem Flag \textit{-E, -r, --regexp-extended}. Die Syntax der Regulären
Ausdrücke ist identisch zu der Syntax von \textit{grep} mit dem Flag
\textit{--perl-regexp} und sollte daher bevorzugt verwendet werden.
In dem folgenden Beispiel wird die Ausgabe umgeleitet in eine Datei.
\begin{bashcode}
$ sed 's/root/nobody/g' /etc/passwd > /tmp/passwd_modified
\end{bashcode}
Möchte man die selbe Datei anhand von Kriterien Filtern und Modifizieren bietet
sich die Option bzw. das Flag \textit{-i, --in-place} an. Die Änderungen werden
direkt in die Quelldatei übernommen.
In dem folgenden Beispiel wird die Zeile 5 bis 10 und die Zeile 12 aus der Datei
\textit{/tmp/passwd\_modified} entfernt.
\begin{bashcode}
$ sed --in-place '5,10d;12d' /tmp/passwd_modified
\end{bashcode}
Sicherlich fragen Sie sich nun was die Zeichen \textit{s/}, \textit{d} oder
\textit{/g} zu Beginn oder zum Ende der Bedingung bedeuten. Mit \textit{s/} wird
\textit{sed} mitgeteilt, dass eine Zeichenkette durch eine andere ersetzt werden
soll. Die Zeichen \textit{/g} bedeuten, dass der Ausdruck auf alle Treffer
angewendet werden soll. Eine andere Bedeutung für das \textit{g} ist
\glqq{}global\grqq{}. Nun, wofür \textit{d} steht können Sie sich nun schon
denken - \textit{delete}.
In dem folgenden Beispiel werden alle Zeilen aus der Datei \textit{/etc/passwd}
und \textit{/etc/group} entfernt, die mit der Zeichenkette \textit{root} beginnen.
\begin{bashcode}
$ sed --regexp-extended '/^root/d' /etc/passwd /etc/group
\end{bashcode}
Ein invert ist ebenfalls möglich. In dem folgenden Beispiel werden alle Zeilen
außer die fünfte Zeile entfernt.
\begin{bashcode}
$ sed '5!d' /etc/passwd
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
\end{bashcode}
Mithilfe von Gruppen, welche Reguläre Ausdrücke bereitstellen, können komplexe
Zeichenketten gruppiert und neu angeordnet werden. In dem folgenden Beispiel
wird die letzte Spalte der Datei \textit{/etc/passwd} an die erste Stelle
verschoben.
\begin{bashcode}
$ sed --regexp-extended 's/^(.*):(.*):(.*):(.*):(.*):(.*):(.*)$/\7:\1:\2:\3:\4:\5:\6/g' /etc/passwd
/bin/bash:root:x:0:0:root:/root
/sbin/nologin:bin:x:1:1:bin:/bin
/sbin/nologin:daemon:x:2:2:daemon:/sbin
...
\end{bashcode}
\begin{itemize}[label={},itemsep=0pt]
\item \textbf{Aufgabe 3a:} Löschen Sie alle Zeilen, welche als Shell
\textit{/sbin/nologin} verwenden. Geben Sie die Ausgabe auf der
Standard-Ausgabe aus.
\item \textbf{Aufgabe 3b:} Löschen Sie alle Zeilen aus der Datei
\textit{/etc/passwd} und \textit{/etc/group}, welche mit \textit{root} oder
\textit{system} beginnen. Zählen Sie anschließend die Zeilen. Nutzen Sie dazu
die Umleitung von einem Programm zu einem anderen per \textit{Pipe}.
\item \textbf{Aufgabe 3c:} Bereinigen Sie die Datei \textit{/etc/services}.
Löschen Sie Kommentarzeilen und Zeilen welche leer sind. Manche Zeilen
enthalten am Ende Kommentare, entfernen Sie diese.
\item \textbf{Aufgabe 3d:} Suchen Sie in der Datei \textit{/etc/services} nach
Zeilen, welche mit dem Port 5 beginnen und vertauschen Sie den Port mit dem
Protokoll.
\end{itemize}
% #>-----------------------------------------------------------------------------<#
\subsection{grep}
\label{sec:text-processing.grep}
Das Programm \textit{grep} wird primär dazu verwendet Dateien oder Streams nach
Schlagwörtern zu filtern. Ähnlich wie bei \textit{sed} können auch hier nicht
alle Optionen oder Flags ausführlich in Detail behandelt werden. Aus diesem
Grund liegt der Fokus erneut auf den gängigen Optionen bzw. Flags aus der Praxis.
Das nachfolgende Beispiel filtert die Datei \textit{/etc/passwd} und
\textit{/etc/group} nach der Zeichenkette \textit{root} und hebt diese farblich
hervor.
\begin{bashcode}
$ grep --color root /etc/passwd /etc/group
/etc/passwd:root:x:0:0:root:/root:/bin/bash
/etc/passwd:operator:x:11:0:operator:/root:/sbin/nologin
/etc/group:root:x:0:
\end{bashcode}
In dem folgenden Beispiel wird ein Regulärer Ausdruck verwendet, um nach der
Zeichenkette \textit{root} oder \textit{sophie}, beginnend für jede Zeile, in
der Datei \textit{/etc/passwd} zu suchen.
\begin{bashcode}
$ grep --color --perl-regexp '^(root|sophie)' /etc/passwd
root:x:0:0:root:/root:/bin/bash
sophie:x:2003:100:Sophie Becker:/home/sophie:/bin/bash
\end{bashcode}
Manchmal möchte man allerdings nur die gefundenen Schlagwörter der Zeile
weiterverarbeiten. Dazu stellt das Programm \textit{grep} die Option
\textit{--only-matching} bereit. Nachfolgend wird der gleiche Befehl erneut
ausgeführt mit der Option.
\begin{bashcode}
$ grep --color --only-matching --perl-regexp '^(root|sophie)' /etc/passwd
root
sophie
\end{bashcode}
Sicherlicht kennen einige die folgende Situation. Unterhalb eines Verzeichnisses
liegen sehr viele Dokumente. Eines von diesen enthält einen Begriff, welcher
dort erklärt oder beschrieben wird. In dem nachfolgenden Beispiel werden alle
Dateien rekursiv geöffnet und nach den Schlagwörtern durchsucht. In der Ausgabe
ist der Dateiname und die Zeile als auch das Schlagwort enthalten, welches den
Treffer ausgelöst hat.
\begin{bashcode}
$ grep --color --recursive --perl-regexp '(btrfs|subvolume|snapshot)' ~/workspace/linux_ws2021
~/workspace/linux_ws2021/referenzen/bibliothek.bib: @online{fedora33-btrfs-default,
~/workspace/linux_ws2021/referenzen/bibliothek.bib: url = {https://fedoramagazine.org/btrfs-coming-to-fedora-33/},
~/workspace/linux_ws2021/referenzen/bibliothek.bib: @online{was-ist-btrfs,
\end{bashcode}
\begin{itemize}[label={},itemsep=0pt]
\item \textbf{Aufgabe 4a:} Suchen Sie alle Zeilen aus der Datei
\textit{/etc/passwd} und \textit{/etc/group} herraus, welche nicht die
Zeichenketten \textit{root} und ihren aktuellen Benutzernamen enthalten.
Nutzen Sie zur Ermittlung ihres Benutzernamens die Umgebungsvariable
\textit{USER}.
\item \textbf{Aufgabe 4b:} Filtern Sie aus der Datei \textit{/etc/passwd} alle
Zeilen, welche mit \textit{system} beginnen. Leiten Sie die Ausgabe weiter an
\textit{sed}, um die Zeichenkette \textit{/sbin/nologin} durch
\textit{/bin/bash} zu ersetzen.
Kleiner Tipp: Statt \textit{/} können auch andere Zeichen, wie
\textit{\#} oder \textit{@}, als Trennung zwischen dem Such- und
Ersetzungspattern verwendet werden. Dies kann hilfreich sein, um ein escapen
von Verzeichnispfaden zu vermeiden.
\item \textbf{Aufgabe 4c:} Zählen Sie alle Zeilen der Datei
\textit{/etc/services}, welche das Protokoll tcp verweden.
\item \textbf{Aufgabe 4d:} Ermitteln Sie, ob in der Datei
\textit{/etc/services} noch andere Protokolle statt tcp und udp verwendet
werden. Gegen Sie die unbekannten Protokolle auf der Standard-Ausgabe aus.
\end{itemize}
% #>-----------------------------------------------------------------------------<#
\subsection{uniq}
\label{sec:text-processing.uniq}
Manchmal möchte man Treffer zählen. Beispielsweise wird eine Datei durchsucht
nach einem Schlagwort und man stellt sich die Frage, wie oft das Schlagwort in
der Datei verwendet wurde. Um diese Frage zu beantworten ist unter anderem
\textit{uniq} sehr helfreich, denn es kann Mehrfachvorkommen von Zeichenketten
vermeiden und ggfls. zählen.
\begin{itemize}[label={},itemsep=0pt]
\item \textbf{Aufgabe 5a:} Zählen Sie wie oft in der Datei
\textit{/etc/passwd} die Zeichkette \textit{root} verwendet wurde. Eventuell
ist es hilfreich \textit{grep} hinzuzuziehen.
\item \textbf{Aufgabe 5b:} Suchen Sie in der Datei \textit{/etc/services} nach
Diensten die nur einen Port bzw Protokoll benötigen. Nutzen Sie auch hier die
bereits kennengelernten Programme.
\end{itemize}
% #>-----------------------------------------------------------------------------<#
\subsection{column}
\label{sec:text-processing.column}
Manchmal erhält man Dateien, welche für das menschliche Auge unübersichtlich
formatiert sind. Das Programm \textit{column} kann hier insoweit Abhilfe
schaffen, dass die Eingabe in mehrere Spalten formatiert wird.
In dem folgenden Beispiel wird die Datei \textit{/etc/passwd} eingelesen, die
Spalten anhand des Trenners \textit{:} getrennt und als Tabelle auf der
Standard-Ausgabe ausgegeben.
\begin{bashcode}
$ column --separator ":" --table /etc/passwd
\end{bashcode}
Es können auch zusätzlich Kopfzeilen hinzugefügt werden. Im nächsten Beispiel
ist dies genau der Fall. Die Kopfzeile wird definiert und mit ausgegeben.
\begin{bashcode}
$ column --separator ":" \
--table \
--table-column "user,password,id,gid,comment,homedir,shell" /etc/passwd
\end{bashcode}
Wahrscheinlich wird das Programm \textit{column} häufiger verwendet, um Dateien
in json umzuwandeln. In dem folgenden Beispiel wird die Datei in json
umgewandelt.
\begin{bashcode}
$ column --separator ":" \
--table \
--table-name "passwd" \
--table-column "user,password,id,gid,comment,homedir,shell" \
--json /etc/passwd
\end{bashcode}
\begin{itemize}[label={},itemsep=0pt]
\item \textbf{Aufgabe 6a:} Filtern Sie alle Zeilen aus der Datei
\textit{/etc/services}, welches das UDP-Protokoll verwenden und mit dem
Buchstaben \textit{n} beginnen. Teilen Sie die zweite Spalte auf in Port und
Protokoll. Die Ausgabe muss in ein json umgewandelt werden. Der Tabellenname
ist \textit{services} und die Spalten sollen \textit{service}, \textit{port}
und \textit{protocol} heißen. Speichern Sie die Ausgabe in die Datei
\textit{/tmp/services.json} ab.
\end{itemize}
% #>-----------------------------------------------------------------------------<#
\subsection{head und tail}
\label{sec:text-processing.head-und-tail}
Mit den bisherigen Programmen können wir schon einiges erreichen, doch fehlt es
an der Möglichkeiten die Ausgabe zu beschränken. Mit den Programmen
\textit{head} und \textit{tail} kann genau das erreicht werden. Beide Programme
schreiben auf die Standard-Ausgabe \textit{stdout}, wenn keine Umleitung
definiert ist.
Mit dem Programm \textit{head} können n Zeilen, beginnend ab der ersten Zeile,
ausgegeben werden. Das Programm \textit{tail} dagegen zählt ab der letzten
Zeile.
In dem nachfolgenden Beispiel werden die ersten drei Zeilen der Datei
\textit{/etc/passwd} ausgegeben.
\begin{bashcode}
$ head --lines=3 /etc/passwd
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
\end{bashcode}
Das nächste Beispiel gibt die letzten drei Zeilen aus.
\begin{bashcode}
$ tail --lines=3 /etc/passwd
tobias:x:2005:100:Tobias Moretti:/home/tobias:/bin/bash
lisa:x:2006:100:Lisa Meerkamp:/home/lisa:/bin/bash
manfred:x:2007:100:Manfred Krupp:/home/manfred:/bin/bash
\end{bashcode}
Zusätzlich besitzt \textit{tail} die Möglichkeit alle Zeilen, beginnend ab der
n-ten Zeile, auszugeben. In dem folgenden Beispiel wird die Datei
\textit{/etc/passwd} ab der 35en Zeile ausgegeben. Die Ausgabe ist zusätzlich
noch einmal gekürzt.
\begin{bashcode}
$ tail --lines=+35 /etc/passwd
hugo:x:2000:100:Hugo McKinnock:/home/hugo:/bin/bash
hans:x:2001:100:Hans Rakinzsky:/home/hans:/bin/bash
marie:x:2002:100:Marie Haseloff:/home/marie:/bin/bash
sophie:x:2003:100:Sophie Becker:/home/sophie:/bin/bash
\end{bashcode}
Manche Programme, insbesondere Dienste, schreibe Log-Dateien, welche gerade für
Administratoren oder Entwickler interessant sein können. Um ausschließlich die
neusten Zeilen aus einer Logdatei zu erhalten bietet das Programm \textit{tail}
die Option \textit{-f, --follow} an. Dadurch wird veranlasst, dass stets neu
hinzukommende Zeilen direkt ausgegeben werden, ohne das das Programm erneut
gestartet werden muss.
% #>-----------------------------------------------------------------------------<#
\subsection{cut}
\label{sec:text-processing.cut}
Das Programm \textit{cut} ist genau das richtige Programm, um Spalten, getrennt
durch einen Trenner, auszuwählen. In dem nachfolgenden Beispiel wird
ausschließlich die erste Zeile der Datei \textit{/etc/passwd} ausgegeben.
\begin{bashcode}
$ cut --delimiter=":" --fields=1 /etc/passwd
root
bin
daemon
...
\end{bashcode}
Es ist auch möglich mehrere Spalten zu selektieren. In dem folgenden Beispiel
wird ausschließlich die zweite bis vierte und siebte Spalte selektiert.
\begin{bashcode}
$ cut --delimiter=":" --fields=2-4,7 /etc/passwd
x:0:0:/bin/bash
x:1:1:/sbin/nologin
x:2:2:/sbin/nologin
...
\end{bashcode}
\begin{itemize}[label={},itemsep=0pt]
\item \textbf{Aufgabe 7a:} Nutzen Sie die bereits bekannten Programme, um die
Datei \textit{/etc/services} zu bereinigen. Lassen Sie sich anschließend nur
die Spalte mit den Ports und Protokollen anzeigen. Leiten Sie die Ausgabe in
die Datei \textit{/tmp/port-protocol} um.
\item \textbf{Aufgabe 7b:} Verwenden Sie die Programme \textit{echo},
\textit{cut}, \textit{cat}, \textit{tail} und \textit{head}, um die folgenden
Felder aus der Datei \text{/etc/passwd} zu selektieren. Spalte/Zeile: (1/1),
(3/7), (5/3), (7/4).
\end{itemize}
% #>-----------------------------------------------------------------------------<#
\subsection{sort}
\label{sec:text-processing.sort}
Um nach Spalten zu sortieren wird durch die GNU Core Utility Collection das
Programm \textit{sort} bereit gestellt. Dieses Programm kann nach mehreren
möglichen Optionen sortieren. Nach einfachem Text, nach Speichergrößen, Zahlen,
Monaten (JAN$<$DEC) oder Versionen. Weitere Optionen sind in der Dokumentation
beschrieben.
Das nächste Beispiel behandelt die Sortierung der Ausgabe des Programms
\textit{df}. Das Programm \textit{df} gibt Informationen über den gesamten,
freien und verbrauchten Speicherplatz der eingehängten Partitionen aus. Die
Ausgabe wird nach der dritten Spalte aufsteigend sortiert. Dazu wird die Option
bzw. das Flag \textit{-k, --key} übergeben. Durch dieses Flag kann die Spalte
zur Sortierung übergeben werden. Mittels tail wird zudem die Kopfzeile
abgeschnitten.
\begin{bashcode}
$ df | tail --lines +2 | sort --key 3 --numeric-sort
tmpfs 401876 0 401876 0% /run/user/0
tmpfs 2009384 4 2009380 1% /tmp
tmpfs 2009384 1068 2008316 1% /run
/dev/vda1 613184 8740 604444 2% /boot/efi
...
\end{bashcode}
Die Speichergrößen können auch abgerundet werden durch die gängigen
Speicherabkürzungen K (Kilobyte), M (Megabyte), G (Gigabyte), T (Terabyte) ect.
In dem folgenden Beispiel wird dazu die Option \textit{--human-readable} dem
Programm \textit{df} übergeben. Anschließend muss sort mitgeteilt werden, dass
die Spalte nun Speichergrößen mit ihren Abkürzungen enthält. Andernfalls
sortiert \textit{sort} falsch.
\begin{bashcode}
$ df --human-readable | tail --lines +2 | sort --key 3 --human-numeric-sort
tmpfs 393M 0 393M 0% /run/user/0
tmpfs 2,0G 4,0K 2,0G 1% /tmp
tmpfs 2,0G 1,1M 2,0G 1% /run
/dev/vda1 599M 8,6M 591M 2% /boot/efi
...
\end{bashcode}
In den beiden Beispielen wurde stets nach einer Spalte sortiert. Es ist jedoch
auch möglich eine Sortierreihenfolge zu definieren. Dies wird erreicht indem das
Flag \textit{-k, --key} mehrfach definiert wird. In dem folgenden Beispiel wird
erst nach dem Mountpoint sortiert und anschließend nach dem gesamten
Speicherplatz.
\begin{bashcode}
$ df --human-readable | tail --lines +2 | sort --key 6 --key 3h
/dev/vda4 17G 4,0G 13G 25% /
/dev/vda2 1014M 228M 787M 23% /boot
/dev/vda1 599M 8,6M 591M 2% /boot/efi
...
\end{bashcode}
Sicherlich fragen Sie sich nun, was der Buchstabe \textit{h} bei Angabe der
zweiten Spalte bedeutet. Es ist möglich \textit{sort} mitzuteilen welche Spalte
welches Format besitzt. Der Buchstabe \textit{h} bedeutet nichts weiteres als
\textit{human-numeric-sort}. Informieren Sie sich in der Dokumentation über die
anderen Buchstaben, um einer Spalte ein Schema zuzuweisen.
In dem folgenden Beispiel wird nach dem dritten Zeichen, der ersten Spalte
von der Ausgabe \textit{df}, absteigend sortiert.
\begin{bashcode}
$ df | tail --lines +2 | sort --reverse --key 1.3
devtmpfs 1989716 0 1989716 0% /dev
tmpfs 401876 0 401876 0% /run/user/0
tmpfs 2009384 4 2009380 1% /tmp
...
\end{bashcode}
\begin{itemize}[label={},itemsep=0pt]
\item \textbf{Aufgabe 8a:} Bereinigen Sie die Datei \textit{/etc/services} und
leiten die Ausgabe an \textit{sort} weiter. Sortieren Sie aufsteigend nach dem
Port, absteigend nach dem Protokoll und anschließend aufsteigend nach dem
Namen.
\item \textbf{Aufgabe 8b:} Sortieren Sie die Datei \textit{/etc/passwd} nach
der \textit{UID} und \textit{GID} absteigend. Anschließend nach dem
Benutzernamen.
\end{itemize}
% #>-----------------------------------------------------------------------------<#
\subsection{join}
\label{sec:text-processing.join}
Sicherlich kennen Sie das \textit{JOIN} Statement von SQL. Ähnlich zu dem
SQL-Statement verhält sich das Programm \textit{join}, jedoch mit einigen
Restriktionen.
\begin{itemize}[itemsep=0pt]
\item Die Spalten müssen nach dem Fremdschlüssel sortiert sein.
\item Beide Dateien müssen den gleichen Trenner verwenden.
\item Kann ein Fremdschlüssel nicht aufgelöst werden, wird eine Fehlermeldung
für die jeweilige Zeile ausgegeben.
\end{itemize}
Im folgenden Beispiel werden die Zeilen aus den Dateien \textit{/etc/passwd} und
\textit{/etc/group} über die Spalte \textit{GID} miteinander verbunden und
weitergeleitet. Dazu werden beide Dateien vorab sortiert. Können Fremdschlüssel
nicht aufgelöst werden wird die Fehlermeldung dazu nach \textit{/dev/null}
umgeleitet. Die Ausgabe wird anschließend noch einmal nach dem Benutzernamen
sortiert.
Informieren Sie sich in der Dokumentation über die Bedeutung der Optionen bzw.
Flags.
\begin{bashcode}
$ join -t ":" -1 3 -2 4 <(sort --field-separator=":" --key 3n /etc/group) <(sort --field-separator=":" --key 4n /etc/passwd) 2> /dev/null | sort --field-separator=":" --key 5
0:root:x::halt:x:7:halt:/sbin:/sbin/halt
0:root:x::operator:x:11:operator:/root:/sbin/nologin
0:root:x::root:x:0:root:/root:/bin/bash
...
\end{bashcode}