Linux und Shell

Linux
Tooling
R
Python
Dateien, Rechte, Pipes und die Befehle, die man täglich braucht.

Kernideen

  • Ein Baum ab /, keine Laufwerksbuchstaben; fast alles ist eine Datei.
  • grep sucht in Dateien, find sucht nach Dateien.
  • Jedes Programm liest von der Standardeingabe und schreibt auf die Standardausgabe; eine Pipe verbindet beides zu Ketten.
  • Drei Ströme: stdin, stdout, stderr. Fehler laufen bewusst getrennt.
  • > überschreibt ohne Rückfrage, >> hängt an. rm kennt keinen Papierkorb.
  • Rechte stehen in zehn Zeichen: Art, dann Besitzer, Gruppe, alle übrigen.

Erklärung

Vorwissen: keines. Die Beispiele dieser Seite laufen beim Bauen der Website auf einem Linux-Rechner, die Ausgaben sind also echt. Angelegt wird dafür ein kleiner Ordner mit einer Logdatei, einer CSV-Datei und einer Notiz. Die Shell ist sprachunabhängig; nur Beispiel 6 zeigt R und Python getrennt.

Warum sich die Shell lohnt

Auswertungen laufen irgendwann auf einem Server, und der hat keine Oberfläche. Ein Befehl ist wiederholbar und dokumentierbar, ein Mausklick nicht. Und manche Aufgaben sind in der Shell schlicht schneller: Wer wissen will, wie viele Zeilen einer Logdatei einen Fehler enthalten, tippt das in fünf Sekunden.

Ein Baum, keine Laufwerke

Unter Windows hat jedes Laufwerk einen Buchstaben. Unter Unix gibt es genau einen Baum ab /; weitere Datenträger werden an einer Stelle darin eingehängt. Auch Geräte und laufende Prozesse erscheinen als Dateien, weshalb sich dieselben Werkzeuge auf fast alles anwenden lassen.

Die Grundbefehle

Befehl tut häufig gebraucht mit
ls Inhalt auflisten -l ausführlich, -a auch versteckte, -R rekursiv
cd Verzeichnis wechseln .. eine Ebene hoch
pwd wo bin ich
mkdir Ordner anlegen -p samt Zwischenebenen
cp, mv kopieren, verschieben, umbenennen -r rekursiv
rm löschen -r rekursiv, kein Papierkorb
cat, head, tail Inhalt, Anfang, Ende -n Zeilenzahl, tail -f mitlesen
wc zählen -l Zeilen

rm löscht sofort und endgültig. rm -rf auf ein falsches Verzeichnis ist einer der teuersten Tippfehler überhaupt. Vor dem Löschen mit Platzhaltern lohnt es sich, denselben Ausdruck zuerst mit ls auszuprobieren.

Die drei Ströme

Strom Nummer wofür umlenken mit
stdin 0 Eingabe < datei
stdout 1 normale Ausgabe > datei (überschreibt), >> datei (hängt an)
stderr 2 Fehlermeldungen 2> datei, 2>/dev/null

Fehlermeldungen laufen über einen eigenen Kanal. Deshalb erscheinen sie weiterhin am Bildschirm, wenn die normale Ausgabe in eine Datei geht, und deshalb landen sie nicht versehentlich mitten in den Daten.

Rechte

ls -l beginnt mit zehn Zeichen, etwa -rw-r--r--: erst die Art (- Datei, d Verzeichnis), dann drei Dreiergruppen für Besitzer, Gruppe und alle übrigen, jeweils Lesen, Schreiben, Ausführen.

Ziffer Rechte entspricht
7 rwx lesen, schreiben, ausführen
6 rw- lesen, schreiben
5 r-x lesen, ausführen
4 r-- nur lesen

chmod 600 datei heisst also: Besitzer darf lesen und schreiben, sonst niemand etwas. Genau das gehört auf Dateien mit Zugangsdaten.

Kurz nachgeschlagen

Aufgabe Befehl
Zeilen zählen wc -l datei
in Dateien suchen grep -rn "muster" ordner/
Dateien finden find . -name "*.csv"
Spalte herausschneiden cut -d';' -f2 datei.csv
rechnen über eine Spalte awk -F';' 'NR>1 {s+=$2} END {print s}' datei.csv
sortieren und zählen sort \| uniq -c \| sort -rn
grosse Datei ansehen less datei, tail -f logdatei
Rechte setzen chmod 600 datei, chmod +x skript.sh
Platzbedarf du -sh ordner, df -h
Befehl finden which python, type ls

