1.4 Advanced Commands

In diesem Lab werden erweiterte Techniken vorgestellt, welche auf den Programmen aufbauen die in den letzten Kapiteln vorgestellt wurden. Dabei wird Wert darauf gelegt, dass auch der nötige Background vermittelt wird um die Commands zu verstehen. Aus diesem Grund ist dieses Lab ein wenig umfangreicher. Hold on to your hats!

Inhalt

find

Im ersten Kapitel haben wir uns die grundlegenden Funktionen von find angesehen. Natürlich kann find aber noch sehr viel mehr. Nachfolgend schauen wir uns einige speziellere Anwendungszwecke von find an.

find nach Zeit

find kann Suchresultate nach Zeit filtern. Dazu stehen uns verschiedene Möglichkeiten bereit. Unix Filesysteme schreiben für jede Datei drei verschiedene Zeitstempel mit. Access Time, Change Time und Modify time. Wir können uns diese Zeitstempel entweder alle mittels stat $dateiname ausgeben lassen. Oder wir verwenden dazu ls. Hier ein kurzer Überblick über die Bedeutung der Zeitstempel.

  • Access time
    • ls -lu
    • Zeigt die Zeit an zu der die Datei oder der Ordner zuletzt gelesen wurde.
    • Wird immer geschrieben, wenn gelesen wird.
  • Change time
    • ls -lc
    • Zeigt die Zeit an zu der die Metadaten einer Datei oder eines Ordners zuletzt geändert wurden. Bspw.: Permission oder Ownerchange mittels chmod.
    • Wird auch neu geschrieben, wenn ein Dateiinhalt verändert wird.
  • Modify time
    • ls -l
    • Zeigt die Zeit an zu welcher der Inhalt einer Datei zuletzt geändert wurde.
    • Verändert sich nicht bei Änderungen der Metadaten (Permissions, Owner, etc).

Da es sich bei diesen Zeitstempeln um Dateiattribute handelt können sie als Filter für find eingesetzt werden. Die entsprechenden Optionen heissen folgendermassen.

  • -atime n
    • Für access time
  • -ctime n
    • Für change time
  • -mtime n
    • Für modify time

Der Parameter n steht hierbei jeweils für eine 24h Einheit. Ein Aufruf von find mit dem Argument -mtime +14 liefert uns nur Dateien welche Änderungen die älter als 2 Wochen sind beinhalten. Das Vorzeichen von n schränkt die Suche weiter ein. Dabei kann n vorzeichenbehaftet sein.

  • +n
    • Änderungen älter als n Zeiteinheiten
  • -n
    • Änderungen jünger als n Zeiteinheiten
  • n
    • Änderungen genau vor n Zeiteinheiten
1
$ find . -type f -mtime +14

Finden von Dateien die vor mindestens 14 Tagen zuletzt verändert wurden

Suche nach Datum

Stellen wir uns vor einer unserer Server hatte einen Ausfall. Wir wollen die Ursache eruieren und möchten dazu die Logs eines bestimmten Zeitraums kopieren. Konkret interessieren uns nur Dateien der letzten zwei Tage, da in unserem Monitoringsystem zwei Tage vor dem Ausfall eine erhöhte Last festgestellt werden kann. Dazu können wir die Option -newerXY reference von find verwenden. XY steht hierbei für einen der Modifikatoren at, ct oder mt. Die Referenz kann entweder eine andere Datei oder ein Textstring sein. Typischerweise verwendet man dazu date.

1
find /var/log -type f -newermt $(date +%Y-%m-%d -d '2 day ago')

Finden aller Logs die jünger als zwei Tage sind

Exec all the things!

find bietet die Möglichkeit auf Matches direkt Aktionen auszuführen. Dazu verwendet man den Switch -exec gefolgt von einer bestimmten Syntax.

