title: | Shell als täglicher Begleiter |
---|---|
Author: | Lars Kruse |
css: | https://wiki.hack-hro.de/talk_styles/assets/css/style2.css |
description: | Hackspace Rostock e.V. |
data-scale: | 0.7 |
data-transition-duration: | |
1 |
________ ________ / ______ \ / ______ \ / / \ \ / / \ \ / / \ " / \ \ | | \ / | | | | ____ _ " _ _ | | | | / ___|| |__ ___| | | | | | | \___ \| '_ \ / _ \ | | | | | | ___) | | | | __/ | | | | | | |____/|_| |_|\___|_|_| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | |
Überblick
- Erscheinungsformen
- Programmaufrufe
- Variablen
- Interaktive Details
- Pipes!
- Optional: Anatomie des shell-Parsers
Wortfindung
shell: n. [orig. Multics techspeak, widely propagated via Unix]
- [techspeak] The command interpreter used to pass commands to an operating system; so called because it is the part of the operating system that interfaces with the outside world.
- More generally, any interface program that mediates access to a special resource or server for convenience, efficiency, or security reasons; for this meaning, the usage is usually a shell around whatever. This sort of program is also called a wrapper.
- A skeleton program, created by hand or by another program (like, say, a parser generator), which provides the necessary incantations to set up some task and the control flow to drive it (the term driver is sometimes used synonymously). The user is meant to fill in whatever code is needed to get real work done. This usage is common in the AI and Microsoft Windows worlds, and confuses Unix hackers.
Historical note: Apparently, the original Multics shell (sense 1) was so called because it was a shell (sense 3); it ran user programs not by starting up separate processes, but by dynamically linking the programs into its own code, calling them as subroutines, and then dynamically de-linking them on return. The VMS command interpreter still does something very like this.
Jargon File
Themeneinschränkung
- textbasierte Unix-Shells
- speziell: ash / bash / dash
- interaktive Nutzung und Programmierung
Der göttliche Kleber
All die schönen Werkzeuge (z.B. GNU coreutils) wären auf sich allein gestellt, gäb es keine Shell, die folgendes tut:
- Prozesssteuerung
- Datenflüsse lenken
- Variablenraum und Kommando-Historie verwalten
Shell als Schnittstelle
Aus Sicht der Shell:
- Eingabe: Mensch schreibt Text (Kommandos)
- Ergebnis: passend abgeleitete Programmaufrufe (man execve)
Kernaufgabe: Interpretation menschlicher Eingaben und Steuerung der resultierenden Prozesse.
Eingabe
foo@erker:~$ history | while read id command; do echo "${#command} $command"; done | sort -n | tail -1 | cut -f 2- -d " "
Eingabe
foo@erker:~$ history \ > | while read id command; do echo "${#command} $command"; done \ > | sort -n \ > | tail -1 \ > | cut -f 2- -d " " ...
Ergebnis
#include <unistd.h> int execve(const char *filename, char *const argv[], char *const envp[]);
Syntax: Variablen
Die Shell kennt nur zwei Datentypen:
- Zeichenketten
- Wahrheitswerte
Es gibt boolesche und mathmatische Operatoren.
Variablen: Zahlen sind Zeichenketten
foo@erker:~$ x=5 foo@erker:~$ echo "$x" 5 foo@erker:~$ echo 4 + x 4 + x foo@erker:~$ echo $((x + 2)) 7
Variablen: Abgrenzung von der Umgebung
foo@erker:~$ x=Welt foo@erker:~$ echo "Hallo $x" Hallo Welt foo@erker:~$ echo "Hallo ${x}en" Hallo Welten
Variablenoperation: Zeichen zählen
foo@erker:~$ echo "Die '$x' besteht aus ${#x} Zeichen" Die 'Welt' besteht aus 4 Zeichen
Variablenoperation: Präfix oder Suffix ersetzen
foo@erker:~$ x=Ausgabe.pdf foo@erker:~$ echo "Umwandlung: pdf2ps '$x' '${x%.pdf}.ps'" Umwandlung: pdf2ps 'Ausgabe.pdf' 'Ausgabe.ps' foo@erker:~$ x=/dev/null foo@erker:~$ echo "Relativ: ${x#/} und absolut: $x" Relativ: dev/null und absolut: /dev/null
Syntax: Flusskontrolle
Die üblichen Verdächtigen sind verfügbar:
- Verzweigungen (if, case)
- Schleifen (for, while)
foo@erker:~$ x=12 foo@erker:~$ if [ "$x" -lt 15 ]; \ > | then echo "kleiner"; \ > | else echo "zu gross"; \ > | fi kleiner
foo@erker:~$ for a in foo bar baz; do echo "$a"; done foo bar baz
foo@erker:~$ x=0; while [ "$x" -lt 3 ]; do echo "$x"; x=$((x+1)); done 0 1 2
Syntax: Funktionen
foo@erker:~$ get_uptime_seconds() { cut -f 1 -d . /proc/uptime; } foo@erker:~$ echo "Systemlaufzeit: $(get_uptime_seconds) Sekunden" Systemlaufzeit: 905923 Sekunden
Funktionen mit Parametern
foo@erker:~$ first_line() { head -1 "$1"; } foo@erker:~$ first_line /etc/passwd root:x:0:0:root:/root:/bin/bash
Programmaufrufe: Eingabe
Bei einem Programmaufruf gibt es drei Informationsflüsse:
- Parameter
- Standardeingabe
- Umgebungsvariablen
Programmaufrufe: Rückgabe
Jeder Programmaufruf liefert drei Informationen zurück:
- Standardausgabe (Zeichenkette)
- Fehlerausgabe (Zeichenkette)
- Exit-Code (Ganzzahl; Null -> Erfolg)
Einfache Kommando-Expansion
foo@erker:~$ echo "Systemlaufzeit: $(cut -f 1 -d . /proc/uptime) Sekunden" Systemlaufzeit: 904953 Sekunden
Verschachtelte Kommando-Expansion
foo@erker:~$ gnu_tage=$(( $(date --date="09/27/1983" +%s) / 3600 / 24)) foo@erker:~$ echo "Heute ist der Tag $gnu_tage des GNU-Projekts." Heute ist der Tag 5016 des GNU-Projekts.
Standardausgabe eines Programms speichern
foo@erker:~$ x=$(date) foo@erker:~$ echo "$x" Mi 8. Apr 04:49:08 CEST 2015
Fehlerausgabe eines Programms speichern
foo@erker:~$ fehler=$(ls NOTHING 2>&1 >/dev/null) foo@erker:~$ echo "$fehler" ls: Zugriff auf NOTHING nicht möglich: Datei oder Verzeichnis nicht gefunden
Wahrheitswerte
- Wahrheitswerte existieren ausschließlich als Exitcodes von Programmen
- Hilfsmittel für Zeichenketten-, Zahlen- und Dateiprüfungen: test bzw. [
- siehe man test bzw. man "[" (synonym)
Exit-Code eines Programms prüfen
foo@erker:~$ true; echo "$?" 0 foo@erker:~$ false; echo "$?" 1
Exit-Code aus Sicht des Programms
Das Programm /bin/true sieht etwa folgendermaßen aus:
int main() { return 0; }
Das Programm /bin/false dagegen könnte dies sein:
int main() { return 1; }
Zeichenketten auswerten
foo@erker:~$ x=Welt foo@erker:~$ [ "$x" = "Leute" ]; echo $? 1 foo@erker:~$ [ "$x" != "Leute" ]; echo $? 0
Zahlen auswerten
foo@erker:~$ x=12 foo@erker:~$ [ "$x" -lt 15 ]; echo $? 0 foo@erker:~$ [ "$x" -eq 15 ]; echo $? 1
Dateiinformationen auswerten
foo@erker:~$ [ -e /etc/passwd ]; echo $? 0 foo@erker:~$ [ -d /etc/passwd ]; echo $? 1
Zusammenfassung: Logische Auswertung
man test
Logische Verknüpfungen
Mittels && und || lassen sich im Sinne unvollständiger boolscher Auswertung Aktionen verknüpfen.
if-Konstruktionen erfüllen denselben Zweck - je nach Komplexität sind sie eventuell übersichtlicher.
foo@erker:~$ [ -d /etc/apache2 ] && echo "Apache2 ist installiert" Apache2 ist installiert foo@erker:~$ if [ -d /etc/apache2 ]; then echo "Apache2 ist installiert"; fi Apache2 ist installiert
Selektor-Muster
foo@erker:~$ minimum() { [ "$1" -lt "$2" ] && echo "$1" || echo "$2"; } foo@erker:~$ minimum 12 8 8 foo@erker:~$ minimum 12 214 12
Introspektion
Was ist das?
foo@erker:~$ type echo echo ist eine von der Shell mitgelieferte Funktion.
Wer macht das?
foo@erker:~$ which echo /bin/echo
Und sonst?
foo@erker:~$ set | wc -l 7341
Interaktive Funktionen
Die interaktive bash unterstützt vielerlei Funktionen für effizientes Tun. Die einfachere dash dagegen enthält nicht diese Fähigkeiten.
Die interaktiven Funktionen werden von der readline-Bibliothek bereitgestellt.
META: im bash-Manual wird die Meta-Taste als M verwendet - die ist entweder ESC oder ALT.
Eingabemodi
- Standard: Emacs-Modus
- alternativ verwendbar: VI-Modus (set -o vi)
- anschließend verhält sich die Kommandozeile ähnlich zu VIM
Schnelle Bewegung
- M-b: ein Wort zurück
- M-f: ein Wort vorwärts
- STRG-a: Beginn der Zeile
- STRG-w: Wort löschen
Autovervollständigung
- Tab-Taste: ein oder mehrmals betätigen, um möglichst weit zu vervollständigen
- typischerweise Vervollständigung basierend auf existierenden Dateinamen
- Vervollständigung via bash-completion für viele Programme
Verwendung der History
- Cursor-Tasten hoch/runter: Historie durchblättern
- history: letzte Kommandozeilen anzeigen
- STRG-R: inkrementelle Suche
- M-STRG-y: erster Parameter der vorherigen Kommandozeile
- M-. (Punkt): der letzte Parameter der vorherigen Kommandozeile
Shell-Expansion verstehen I
foo@erker:~$ echo $(date +%s)
Word Expansion auflösen: ESC STRG-e
foo@erker:~$ echo echo 1428512497
Shell-Expansion verstehen II
foo@erker:~$ ls
Word Expansion auflösen: ESC STRG-e
foo@erker:~$ ls --color=auto
Komplexe Mehrzeiler komfortabel komponieren
STRG-x STRG-e
- öffne die aktuelle Zeile in einem Editor
- nach Beenden des Editors wird die Zeile / das Skript ausgeführt
Verknüpfung von Datenströmen: Pipelines
Das Kern-Feature der Shell: die Funktionalität verschiedenster Programme vereinen.
Was passiert?
du -s * \ | sort -n \ | while read size item; do du -sh "$item" done
Was passiert?
history \ | while read id command; do echo "${#command} $command"; done \ | sort -n \ | tail -1 \ | cut -f 2- -d " "
Was passiert?
openvpn $(grep -v "^$" "$config_file" \ | grep -v "^#" \ | grep -v "^remote" \ | sed 's/^/--/' \ | tr '\n' ' ') --remote "$host"
Was passiert?
ssh sprachrohr "tail -100 /var/log/asterisk/cdr-csv/Master.csv" \ | cut -f 3 -d "," \ | cut -f 2 -d '"' \ | sort \ | uniq -c \ | sort -n
Was passiert?
- ssh sprachrohr "tail -100 /var/log/asterisk/cdr-csv/Master.csv"
awk ' BEGIN { FS=","; x=0; } { x += $15; } END { print x; }'
Was passiert?
find ~ -type f -mtime -10 -name "*.png" \ | grep -v "^[0-9]\{8\}-[0-9]\{4\}_" \ | while read fname; do prefix=$(date --reference "$fname" +%Y%m%d-%H%M%S) new_name="$(dirname "$fname")${prefix}_$(basename "${fname%.png}").png" mv "$fname" "$new_name" done
Interpretationsablauf
- Quoting anwenden
- Aufteilung in einzelne Kommandos: & && ( ) ; ;; | || \n
- Word Expansion (Variablen, Kommandos, Berechnungen)
- Field Splitting
- Pathname Expansion
- Quote Removal
- Ausführung!
___ _ _ / _ \ _ _ ___ | |_(_)_ __ __ _ | | | | | | |/ _ \| __| | '_ \ / _` | | |_| | |_| | (_) | |_| | | | | (_| | \__\_\\__,_|\___/ \__|_|_| |_|\__, | |___/
foo@erker:~$ x="echo bar; ls"; echo $x
foo@erker:~$ x="echo bar; ls"; echo $x echo bar; ls
foo@erker:~$ x="echo bar; ls"; echo $x echo bar; ls foo@erker:~$ x="echo bar; ls"; $x
foo@erker:~$ x="echo bar; ls"; echo $x echo bar; ls foo@erker:~$ x="echo bar; ls"; $x bar; ls
foo@erker:~$ x="echo bar; ls"; echo $x echo bar; ls foo@erker:~$ x="echo bar; ls"; $x bar; ls foo@erker:~$ x="echo bar; ls"; "$x"
foo@erker:~$ x="echo bar; ls"; echo $x echo bar; ls foo@erker:~$ x="echo bar; ls"; $x bar; ls foo@erker:~$ x="echo bar; ls"; "$x" bash: echo bar; ls: Kommando nicht gefunden.
foo@erker:~$ x="echo bar; ls"; echo $x echo bar; ls foo@erker:~$ x="echo bar; ls"; $x bar; ls foo@erker:~$ x="echo bar; ls"; "$x" bash: echo bar; ls: Kommando nicht gefunden. foo@erker:~$ x="echo bar; ls"; eval "$x"
foo@erker:~$ x="echo bar; ls"; echo $x echo bar; ls foo@erker:~$ x="echo bar; ls"; $x bar; ls foo@erker:~$ x="echo bar; ls"; "$x" bash: echo bar; ls: Kommando nicht gefunden. foo@erker:~$ x="echo bar; ls"; eval "$x" bar aufnahmen conditioning.png debugger.png
_____ _ _ _ ____ _ _ _ _ _ | ___(_) ___| | __| | / ___| _ __ | (_) |_| |_(_)_ __ __ _ | |_ | |/ _ \ |/ _` | \___ \| '_ \| | | __| __| | '_ \ / _` | | _| | | __/ | (_| | ___) | |_) | | | |_| |_| | | | | (_| | |_| |_|\___|_|\__,_| |____/| .__/|_|_|\__|\__|_|_| |_|\__, | |_| |___/ _ __ __ _ _ __ __ _ ___| |__ \ \ / /__ _ __ __| | | '_ \ / _` |/ __| '_ \ \ \ /\ / / _ \| '__/ _` | | | | | (_| | (__| | | | \ V V / (_) | | | (_| | |_| |_|\__,_|\___|_| |_| \_/\_/ \___/|_| \__,_| _____ _ | ____|_ ___ __ __ _ _ __ ___(_) ___ _ __ | _| \ \/ / '_ \ / _` | '_ \/ __| |/ _ \| '_ \ | |___ > <| |_) | (_| | | | \__ \ | (_) | | | | |_____/_/\_\ .__/ \__,_|_| |_|___/_|\___/|_| |_| |_|
- Variablen können in mehrere Felder separiert werden
- besonders beliebt und überraschend bei Dateinamen mit Leerzeichen
- Quoting hilft!
foo@erker:~$ x="-2 /etc/passwd"; tail $x
foo@erker:~$ x="-2 /etc/passwd"; tail $x liquidsoap:x:144:159::/usr/share/liquidsoap:/bin/false foo:x:1008:1008:,,,:/home/foo:/bin/bash
foo@erker:~$ x="-2 /etc/passwd"; tail $x liquidsoap:x:144:159::/usr/share/liquidsoap:/bin/false foo:x:1008:1008:,,,:/home/foo:/bin/bash foo@erker:~$ x="-2 /etc/passwd"; tail "$x"
foo@erker:~$ x="-2 /etc/passwd"; tail $x liquidsoap:x:144:159::/usr/share/liquidsoap:/bin/false foo:x:1008:1008:,,,:/home/foo:/bin/bash foo@erker:~$ x="-2 /etc/passwd"; tail "$x" tail: Option in ungültigen Kontext benutzt – 2
__ __ _ _____ _ \ \ / /__ _ __ __| | | ____|_ ___ __ __ _ _ __ ___(_) ___ _ __ _ \ \ /\ / / _ \| '__/ _` | | _| \ \/ / '_ \ / _` | '_ \/ __| |/ _ \| '_ \(_) \ V V / (_) | | | (_| | | |___ > <| |_) | (_| | | | \__ \ | (_) | | | |_ \_/\_/ \___/|_| \__,_| |_____/_/\_\ .__/ \__,_|_| |_|___/_|\___/|_| |_(_) |_| ____ _ | __ ) ___ _ __ ___ ___| |__ _ __ _ _ _ __ __ _ ___ _ __ | _ \ / _ \ '__/ _ \/ __| '_ \| '_ \| | | | '_ \ / _` |/ _ \ '_ \ | |_) | __/ | | __/ (__| | | | | | | |_| | | | | (_| | __/ | | | |____/ \___|_| \___|\___|_| |_|_| |_|\__,_|_| |_|\__, |\___|_| |_| |___/
foo@erker:~$ x=4
foo@erker:~$ x=4 foo@erker:~$ echo $((x * 3))
foo@erker:~$ x=4 foo@erker:~$ echo $((x * 3)) 12
foo@erker:~$ x=4 foo@erker:~$ echo $((x * 3)) 12 foo@erker:~$ x=8 echo $((x * 3))
foo@erker:~$ x=4 foo@erker:~$ echo $((x * 3)) 12 foo@erker:~$ x=8 echo $((x * 3)) 12
foo@erker:~$ x=4 foo@erker:~$ echo $((x * 3)) 12 foo@erker:~$ x=8 echo $((x * 3)) 12 foo@erker:~$ x=8; echo $((x * 3))
foo@erker:~$ x=4 foo@erker:~$ echo $((x * 3)) 12 foo@erker:~$ x=8 echo $((x * 3)) 12 foo@erker:~$ x=8; echo $((x * 3)) 24
____ _ _ _ _ / ___| ___ __ _ _ _ ___ _ __ | |_(_) ___| | | ___ \___ \ / _ \/ _` | | | |/ _ \ '_ \| __| |/ _ \ | |/ _ \ ___) | __/ (_| | |_| | __/ | | | |_| | __/ | | __/ |____/ \___|\__, |\__,_|\___|_| |_|\__|_|\___|_|_|\___| |_| _____ _ | ____|_ ___ __ __ _ _ __ ___(_) ___ _ __ | _| \ \/ / '_ \ / _` | '_ \/ __| |/ _ \| '_ \ | |___ > <| |_) | (_| | | | \__ \ | (_) | | | | |_____/_/\_\ .__/ \__,_|_| |_|___/_|\___/|_| |_| |_|
Ein Kommando wird immer erst zum Zeitpunkt seiner Ausführung expandiert.
foo@erker:~$ x=$(date +%s); sleep 3; echo $(( $(date +%s) - x))
foo@erker:~$ x=$(date +%s); sleep 3; echo $(( $(date +%s) - x)) 3
Hinweise zum Nachschlagen
- kurz und knapp: man dash
- übertrieben ausführlich: man bash