Beispiele

Frage und Datenlage

Ein Ordner mit zwei Unterordnern und drei Dateien. Wie sieht man die Struktur, die Details und den Anfang einer Datei?

Rechnung

lauf("ls -R")
.:
daten
logs
notiz.txt

./daten:
messung.csv

./logs:
anlage.log
lauf("ls -l logs")
total 4
-rw-r--r-- 1 runner runner 199 Sep 12 13:46 anlage.log
lauf("head -n 2 logs/anlage.log")
2026-03-01 INFO Start
2026-03-01 ERROR Sensor 3 antwortet nicht
lauf("wc -l logs/anlage.log daten/messung.csv")
  6 logs/anlage.log
  4 daten/messung.csv
 10 total

Output Zeile für Zeile

Befehl Ausgabe Erklärung
ls -R daten, logs, notiz.txt, danach je Unterordner dessen Inhalt rekursiv, mit dem Ordnernamen als Überschrift
ls -l logs eine Zeile mit Rechten, Besitzer, Gruppe, Grösse, Zeitpunkt, Name Die zehn Zeichen links sind die Rechte.
head -n 2 die ersten beiden Logzeilen tail nimmt das Ende, tail -f wartet auf Neues.
wc -l 6 für die Logdatei, 4 für die CSV-Datei, dazu die Summe Die CSV-Datei hat vier Zeilen, also drei Datensätze plus Kopfzeile.

Interpretation und Ergebnissatz

wc -l ist der schnellste Weg, die Grösse einer Datei in Datensätzen zu schätzen, und zugleich eine Falle: Die Kopfzeile zählt mit, und ein fehlender Zeilenumbruch am Dateiende kostet eine Zeile. Für eine Prüfung nach dem Import ist die Zahl trotzdem gut genug: Wer 4 Zeilen liest und 3 Datensätze importiert, weiss, dass es stimmt.

Der Ordner enthält zwei Unterordner und drei Dateien. Die Logdatei hat sechs Zeilen, die CSV-Datei vier, also drei Datensätze und eine Kopfzeile.

Frage und Datenlage

Wie viele Fehlerzeilen stehen im Log, in welchen Zeilen, und wo liegen die CSV-Dateien?

Rechnung

lauf("grep ERROR logs/anlage.log")
2026-03-01 ERROR Sensor 3 antwortet nicht
2026-03-02 ERROR Zeitüberschreitung
2026-03-03 ERROR Sensor 3 antwortet nicht
lauf("grep -c ERROR logs/anlage.log")        # nur zaehlen
3
lauf("grep -n 'Sensor' logs/anlage.log")     # mit Zeilennummer
2:2026-03-01 ERROR Sensor 3 antwortet nicht
6:2026-03-03 ERROR Sensor 3 antwortet nicht
lauf("grep -ri 'puffer' .")                  # rekursiv, ohne Gross- und Kleinschreibung
./logs/anlage.log:2026-03-02 WARN Puffer fast voll
lauf("find . -name '*.csv'")
./daten/messung.csv
lauf("find . -type d")
.
./logs
./daten

Output Zeile für Zeile

Befehl Ergebnis Erklärung
grep ERROR drei Zeilen die vollständigen Treffer
grep -c ERROR 3 nur die Anzahl
grep -n Sensor Zeilen 2 und 6 Dieselbe Meldung kommt zweimal vor, an zwei Tagen.
grep -ri puffer . die WARN-Zeile, mit Dateinamen davor -r durchsucht den Baum, -i ignoriert Gross- und Kleinschreibung. Bei mehreren Dateien stellt grep den Dateinamen voran.
find . -name '*.csv' ./daten/messung.csv sucht nach Namen, gibt Pfade aus
find . -type d ., ./daten, ./logs nur Verzeichnisse

Interpretation und Ergebnissatz

Die beiden Werkzeuge beantworten verschiedene Fragen und ergänzen sich: find . -name '*.log' -exec grep -c ERROR {} + zählt Fehler in allen Logdateien. In der Datenarbeit ist grep -c der schnellste Weg, eine Vermutung über eine Datei zu prüfen, bevor man sie überhaupt einliest.