1
$ find . [Argumente] -exec [Command] {} \[Modifikator]`

Aufruf von find mit exec

  • [Argumente] sind die Argumente für find selbst.
  • [Command] hier steht das gewünschte Kommando welches auf die gefundene Datei angewendet werden soll.
  • {} ist ein Platzhalter für das Gefundene.
  • [Modifikator] sagt aus wie die Suchresultate von exec weiterverwendet werden sollen. Bsp:
    • \; Führe [Command] für jedes Suchresultat aus.
    • \+ Konkateniere alle Suchresultate und führe [Command] auf die Liste aus.
      • Aufpassen! Nicht alle Programme nehmen eine Liste von Befehlen entgegen.
1
2
3
4
5
# Äquivalent zu einem Aufruf von "rm file" für jedes gefundene File
$ find /var/log -type f -newermt $(date +%Y-%m-%d -d '2 day ago') -exec rm -f {} \;

# Äquivalent zu einem Aufruf von "rm file1 file2 file3" für jedes gefundene File
$ find /var/log -type f -newermt $(date +%Y-%m-%d -d '2 day ago') -exec rm -f {} \+

Lösche alle Logdateien die älter als 2 Tage sind

Verknüpfen verschiedener Suchen

Natürlich lässt find auch logische Verknüpfungen gemäss bool’scher Algebra zu. Wichtig! Werden mehrere Angaben ohne explizite Verknüpfung aneinandergereiht, werden sie implizit AND verknüpft! Die wichtigsten Verknüpfungen sind:

  • ( ... ) Präzedenzmodifikatoren (Hierarchische Ordnung, alles in Klammern wird zusammen ausgeführt)
  • -a, -and für ein logisches AND
  • -o, -or für ein logisches OR
  • !, -not für ein logisches NOT
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# Finden aller Dateien die auf .conf oder .cfg enden
$ find . -name "*.conf" -or -name "*.cfg"

# Finden aller Dateien und Ordner die dem User Tux oder Root gehören
$ find . -user "tux" -or -user "root"

# Finden aller Dateien die dem User Tux oder Root gehören mit Präzedenz
$ find . -type f -and \( -user "tux" -or -user "root" \)

# Finden aller Dateien die jünger als zwei Tage sind und dem User apache gehören
$ find . -type f -and -newermt $(date +%Y-%m-%d -d '2 days ago') -and -user "apache"

Logische Verknüpfungen beim Suchen von Dateien

Die Klammern modifizieren die Präzedenz (Operatorrangfolge) und werden dazu verwendet, logische Aussagen zu bündeln. In obigem Beispiel muss es sich bei dem gesuchten Objekt beispielsweise um eine Datei handeln und sie muss gleichzeitig entweder Root oder Tux gehören. Würden wir die Klammern weglassen, wäre die Aussage eine andere. Dann würde man nach Objekten suchen die dem User Tux gehören oder Objekten und Ordnern welche Root gehören.

xargs

Xargs ist ein UNIX Werkzeug welches, im Gegensatz zu vielen anderen Programmen, erlaubt Argumente per stdin zu übergeben. Aus diesen Argumenten lässt sich anschliessend ein neuer Programmaufruf bauen. Aber wozu braucht man das? Nun, viele Werkzeuge wie beispielsweise grep sind dazu fähig, Argumente per stdin anzunehmen. Allerdings gibt es auch Programme wie mkdir, rm oder echo die dies der Einfachheit halber nicht können.

Kurz gesagt kann man also mittels xargs den Output eines Programms als Input für ein Weiteres benutzen.

1
2
3
4
5
6
7
8
9
$ echo "how to generate lots of useless files" | xargs touch
$ ls -l
-rw-r--r-- 1 tux users 1. May 13:37 files
-rw-r--r-- 1 tux users 1. May 13:37 generate
-rw-r--r-- 1 tux users 1. May 13:37 how
-rw-r--r-- 1 tux users 1. May 13:37 lots
-rw-r--r-- 1 tux users 1. May 13:37 of
-rw-r--r-- 1 tux users 1. May 13:37 to
-rw-r--r-- 1 tux users 1. May 13:37 useless

Beispiel eines xargs Aufrufes

Oben sehen wir einen Aufruf von xargs. Per stdin wird eine Liste von Wörtern übergeben. Xargs verwendet diese Liste, um für jedes Wort einen Aufruf von touch durchzuführen. Dadurch wird aus jedem übergebenen Wort eine Datei. Ohne weitere Konfiguration nutzt xargs das Leerzeichen als Trennzeichen. Bei Dateinamen die Leerzeichen enthalten ist somit Vorsicht geboten!

Parameter von xargs

1
$ echo "sample.txt" | xargs -I % du -h %

Beispiel von Xargs mit substitution eines Parameters über stdin

Bei obigem Beispiel wird an xargs über den stdin Kanal ein Input übergeben, welcher dazu verwendet wird ein beliebiges Command zu vervollständigen. Der Substitutionsstring ist hier lediglich “%”, es kann sich dabei aber um ein beliebiges Zeichen oder Wort handeln. Das Geniale an xargs ist nun aber dass es sich beim Input auch um eine Liste von Werten handeln kann, über welche dann entsprechend iteriert wird.

Xargs selbst nimmt einige Parameter entgegen, welche beim Debugging sehr hilfreich sein können.

  • -I, --replace
    • Ersetze das entsprechende Stringargument an jeder Stelle des Commands mit dem Wert der über Stdin an Xargs übergeben wurde.
  • -t, --verbose
    • Generiere für jeden Aufruf eine Ausgabe in der Shell
  • -p, --interactive
    • Lasse jeden Aufruf vor der Ausführung bestätigen
  • -n, --max-args
    • Gibt an wie viele Argumente an einen Aufruf übergeben werden sollen
  • -0, --null
    • Verwende den Nullterminator als Trennzeichen (Ende eines Strings)

Der letzte Punkt bedarf eventuell einer Erläuterung. Greifen wir ein klein wenig vor und verwenden xargs kombiniert mit find um den Inhalt eines gesuchten Ordners auszugeben.

1
2
3
4
5
6
7
8
9
$ mkdir "some weird folder with spaces"
$ ls
'some weird folder with spaces'
$ find . -type d | xargs ls
ls: cannot access './some': No such file or directory
ls: cannot access 'weird': No such file or directory
ls: cannot access 'folder': No such file or directory
ls: cannot access 'with': No such file or directory
ls: cannot access 'spaces': No such file or directory

Fehlerhafter Aufruf von xargs bei Dateinamen mit Leerzeichen

Oh Graus! Was ist denn da passiert? find übergibt xargs einen Dateinamen, welcher Leerzeichen enthält. Da Leerzeichen von xargs aber als Trennzeichen interpretiert werden, wird aus einem Ordner plötzlich ein ganzer Haufen. Die einzelnen Ordner existieren aber natürlich nicht und können somit auch nicht angezeigt werden. Wir können das korrigieren indem wir find darum bitten, uns das Ende jeder Zeile mit einem Nullterminator zu dekorieren. Dafür rufen wir xargs mit der Option -0 auf.

1
2
3
4
find . -type d -print0 | xargs -0 ls -l
'./some weird folder with spaces':
-rw-r--r-- 1 tux users 1. May 13:37 i_am_some_file.lol
-rw-r--r-- 1 tux users 1. May 13:37 me_too.rofl

Richtiger Aufruf von xargs bei Dateinamen mit Leerzeichen

Jetzt sehen wir den Inhalt des gefundenen Ordners. Für den Rest des Kapitels wird aber der Einfachheit halber auf die Angabe von -print0 verzichtet. Die Verwendung dieser Syntax ist beim Umgang mit Dateinamen welche Leerzeichen beinhalten könnten aber zu empfehlen!

1
2
3
4
find . -type d -print0 | xargs -0 ls -l
'./some weird folder with spaces':
-rw-r--r-- 1 tux users 1. May 13:37 i_am_some_file.lol
-rw-r--r-- 1 tux users 1. May 13:37 me_too.rofl

Richtiger Aufruf von xargs bei Dateinamen mit Leerzeichen

Xargs kombiniert mit find

Wie eben bereits kennengelernt ist es sehr beliebt, xargs im Zusammenhang mit find zu verwenden. Möchten wir beispielsweise in einem Ordner alle Dateien entfernen die älter als zwei Wochen sind, so können wird dies mit einer einfachen Kombination aus find und xargs erledigen.

1
$ find /mydir -type f -mtime +14 | xargs rm

Verwendung von xargs zur Entfernung alter Dateien

Aber Moment! Wieso sollte man in diesem Beispiel xargs verwenden? find verfügt doch über eine -exec Funktion, welche es ermöglicht genau dies zu tun? Ja, das stimmt. Aber xargs ist bedeutend schneller. Sehen wir uns das anhand eines Beispiels an. Dazu erzeugen wir in unserem Ordner 1000 leere Textdateien.

1
$ for x in {1..1000}; do touch $x.txt; done

Erstellen von ein paar Dateien

Anschliessend nutzen wird das Programm time, um die Ausführungszeit beider Varianten zu messen.

1
2
3
4
5
$ time find . -type f -name "*.txt" -exec rm {} \;
0.54s user 0.25s system 97% cpu 0.815 total

$ time find . -type f -name "*.txt" | xargs rm
0.00s user 0.01s system 89% cpu 0.008 total

Vergleich der Ausführungsgeschwindigkeiten

Der Unterschied zwischen den beiden Aufrufen liegt darin, dass find das -exec Kommando auf jedes Suchresultat separat anwendet. Wohingegen xargs in diesem Beispiel dem Programm rm eine Liste mit Werten übergibt. Die zweite Methode ist dadurch natürlich viel effizienter, da rm nur einmal ausgeführt wird. Ganz gewiefte Leser_innen, welche während der Lektüre des erweiterten Kapitels zu find nicht geschlafen haben, werden nun einwerfen, dass es mittels find ... \+ ebenfalls möglich ist eine konkatenierte Liste von Befehlen über -exec an ein Programm zu übergeben. Ja, das ist absolut korrekt. Die Ausführungszeit wird sich in dem Beispiel auch nicht wirklich unterscheiden. Aber natürlich gibt es gute Gründe für die Verwendung von xargs:

  • Erstens ist die Syntax mit xargs viel einfacher.
  • Zweitens gibt xargs den Returncode des ausgeführten Kommandos zurück, find hingegen nur seinen eigenen.
  • Drittens lassen sich mittels xargs auch bequem mehrere Kommandos aneinanderreihen.
  • Viertens lassen sich die von xargs generierten Programmaufrufe auch parallelisieren. Dazu sehen wir uns gleich ein weiteres Beispiel an.
  • Fünftens wurde vor vielen, vielen Jahren die Unix Philosophie von Männern mit langen, grauen Bärten niedergeschrieben. Sie besagt, dass ein gutes Werkzeug jeweils nur eine einzige Aufgabe erledigen soll, diese dafür so gut wie möglich. Das wollen wir natürlich respektieren. ;)

Xargs parallelisieren

Stellen wir uns vor wir suchen mal wieder die Nadel im Heuhaufen. Irgendwo im Dateisystem befindet sich eine Datei mit einem Inhalt, den wir suchen. Da wir heutzutage viel Geld für Prozessoren mit mehreren Rechenkernen hinblättern, wollen wir diese auch so gut es geht auslasten. Daher möchten wir pro CPU-Core jeweils einen Suchprozess starten.

1
2
$ find / -type f -print0 | xargs -0 -n1 -P8 grep -Hn 'mysecret.key'
/home/tux/git/.hidden_file:1: mysecret.key lolololnobodyeverfindsmysupersecrettoken

Parallelisiertes durchsuchen von Dateien

Sehen wir uns doch kurz an was hier genau passiert.

  • -print0 und -0 verwenden wir, weil wir nicht wissen ob die Datei Leerzeichen enthält
  • -n1 teilt xargs mit, pro Ausgabe von find einen Command abzusetzen
  • -P8 teilt xargs mit, gleichzeitig 8 parallele Prozesse zu starten

Wenn wir uns die Systemauslastung beispielsweise mit htop anzeigen lassen, sehen wir dass jeder Kern gleichzeitig am Rechnen ist. Natürlich lässt sich die Parallelisierung von Prozessen für sehr viel mehr als nur die Suche nach verlorenen Dateien verwenden. Aber für den Moment belassen wir es mal dabei.

grep

Wie wir bereits bei find gesehen haben, sind gewisse Standardwerkzeuge sehr vielseitig. Das Programm grep ist hierbei keine Ausnahme. Wie bereits im Textverarbeitungskapitel erwähnt, basiert grep auf sogenannten regular Expressions. Auf eine Beschreibung des kompletten Funktionsumfangs sei an dieser Stelle aber verzichtet. Stattdessen wollen wir uns die wichtigen Funktionen ansehen, welche im Alltag oft Verwendung finden.

Grundlagen der regular Expressions

Die Grundlage der regular Expressions basiert darauf, dass man sich Platzhalter für Gruppen von Zeichen überlegt. Auch spezielle Zeichen wie ein Zeilenumbruch oder der Anfang einer Zeile können über Regular Expressions als Mittel zur Suche von Textstellen benutzt werden. Im normalen Modus deckt grep ohnehin nur die wichtigsten Funktionen ab. Für kompliziertere Ausdrücke kommt grep -E respektive egrep zum Einsatz.

  • RegEx Notation
    • ^ Markiert den Zeilenanfang
    • $ Markiert das Zeilenende
    • . Markiert ein beliebiges Zeichen
    • ( ) Markiert eine Zeichengruppierung
    • { n } Quantifiziert einen vorhergehenden Ausdruck n-mal
    • ? Quantifizierung für “höchstens einmal”
    • * Quantifizierung für “null oder mehrmals”
    • + Quantifizierung für “ein oder mehrmals”

Diesem Lab liegt eine vorbereitete Beispieldatei bei, welche Ausdrücke enthält anhand deren wir nun einige Funktionalitäten von grep studieren. Fangen wir ganz einfach an und schauen uns die Effekte von grep auf unsere sample.txt Datei an.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
cat sample.txt
puzzle
puzzle itc
PUZZLE TEST
hello puzzle memberLinux is great
tux
unix
Preferita is my favourite pizza
asdfasdf
öäü///

Inhalt der sample.txt Datei

Case Sensitivity

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
$ grep puzzle sample.txt
puzzle
puzzle itc
hello puzzle member

$ grep -i puzzle sample.txt
puzzle
puzzle itc
PUZZLE TEST
hello puzzle member

Suche eines case insensitiven Strings

Normalerweise ist grep case sensitiv! Wird grep aber mit der Option -i auf eine Textdatei angewendet, so werden alle Instanzen des Suchbegriffes angezeigt die abgesehen von der Gross/Kleinschreibung übereinstimmen.

Anwendung grundlegender regular Expressions

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
$ grep ^puzzle sample.txt
puzzle
puzzle itc

$ grep pizza$ sample.txt
Preferita is my favourite pizza

$ grep .ux sample.txt
Linux is great
tux

Suche mittels grundlegender regular Expressions

Die drei speziellen Zeichen, die wir oben kennengelernt haben verwenden wir nun gleich in der Praxis.

  • Im ersten Beispiel werden alle Zeilen angezeigt die mit dem kleinen Wort “puzzle” beginnen.
  • Im zweiten Beispiel werden alle Zeilen angezeigt die auf das kleine Wort “pizza” enden.
  • Im dritten Beispiel werden alle Zeilen angezeigt die einen beliebigen Buchstaben, gefolgt von “ux” beinhalten.

Inverse matching

Manchmal ist es hilfreich, nicht die Menge an gesuchten Ausdrücken zu spezifizieren, sondern alles was man nicht möchte.

1
2
3
4
5
6
7
8
$ grep -v puzzle sample.txt
PUZZLE TEST
Linux is great
tux
unix
Preferita is my favourite pizza
asdfasdf
öäü///

grep inverse match

Hier werden nun alle Zeilen ausgegeben die nicht das kleine Wort “puzzle” beinhalten.

Anzahl ausgegebener Zeilen modifizieren

Sucht man beispielsweise in einem Konfigurationsfile nach einem gewissen Parameter, so kommt es oft vor, dass dieser unmittelbar auf der nächsten Zeile steht. Mittels -A, -B und -C lassen sich die Anzahl angezeigter Zeilen spezifizieren.

  • -A, --after-context
  • -B, --before-context
  • -C, --context

Konkret verwenden kann man diese Parameter folgendermassen.

1
2
3
4
5
6
7
8
9
$ grep NTPServers /etc/ntpd.conf
# NTPServers

$ grep -A4 NTPServers /etc/ntpd.conf
# NTPServers
servers 0.gentoo.pool.ntp.org
servers 1.gentoo.pool.ntp.org
servers 2.gentoo.pool.ntp.org
servers 3.gentoo.pool.ntp.org

grep inverse match

Anhand des ersten Beispieles sehen wir, dass es nicht so viel Sinn macht nur nach unserem Suchbegriff zu suchen. Die relevante Information steht nämlich weiter unten. Durch die Verwendung von -A4 werden auch die 4 nächsten Zeilen nach dem Match angezeigt. Bei -B4 wären es die 4 Zeilen vor und bei -C4 die 4 Zeilen vor und nach dem Match.

grep -E

Wie bereits angesprochen kann über das Flag -E auf die erweiterten Funktionen von grep zugegriffen werden. Um den Rahmen nicht zu sprengen schauen wir uns kurz einige sinnvolle Beispiele an.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
$ grep -E '(zz)+' sample.txt
puzzle
puzzle itc
hello puzzle member
Preferita is my favourite pizza

$ grep -E '(Linux|TEST)' sample.txt
PUZZLE TEST
Linux is great

$ grep -Eo '^[a-z]*' sample.txt
puzzle
puzzle
hello
tux
unix
asdfasdf

Verschiedene Aufrufe von egrep

  • Beim ersten Aufruf handelt es sich um die Suche nach der Buchstabengruppe zz die mindestens ein oder mehrmals vorkommen darf.
  • Beim zweiten Aufruf handelt es sich um eine Suche nach der Buchstabengruppe “Linux” oder “Test”.
  • Beim dritten Aufruf suchen wir jeden Zeilenanfang der mit einer beliebigen Anzahl Buchstaben des Sets der Kleinbuchstaben a-z beginnt.

Verlorene Daten mit grep wiederfinden

Weil grep so vielseitig ist kann es für unterschiedlichste Zwecke verwendet werden. Im Folgenden lernen wir einen kreativen Weg kennen wie man versehentlich gelöschte Daten mittels grep wiederfinden könnte. Dazu benötigen wir aber ein wenig Background zum Linuxfilesystem.

Ein wichtiger Teil der Unix Philosophie ist der Grundsatz “Everything is a file”. Das bedeutet alles, was vom Kernel in den Userspace exportiert wird liegt irgendwo im Filesystem als einfaches File herum. Beispielsweise wird fast jede Hardwarekomponente unter dem Devicetree /dev als Datei repräsentiert. So zum Beispiel auch die Maus, was wir einfach überprüfen können.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
$ sudo cat /dev/input/mouse0 | hexdump
...

* Maus bewegen *

0000000 ff18 1800 00ff ff18 1800 01fe fe18 1800
0000010 00fe fd18 1800 01fd fd18 1800 01fc fd18
0000020 1800 01fd fc18 1801 00fd fe18 1800 01fd
0000030 fd18 1800 01fe fe18 1800 00fe fe18 1800
0000040 00fe fe18 1800 00ff fe18 3800 ffff ff18
0000050 3800 ffff 0028 28ff ff00 0028 28ff ff00
0000060 0028 28ff ff00 0028 28ff ff00 0028 28ff
0000070 fe00 0028 28ff fe00 0028 28ff fe00 0028
0000080 28ff ff00 0028 38fe ffff 0028 28ff ff00
0000090 0028 38fe feff 0028 38ff ffff 0028 28ff
00000a0 ff00 ff38 38ff ffff 0028 18ff 00ff 0028
...

Ausgabe der Mauskoordinaten aus ästhetischen Gründen hexadezimal dargestellt

Doch wie funktioniert das Ganze? Nun, der entsprechende Treiber kümmert sich im Kernel um das periodische Polling der Mauskoordinaten. Er macht nichts anderes als für jedes HID (Human Input Device) eine Datei zur Verfügung zu stellen und sie laufend zu updaten. Programme welche nun die Mauskoordinaten verwenden möchten, müssen also nichts anderes tun, als aus dieser Datei zu lesen. Diese Philosophie zieht sich vom Framebuffer, über den Netzwerkstack bis hin zu den Blockdevices quer durchs komplette Linuxsystem. Alles ist ein File.

Anbei einige interessante sys-fs Files an welchen bedenkenlos herumgespielt werden kann.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# CPU Informationen abfragen
$ cat /proc/cpuinfo

# RealTimeClock abfragen
$ cat /sys/class/rtc/rtc0/date
$ cat /sys/class/rtc/rtc0/time

# Hintergrundbeleuchtung eines Dell Laptops anzeigen
cat /sys/class/backlight/intel_backlight/brightness
937
# Hintergrundbeleuchtung eines Dell Laptops einstellen
echo "200" > /sys/class/backlight/intel_backlight/brightness

Experimentieren mit dem sys-fs

Nun da wir wissen, dass alle Geräte irgendwo als Datei vorhanden sind, stellt sich doch die Frage wie das denn mit den Festplatten aussieht. Tatsächlich verbirgt sich hinter dem Pfad /dev/sda1 nichts anderes als eine Datei, welche die erste Partition der ersten Festplatte referenziert. Und Dateien können wir mit grep bedenkenlos lesen, respektive durchsuchen. Testen wir das doch an einem Beispiel.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
$ cat mylovelyfile.txt
This file is very important to me. It contains lots of useful
Information especially the folling word.

I_will_never_forget_this_particular_string!

Here is some ascii art, straight outta the nineties:

░░░░░░░░░░░░░░░░░░░░░░█████████
░░███████░░░░░░░░░░███▒▒▒▒▒▒▒▒███
░░█▒▒▒▒▒▒█░░░░░░░███▒▒▒▒▒▒▒▒▒▒▒▒▒███
░░░█▒▒▒▒▒▒█░░░░██▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒██
░░░░█▒▒▒▒▒█░░░██▒▒▒▒▒██▒▒▒▒▒▒██▒▒▒▒▒███
░░░░░█▒▒▒█░░░█▒▒▒▒▒▒████▒▒▒▒████▒▒▒▒▒▒██
░░░█████████████▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒██
░░░█▒▒▒▒▒▒▒▒▒▒▒▒█▒▒▒▒▒▒▒▒▒█▒▒▒▒▒▒▒▒▒▒▒██
░██▒▒▒▒▒▒▒▒▒▒▒▒▒█▒▒▒██▒▒▒▒▒▒▒▒▒▒██▒▒▒▒██
██▒▒▒███████████▒▒▒▒▒██▒▒▒▒▒▒▒▒██▒▒▒▒▒██
█▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒█▒▒▒▒▒▒████████▒▒▒▒▒▒▒██
██▒▒▒▒▒▒▒▒▒▒▒▒▒▒█▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒██
░█▒▒▒███████████▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒██
░██▒▒▒▒▒▒▒▒▒▒████▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒█
░░████████████░░░█████████████████

# Hier wird aus versehen rm verwendet ...
$ rm mylovelyfile.txt
$ cat mylovelyfile.txt
cat: mylovelyfile.txt: No such file or directory

Hoppla

In obigem Beispiel wurde versehentlich die Datei mylovelyfile.txt gelöscht. Doch was bedeutet denn ein einfaches Löschen für die Datei? Beim Löschen wird im Filesystem lediglich die Referenz auf die Datei gelöscht. Dies führt dazu dass der Block in dem die Datei gespeichert war wieder freigegeben wird. Solange der Block aber nicht überschrieben wurde, kann die Datei noch gelesen werden! Jedenfalls gilt dies für Filesysteme wie ext2/3/4, xfs, ntfs. Sogenannte Copy-On-Write (CoW) Filesysteme wie zfs oder btrfs funktionieren anders und würden eigens Möglichkeiten bieten, eine verlorene Datei wiederherzustellen.

Um eine Datei zu löschen und zu überschreiben kann entweder shred oder dd benutzt werden. Mit diesen Befehlen aber bitte Vorsichtig sein!

Im Ernstfall ist der Computer umgehend herunterzufahren um eine Korruption der Daten zu verhindern. Die Disk sollte danach über ein Livesystem im read only Modus eingehängt werden. Anschliessen können wir mit grep im binary Modus nach der Datei suchen.

  • Vorgehen bei einem Ernstfall
    • System umgehend herunterfahren
    • System mittels Livesystem (bspw. grml oder arch-live) booten
    • Festplatte read only mounten
      • mount -o ro,noload /dev/sda1 /mnt/recovery
    • Geeignetes Werkzeug verwenden um die Daten wiederherzustellen
      • grep
      • testdisk

Da es sich hier aber nur um ein einfaches Beispiel handelt, können wir das Verfahren auch live ausprobieren. Wir verwenden dazu grep im binary Modus.

1
2
3
4
5
6
7
8
$ grep --binary-files=text --context=n 'I_will_never_forget_this_particular_string!' /dev/sdx1
............
... Müll ...
............
> Inhalt der gesuchten Datei
............
... Müll ...
............

Binary grep nach verlorenen Dateien

Dabei setzen wir den n gross genug um die etwa gesamte grösse Datei auszugeben und suchen nach einem uns bekannten (und möglichst einzigartigen) Textstück in der Datei. Vor und nach der Datei werden sich entweder Zufällige Daten, oder andere Dateien befinden. Wenn der Parameter n zu gross ist, wird der ausgegebene Bereich überlappen. Das ist grep aber ziemlich egal. Falls die Datei noch nicht überschrieben wurde, wird ihr Inhalt abhängig von der Grösse der Disk nach einigen Minuten auftauchen.

sed

Das Kürzel sed steht für stream Editor. Wie alle anderen hier vorgestellten Werkzeuge kann auch sed eine Vielzahl an Aufgaben übernehmen. Dazu gehören:

  • Textsubstitution
  • Editieren von Textdateien
  • Selektives Ausgeben von Dateien
  • Nicht-interaktives Editieren von Text

sed funktioniert nach dem einfachen Prinzip Einlesen->Verarbeiten->Ausgeben.

  • Einlesen
    • sed liest einen input Stream ein (file, pipe, stdin) und speichert ihn temporär im internen Buffer.
  • Verarbeiten
    • Alle Kommandos und Argumente die sed mitgegeben wurden werden auf den Buffer angewendet.
  • Ausgeben
    • Ausgeben des gesamten Buffers auf den output Stream.

Dieser Vorgang wiederholt sich bis der gesamte Input abgearbeitet ist. Der einfachste Aufruf von sed ist derjenige ohne irgendwelche Argumente.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
$ sed '' sample.txt
puzzle
puzzle itc
PUZZLE TEST
hello puzzle member
Linux is great
tux
unix
Preferita is my favourite pizza
asdfasdf
öäü///

Verwendung von sed ohne Argumente auf eine Datei

Wird sed ohne Argumente aufgerufen, ist der Verarbeitungsschritt trivial und die Datei wird unverändert ausgegeben. Ein weiterer Kanal mit dem wir sed mit Daten versorgen können ist der pipe Operator.

1
2
$ echo "who stole my cookies?" | sed ''
who stole my cookies?

Verwendung von sed ohne Argumente auf eine Pipe

Ausgeben von Zeilen

Eine der Funktionen von sed ist das Printkommando.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
$ sed 'p' sample.txt
puzzle
puzzle
puzzle itc
puzzle itc
PUZZLE TEST
PUZZLE TEST
hello puzzle member
hello puzzle member
Linux is great
Linux is great
tux
tux
unix
unix
Preferita is my favourite pizza
Preferita is my favourite pizza
asdfasdf
asdfasdf
öäü///
öäü///

Ausprobieren von seds Printkommando

Wie unschwer zu erkennen ist, wird nun im Gegensatz zu vorher jede Zeile doppelt ausgegeben. Das passiert weil sed standardmässig bereits jede eingelesene Zeile ausgibt. Mit dem Printkommando teilt man sed explizit mit jede Zeile auszugeben. Daher erscheinen sie nun doppelt. Hier sehen wir auch schön das gepufferte Verhalten von sed. Es liest Zeile für Zeile ein, speichert sie in den Buffer und gibt diesen dann aus. Mit dem Parameter -n können wir die Standardausgabe von sed unterdrücken. Somit wirkt sich nur noch das Printkommando auf die Ausgabe aus.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
$ sed -n 'p' sample.txt
puzzle
puzzle itc
PUZZLE TEST
hello puzzle member
Linux is great
tux
unix
Preferita is my favourite pizza
asdfasdf
öäü///

Unterdrückte Standardausgabe

Nun sind wir wieder am gleichen Punkt wie zu Beginn. Doch nun erweitern wir den Printbefehl um die Angabe von sogenannten Adressbereichen oder auch Address Ranges.

Verwendung von Adressbereichen

Mit Adressen ist es möglich, spezifische Bereiche aus einem Stream auszugeben. Man kann dabei einzelne Zeilen oder einen ganzen Bereich von Adressen ausgeben.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
$ sed -n '3p' sample.txt
PUZZLE TEST

$ sed -n '1,4p' sample.txt
puzzle
puzzle itc
PUZZLE TEST
hello puzzle member

$ sed -n '2,+4p' sample.txt
puzzle itc
PUZZLE TEST
hello puzzle member
Linux is great
tux

$ sed -n '1~2p' sample.txt
puzzle
PUZZLE TEST
Linux is great
unix
asdfasdf

Verwendung von Adressen, Bereichen und Offsets

  • Im ersten Beispiel sehen wir die Verwendung einer Adresse. Wir veranlassen sed dazu die dritte Zeile auszugeben.
  • Im zweiten Beispiel verwenden wir den Bereich 1-4, dadurch werden die Zeilen 1-4 ausgegeben.
  • Im dritten Beispiel verwenden wir einen Offset. Wir teilen sed mit erst bei der zweiten Zeile anzufangen und ab da vier Zeilen auszugeben.
  • Im vierten Beispiel verwenden wir den Stepoperator mit dem Argument 2. Es werden nur Zeilen ausgegeben die ein ganzzahliges Vielfaches von 2 sind.
    • Mit ~2 wird beginnend mit der ersten Zeile jede zweite Zeile ausgegeben
    • Mit ~5 jede fünfte
    • usw.

Da wir nun die Verwendung von seds Ausgabe beherrschen, können wir einen Schritt weiter gehen und damit beginnen Text zu löschen.

Text löschen

Beim Löschen von Text können wir die genau gleiche Syntax wie beim Ausgeben verwenden. Wir ersetzen den Printbefehl durch den Deletebefehl. Doch nun benötigen wir die Option -n nicht mehr, da sed nur ausgibt was im Buffer übrig bleibt.

1
2
3
4
5
6
$ sed '1~2d' sample.txt
puzzle itc
hello puzzle member
tux
Preferita is my favourite pizza
öäü///

Löschen mit sed

Verwenden wir den Befehl '1~2d', liest sed die Zeilen in den Buffer ein und löscht jede Zweite. Alles was übrig bleibt wird ausgegeben. Wir erhalten als Ausgabe alle Zeilen mit ungerader Nummer.

Wenn wir nun jede zweite Zeile aus einer Datei löschen möchten, weil vielleicht beim Kopieren etwas schief gelaufen ist, könnten wir auf folgende Idee kommen.

1
$ sed '1~2d' sample.txt > sample.txt

Löschen jeder zweiten Zeile aus einer Datei

Für genau diesen Fall bietet sed den sogenannten “in-place” Modus an. Wir können mittels -i Flag in diesen Modus wechseln. Dadurch werden alle Änderungen direkt auf die Originaldatei angewendet. Dieser Modus sollte mit Vorsicht verwendet werden, da sed keine Fragen stellt sondern einfach überschreibt oder löscht.

1
$ sed -i '1~2d' sample.txt

in-place Löschen jeder zweiten Zeile aus einer Datei

Obiger Aufruf von sed ist viel einfacher und manipuliert die Originaldatei direkt. Zur Sicherheit lässt sich aber auch direkt eine Backupdatei erstellen. Dadurch wird die Originaldatei vor Durchführung der Änderungen mit der Endung “.bak” wegkopiert.

1
2
3
$ sed -i.bak '1~2d' sample.txt
$ ls
sample.txt  sample.txt.bak

in-place Löschen jeder zweiten Zeile aus einer Datei mit Erstellen eines Backups

Als kleine Rekapitulation versuchen wir uns an den find Befehl zu erinnern. Den können wir nämlich auf zwei verschiedene Arten mit unserer sed Instruktion verbinden.

1
2
$ find . -type f -iname '*.txt' -exec sed -i '1~2d' {} \;
$ find . -type f -iname '*.txt' | xargs sed -i '1~2d'

Löschen jeder zweiten Zeile aus allen lokalen Textdateien

Text substituieren

Der wohl am Meisten verwendete Modus von sed ist der Substitutionsmodus. Damit wird Text ersetzt. Das ganze basiert auf einer regular Expression, welche wir ja bereits von grep kennen.

Die absolut einfachste Form des Suchen und Ersetzens ist ein Ausdruck der Form 's/wasser/bier/'.

  • Mit s wechseln wir in den Substitutionsmodus
  • / fungiert als Trennzeichen (dies kann aber theoretisch auch jedes beliebige andere Zeichen sein. Bspw: ',' oder '_')
  • Das erste Wort wird durch das Zweite ersetzt (offensichtlich).

Das Verwenden von verschiedenen Trennzeichen vereinfacht beispielsweise die Arbeit mit URLs. Sehen wir uns dazu folgendes Beispiel an. Hätten wir '/' oder '_' als Trennzeichen verwendet, hätten wir uns wahrscheinlich die Finger verknotet.

1
2
$ echo "https://www.puzzle.ch/de/home" | sed 's,.ch/de/home,-itc.de/de/home_pitc_de,'
https://www.puzzle-itc.de/de/home_pitc_de

Umbiegen einer URL mittels sed

Damit haben wir auch bereits eine relativ komplizierte Substitution hinter uns. Daher schauen wir uns die Möglichkeiten noch im Detail an. Dazu verwenden wir jeweils die zweite sample2.txt Datei.

1
2
3
4
5
6
7
8
$ cat sample2.txt
But, if constructing the future and settling everything
for all times are not our affair, it is all the more clear
what we have to accomplish at present: I am referring to
ruthless criticism of all that exists, ruthless both in
the sense of not being afraid of the results it arrives
at and in the sense of being just as little afraid of
conflict with the powers that be.

Inhalt der sample2.txt Datei

Fangen wir mit einer einfachen Substitution an. Zur Hervorhebung wurden die ersetzten Wörter zusätzlich mit * markiert.

1
2
3
4
5
6
7
8
$ sed 's/all/none/' sample2.txt
But, if constructing the future and settling everything
for *none* times are not our affair, it is all the more clear
what we have to accomplish at present: I am referring to
ruthless criticism of *none* that exists, ruthless both in
the sense of not being afraid of the results it arrives
at and in the sense of being just as little afraid of
conflict with the powers that be.

Einfache Substitution

Mit dem Befehl ersetzen wir das Wort “all” mit “none” auf jeder Zeile. Doch bereits auf der zweiten Zeile wird klar, dass nicht alle Instanzen ersetzt wurden! Ohne Erweiterung ersetzt sed nur den ersten Match. Um pro Zeile alle Vorkommnisse eines Wortes zu ersetzen, kommt der Modifikator g zum Einsatz.

1
2
3
4
5
6
7
8
sed 's/all/none/g' sample2.txt
But, if constructing the future and settling everything
for *none* times are not our affair, it is *none* the more clear
what we have to accomplish at present: I am referring to
ruthless criticism of *none* that exists, ruthless both in
the sense of not being afraid of the results it arrives
at and in the sense of being just as little afraid of
conflict with the powers that be.

Generelle Substitution

Der Modifikator steuert die Art der Substitution. Wollen wir beispielsweise nur das Zweite “all” ersetzen, gelingt dies folgendermassen.

1
2
3
4
5
6
7
8
$ sed 's/all/none/2' sample2.txt
But, if constructing the future and settling everything
for all times are not our affair, it is *none* the more clear
what we have to accomplish at present: I am referring to
ruthless criticism of all that exists, ruthless both in
the sense of not being afraid of the results it arrives
at and in the sense of being just as little afraid of
conflict with the powers that be.

Bedingte Substitution

Natürlich sind Modifikatoren kombinierbar! Beispielsweise ist es möglich, mittels p nur geänderte Zeilen auszugeben. Dafür müssen wir aber auch wieder -n verwenden um die Standardausgabe zu unterdrücken!

1
2
$ sed -n 's/all/none/2p' sample2.txt
for all times are not our affair, it is none the more clear

Bedingte substitution mit print statement

Nun wird nur die geänderte Zeile ausgegeben. Es handelt sich um die Zeile, bei welcher das gesuchte Wort zum 2. Mal aufgetaucht ist. Möchten wir die Substitution unabhängig von Gross/Kleinschreibung durchführen, so können wir den Modifikator i verwenden.

1
2
3
$ sed -n 's/ALL/none/pi' sample2.txt
for none times are not our affair, it is all the more clear
ruthless criticism of none that exists, ruthless both in

Case insensitive substitution

Bei sed gelten die selben Regeln für regular Expression wie bei grep. Nachfolgend einige erweiterte Beispiele.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
$ sed -n 's/^.*with/none of/p' sample2.txt
none of the powers that be.

$ sed -n 's/^.*with/(&)/p' sample2.txt
(conflict with) the powers that be.

$ sed 's/\([a-zA-Z]*\) \([a-zA-Z]*\)/\2 \1/' sample2.txt
But, if constructing the future and settling everything
all for times are not our affair, it is all the more clear
we what have to accomplish at present: I am referring to
criticism ruthless of all that exists, ruthless both in
sense the of not being afraid of the results it arrives
and at in the sense of being just as little afraid of
with conflict the powers that be.

Substitutionen basierend auf regular Expressions

  • Beispiel 1 zeigt die Verwendung des RegEx ^.*with
    • ^ stellt den Zeilenanfang dar
    • .* eine beliebige Anzahl beliebiger Zeichen
    • with lediglich das Wort “with”
      • Die regular Expression matched vom Zeilenanfang bis zu “with”
      • Und replaced in einem zweiten Teil alles durch “none of”
  • Beispiel 2 zeigt die Verwendung des selben RegEx aber mit & in der Substitution
    • & ist eine Referenz auf den Match des RegEx
    • Der Match wird mit Klammern umfasst
  • Beispiel 3 zeigt die Verwendung von Gruppen
    • ([a-zA-Z]*)([a-zA-Z]*) Stellt eine Gruppe aus zwei Wörtern dar welche aus kleinen oder grossen Buchstaben bestehen
      • Alles innerhalb von runden Klammern wird als Gruppe zusammengefasst
      • Klammern müssen “escaped” werden, sonst sucht sed auch nach den Klammern
    • \1 und \2 im Replace sind Referenzen auf die erste und zweite Gruppe
      • Durch eine gewiefte Verdrehung der Zahlen switchen die Wörter ihre Plätze
    • Wir sehen, dass die RegEx nicht perfekt ist da in Zeile 1 ein Komma an zweiter Stelle steht.
      • Das lässt sich natürlich durch sed 's/\([^ ]*\) \([^ ]*\)/\2 \1/' sample2.txt fixen, aber das wird mir jetzt zu blöd. ;-)

Ab diesem Punkt würde dieses Tutorial zu einem RegEx Tutorial, was den Rahmen ein wenig sprengen würde. Mit sed lässt sich eine ganze Reihe von Problemen lösen. Oft scheitert es aber dann an der Komplexität der Regular Expression. Hier gilt dasselbe Prinzip wie bei der Salatsauce. Viel hilft viel.

Zuletzt geändert November 8, 2022: convert to hugo, deploy to openshift (856591c)