Die Logdatei enthält drei Fehlerzeilen, die Meldung über Sensor 3 steht in den Zeilen 2 und 6. find liefert den Pfad der einzigen CSV-Datei.

Frage und Datenlage

Welche Meldungsarten kommen wie oft vor? Und wie schreibt man die Fehlerzeilen in eine eigene Datei?

Rechnung

lauf("cat logs/anlage.log | awk '{print $2}' | sort | uniq -c | sort -rn")
      3 ERROR
      2 INFO
      1 WARN
lauf("grep ERROR logs/anlage.log > fehler.txt")
lauf("wc -l < fehler.txt")
3
lauf("ls")
daten
fehler.txt
logs
notiz.txt
lauf("ls nichtvorhanden.txt 2>/dev/null; echo \"Rueckgabewert: $?\"")
Rueckgabewert: 2

Output Zeile für Zeile

Schritt der Kette Zwischenergebnis
cat logs/anlage.log sechs Zeilen
awk '{print $2}' nur die zweite Spalte: INFO, ERROR, INFO, ERROR, WARN, ERROR
sort gleiche Werte stehen nebeneinander
uniq -c zählt gleiche Nachbarn, deshalb muss vorher sortiert werden
sort -rn nach Anzahl absteigend
Ergebnis Wert
ERROR 3
INFO 2
WARN 1
wc -l < fehler.txt 3
ls nach der Umlenkung zusätzlich fehler.txt
fehlende Datei, Fehler unterdrückt keine Meldung, Rückgabewert 2

Interpretation und Ergebnissatz

Vier kleine Programme, von denen keines etwas von den anderen weiss, ergeben eine Auswertung. Das ist das Prinzip: Ein Werkzeug tut eine Sache, und die Pipe verbindet sie. Wichtig ist die Reihenfolge, denn uniq zählt nur benachbarte gleiche Zeilen; ohne sort davor kommt Unsinn heraus. Und der Rückgabewert ist der Grund, warum sich Befehle in Skripten verketten lassen: && führt den nächsten nur bei 0 aus.

Die Logdatei enthält dreimal ERROR, zweimal INFO und einmal WARN. Die Fehlerzeilen stehen nach der Umlenkung in fehler.txt, das drei Zeilen hat; der Zugriff auf eine fehlende Datei liefert den Rückgabewert 2.

Frage und Datenlage

Die CSV-Datei mit Semikolon als Trenner: Wie holt man eine Spalte heraus, wie summiert man sie, und wie zählt man je Standort?

Rechnung

lauf("cat daten/messung.csv")
id;wert;standort
1;12.4;Bern
2;9.8;Chur
3;14.1;Bern
lauf("cut -d';' -f2 daten/messung.csv")
wert
12.4
9.8
14.1
lauf("awk -F';' 'NR>1 {summe += $2} END {printf \"%.1f\\n\", summe}' daten/messung.csv")
36.3
lauf("tail -n +2 daten/messung.csv | cut -d';' -f3 | sort | uniq -c")
      2 Bern
      1 Chur

Output Zeile für Zeile

Befehl Ausgabe Erklärung
cat vier Zeilen samt Kopfzeile
cut -d';' -f2 wert, 12.4, 9.8, 14.1 Die Kopfzeile wird mitgeschnitten, cut kennt keine Tabellenstruktur.
awk mit NR>1 36.3 NR ist die Zeilennummer; NR>1 überspringt die Kopfzeile. Die Summe stimmt mit der Datenimport-Seite überein.
tail -n +2 \| cut \| sort \| uniq -c Bern 2, Chur 1 tail -n +2 beginnt bei Zeile 2, also ohne Kopfzeile.

Interpretation und Ergebnissatz

Für einen schnellen Blick in eine grosse Datei ist das unschlagbar: Zeilen zählen, Spalte prüfen, Werte summieren, alles ohne Import und ohne Arbeitsspeicher. Die Grenze ist erreicht, sobald Anführungszeichen, eingebettete Trennzeichen oder Zeilenumbrüche im Feld vorkommen; dann braucht es einen echten CSV-Leser, siehe Datenimport.

Die Werte summieren sich zu 36.3, und von den drei Messungen entfallen zwei auf Bern und eine auf Chur. cut schneidet die Kopfzeile mit, awk mit NR>1 und tail -n +2 lassen sie weg.

Frage und Datenlage

Eine Notizdatei und ein kleines Skript. Wie sehen die Rechte aus, und was ändert chmod?

Rechnung

lauf("ls -l notiz.txt")
-rw-r--r-- 1 runner runner 17 Sep 12 13:46 notiz.txt
lauf("chmod 600 notiz.txt && ls -l notiz.txt")
-rw------- 1 runner runner 17 Sep 12 13:46 notiz.txt
lauf("printf '#!/bin/sh\\necho hallo aus dem Skript\\n' > start.sh")
lauf("ls -l start.sh")
-rw-r--r-- 1 runner runner 36 Sep 12 13:46 start.sh
lauf("./start.sh 2>&1 || echo 'nicht ausfuehrbar'")
sh: 1: ./start.sh: Permission denied
nicht ausfuehrbar
lauf("chmod +x start.sh && ls -l start.sh")
-rwxr-xr-x 1 runner runner 36 Sep 12 13:46 start.sh
lauf("./start.sh")
hallo aus dem Skript

Output Zeile für Zeile

Schritt Rechte Erklärung
notiz.txt vorher -rw-r--r-- Besitzer darf lesen und schreiben, alle anderen nur lesen.
nach chmod 600 -rw------- Nur noch der Besitzer. Genau das gehört auf eine Datei mit Zugangsdaten.
start.sh neu geschrieben -rw-r--r-- Ein Skript ist zunächst nicht ausführbar.
Aufruf ohne Ausführrecht Fehlermeldung, nicht ausfuehrbar Die Datei existiert, das Recht fehlt.
nach chmod +x -rwxr-xr-x Das x ist gesetzt.
Aufruf danach hallo aus dem Skript

Interpretation und Ergebnissatz

Das x-Recht ist der Grund, warum ein frisch heruntergeladenes Skript nicht startet. Wichtiger für die Datenarbeit ist die andere Richtung: chmod 600 auf Dateien mit Zugangsdaten, damit sie nicht von anderen Konten auf demselben Server gelesen werden können. Was die Rechte nicht leisten, ist Schutz vor dem Besitzer selbst; ein versehentlich committetes Passwort bleibt ein Passwort in der Historie, siehe Git, Beispiel 6.

chmod 600 nimmt Gruppe und allen anderen jeden Zugriff auf die Notiz. Das neu geschriebene Skript ist erst nach chmod +x ausführbar und gibt dann hallo aus dem Skript aus.

Frage und Datenlage

Dieselbe Zählung der Fehlerzeilen, aber aus einem Skript heraus: Wie kommt die Ausgabe in eine Variable, und wie prüft man, ob der Befehl gelungen ist?

Rechnung

pfad <- file.path(demo, "logs", "anlage.log")

anzahl <- system2("grep", c("-c", "ERROR", shQuote(pfad)), stdout = TRUE)
anzahl
[1] "3"
as.integer(anzahl)
[1] 3
fehlgriff <- suppressWarnings(
  system2("grep", c("-c", "ERROR", "gibtsnicht.log"),
          stdout = TRUE, stderr = FALSE))
attr(fehlgriff, "status")
[1] 2
pfad = demo_py / "logs" / "anlage.log"

lauf = subprocess.run(["grep", "-c", "ERROR", str(pfad)],
                      capture_output=True, text=True)
print(repr(lauf.stdout), lauf.returncode)
'3\n' 0
print(int(lauf.stdout.strip()))
3
fehlgriff = subprocess.run(["grep", "-c", "ERROR", "gibtsnicht.log"],
                           capture_output=True, text=True)
print(fehlgriff.returncode, repr(fehlgriff.stdout), fehlgriff.stderr.strip()[:60])
2 '' grep: gibtsnicht.log: No such file or directory

Output Zeile für Zeile

Grösse R Python Erklärung
Ausgabe "3" als Text '3\n' Was von der Shell kommt, ist immer Text, samt Zeilenumbruch.
umgewandelt 3 3 as.integer() beziehungsweise int(...) nach strip().
Rückgabewert bei Erfolg kein status-Attribut 0
Rückgabewert beim Fehlgriff 2 im Attribut status 2, Ausgabe leer, Meldung auf stderr Bei grep heisst 1 “nichts gefunden” und 2 “Fehler”. Der Unterschied ist wichtig: Nichts gefunden ist kein Fehler.

In Python ist die Liste der Argumente der sichere Weg: Ohne shell=True interpretiert keine Shell die Zeichen, und ein Dateiname mit Leerzeichen oder Apostroph kann nichts anrichten. In R leistet system2 mit shQuote() dasselbe.

Interpretation und Ergebnissatz

Der Aufruf aus dem Skript lohnt für genau die Fälle, in denen die Shell schneller ist: eine Datei vorab zählen, ein Archiv entpacken, ein Kommandozeilenwerkzeug ansteuern. Zwei Regeln machen ihn sicher: Argumente als Liste übergeben statt eine Zeichenkette zusammenzubauen, und den Rückgabewert prüfen, statt anzunehmen, dass es geklappt hat.

Der Aufruf liefert die Ausgabe als Text ('3\n'), die erst umgewandelt werden muss. Beim Zugriff auf eine fehlende Datei ist der Rückgabewert 2 und die Ausgabe leer; die Meldung steht auf dem Fehlerkanal.

Verständnisfragen

Was ist der Unterschied zwischen grep muster datei und find . -name muster?

grep sucht in Dateien, find sucht nach Dateien
Richtig. grep durchsucht den Inhalt und gibt passende Zeilen aus, find durchsucht das Dateisystem nach Namen und gibt Pfade aus.
grep arbeitet nur auf einer Datei, find auf mehreren
grep nimmt ebenso viele Dateien entgegen.
find ist die neuere Variante von grep
Es sind zwei Werkzeuge für zwei verschiedene Fragen, die sich gut kombinieren lassen.

Warum ist > gefährlicher als >>?

Es überschreibt die Zieldatei ohne Rückfrage, und zwar bevor der Befehl links läuft
Richtig. Ein vertippter Dateiname löscht damit deren Inhalt, auch wenn der Befehl danach scheitert. >> hängt an.
Es schreibt auch Fehlermeldungen in die Datei
Das täte erst 2> oder &>.
Es ist langsamer bei grossen Dateien
Die Geschwindigkeit ist nicht das Thema.

cat log | awk '{print $2}' | uniq -c liefert seltsame Zahlen: Dieselbe Meldungsart erscheint mehrfach mit kleinen Anzahlen. Was fehlt?

Ein sort vor uniq
Richtig. uniq fasst nur benachbarte gleiche Zeilen zusammen. Ohne Sortierung wird jede Gruppe für sich gezählt.
Die Option -a bei uniq
Die gibt es nicht; das Verhalten ist so gewollt.
awk muss die Spalte anders adressieren
$2 ist richtig.

Eine CSV-Datei hat 1001 Zeilen laut wc -l. Wie viele Datensätze sind das?

Vermutlich 1000, weil die Kopfzeile mitgezählt wird
Richtig. Dazu kommt: Fehlt am Dateiende der Zeilenumbruch, zählt wc -l eine Zeile zu wenig. Für eine Kontrolle nach dem Import reicht es trotzdem.
Genau 1001
Nur ohne Kopfzeile.
Das lässt sich nicht sagen
Mit einem Blick auf head -n 1 schon.

Eine Datei mit Zugangsdaten liegt auf einem gemeinsam genutzten Server. Welche Rechte gehören darauf?

chmod 600, also nur der Besitzer darf lesen und schreiben
Richtig. Voreingestellt ist meist -rw-r--r--, und damit kann jedes andere Konto auf dem Server die Datei lesen.
chmod 777, damit alle Programme darauf zugreifen können
Das gibt jedem alle Rechte und ist das Gegenteil des Gewollten.
chmod +x, damit nur ausführbare Programme sie lesen
Das Ausführrecht hat mit Lesen nichts zu tun.

Ein Python-Skript ruft subprocess.run("grep -c ERROR " + dateiname, shell=True) auf. Was ist daran riskant?

Der Dateiname wird von der Shell interpretiert; Leerzeichen oder Sonderzeichen ändern den Befehl
Richtig. Mit einer Argumentliste ohne shell=True kann das nicht passieren. Es ist dieselbe Trennung von Wert und Anweisung wie bei SQL-Parametern.
grep ist für Skripte ungeeignet
Es eignet sich gut, nur der Aufruf ist hier unsicher.
Der Rückgabewert geht verloren
Er steht in returncode; das ist ein anderer Punkt.

Verlinkte Ressourcen