#format rst :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] 1. **[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.** 2. 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. 3. 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 --------------------- .. code:: shell-session foo@erker:~$ history | while read id command; do echo "${#command} $command"; done | sort -n | tail -1 | cut -f 2- -d " " ---- Eingabe ------- .. code:: shell-session foo@erker:~$ history \ > | while read id command; do echo "${#command} $command"; done \ > | sort -n \ > | tail -1 \ > | cut -f 2- -d " " ... ---- Ergebnis -------- .. code:: c #include 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 ------------------------------------ .. code:: shell-session 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 -------------------------------------- .. code:: shell-session foo@erker:~$ x=Welt foo@erker:~$ echo "Hallo $x" Hallo Welt foo@erker:~$ echo "Hallo ${x}en" Hallo Welten ---- Variablenoperation: Zeichen zählen ---------------------------------- .. code:: shell-session foo@erker:~$ echo "Die '$x' besteht aus ${#x} Zeichen" Die 'Welt' besteht aus 4 Zeichen ---- Variablenoperation: Präfix oder Suffix ersetzen ----------------------------------------------- .. code:: shell-session 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) ---- .. code:: shell-session foo@erker:~$ x=12 foo@erker:~$ if [ "$x" -lt 15 ]; \ > | then echo "kleiner"; \ > | else echo "zu gross"; \ > | fi kleiner ---- .. code:: shell-session foo@erker:~$ for a in foo bar baz; do echo "$a"; done foo bar baz ---- .. code:: shell-session foo@erker:~$ x=0; while [ "$x" -lt 3 ]; do echo "$x"; x=$((x+1)); done 0 1 2 ---- Syntax: Funktionen ================== .. code:: shell-session 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 ------------------------- .. code:: shell-session 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 --------------------------- .. code:: shell-session foo@erker:~$ echo "Systemlaufzeit: $(cut -f 1 -d . /proc/uptime) Sekunden" Systemlaufzeit: 904953 Sekunden ---- Verschachtelte Kommando-Expansion --------------------------------- .. code:: shell-session 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 ----------------------------------------- .. code:: shell-session foo@erker:~$ x=$(date) foo@erker:~$ echo "$x" Mi 8. Apr 04:49:08 CEST 2015 ---- Fehlerausgabe eines Programms speichern --------------------------------------- .. code:: shell-session 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 -------------------------------- .. code:: shell-session 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: .. code:: c int main() { return 0; } Das Programm ``/bin/false`` dagegen könnte dies sein: .. code:: c int main() { return 1; } ---- Zeichenketten auswerten ----------------------- .. code:: shell-session foo@erker:~$ x=Welt foo@erker:~$ [ "$x" = "Leute" ]; echo $? 1 foo@erker:~$ [ "$x" != "Leute" ]; echo $? 0 ---- Zahlen auswerten ---------------- .. code:: shell-session foo@erker:~$ x=12 foo@erker:~$ [ "$x" -lt 15 ]; echo $? 0 foo@erker:~$ [ "$x" -eq 15 ]; echo $? 1 ---- Dateiinformationen auswerten ---------------------------- .. code:: shell-session 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. ---- .. code:: shell-session 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 --------------- .. code:: shell-session 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? .. code:: shell-session foo@erker:~$ type echo echo ist eine von der Shell mitgelieferte Funktion. Wer macht das? .. code:: shell-session foo@erker:~$ which echo /bin/echo Und sonst? .. code:: shell-session 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 --------------------------- .. code:: shell-session foo@erker:~$ echo $(date +%s) *Word Expansion* auflösen: ESC STRG-e .. code:: shell-session foo@erker:~$ echo echo 1428512497 ---- Shell-Expansion verstehen II ---------------------------- .. code:: shell-session foo@erker:~$ ls *Word Expansion* auflösen: ESC STRG-e .. code:: shell-session 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? ------------- .. code:: shell du -s * \ | sort -n \ | while read size item; do du -sh "$item" done ---- Was passiert? ------------- .. code:: shell history \ | while read id command; do echo "${#command} $command"; done \ | sort -n \ | tail -1 \ | cut -f 2- -d " " ---- Was passiert? ------------- .. code:: shell openvpn $(grep -v "^$" "$config_file" \ | grep -v "^#" \ | grep -v "^remote" \ | sed 's/^/--/' \ | tr '\n' ' ') --remote "$host" ---- Was passiert? ------------- .. code:: shell 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? ------------- .. code:: shell 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 ===================== 1. Quoting anwenden 2. Aufteilung in einzelne Kommandos: ``& && ( ) ; ;; | || \n`` 3. Word Expansion (Variablen, Kommandos, Berechnungen) 4. Field Splitting 5. Pathname Expansion 6. Quote Removal 7. Ausführung! ---- :: ___ _ _ / _ \ _ _ ___ | |_(_)_ __ __ _ | | | | | | |/ _ \| __| | '_ \ / _` | | |_| | |_| | (_) | |_| | | | | (_| | \__\_\\__,_|\___/ \__|_|_| |_|\__, | |___/ ---- .. code:: shell-session foo@erker:~$ x="echo bar; ls"; echo $x ---- .. code:: shell-session foo@erker:~$ x="echo bar; ls"; echo $x echo bar; ls ---- .. code:: shell-session foo@erker:~$ x="echo bar; ls"; echo $x echo bar; ls foo@erker:~$ x="echo bar; ls"; $x ---- .. code:: shell-session foo@erker:~$ x="echo bar; ls"; echo $x echo bar; ls foo@erker:~$ x="echo bar; ls"; $x bar; ls ---- .. code:: shell-session 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" ---- .. code:: shell-session 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. ---- .. code:: shell-session 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" ---- .. code:: shell-session 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! ---- .. code:: shell-session foo@erker:~$ x="-2 /etc/passwd"; tail $x ---- .. code:: shell-session 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 ---- .. code:: shell-session 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" ---- .. code:: shell-session 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 / (_) | | | (_| | | |___ > <| |_) | (_| | | | \__ \ | (_) | | | |_ \_/\_/ \___/|_| \__,_| |_____/_/\_\ .__/ \__,_|_| |_|___/_|\___/|_| |_(_) |_| ____ _ | __ ) ___ _ __ ___ ___| |__ _ __ _ _ _ __ __ _ ___ _ __ | _ \ / _ \ '__/ _ \/ __| '_ \| '_ \| | | | '_ \ / _` |/ _ \ '_ \ | |_) | __/ | | __/ (__| | | | | | | |_| | | | | (_| | __/ | | | |____/ \___|_| \___|\___|_| |_|_| |_|\__,_|_| |_|\__, |\___|_| |_| |___/ ---- .. code:: shell-session foo@erker:~$ x=4 ---- .. code:: shell-session foo@erker:~$ x=4 foo@erker:~$ echo $((x * 3)) ---- .. code:: shell-session foo@erker:~$ x=4 foo@erker:~$ echo $((x * 3)) 12 ---- .. code:: shell-session foo@erker:~$ x=4 foo@erker:~$ echo $((x * 3)) 12 foo@erker:~$ x=8 echo $((x * 3)) ---- .. code:: shell-session foo@erker:~$ x=4 foo@erker:~$ echo $((x * 3)) 12 foo@erker:~$ x=8 echo $((x * 3)) 12 ---- .. code:: shell-session 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)) ---- .. code:: shell-session 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. ---- .. code:: shell-session foo@erker:~$ x=$(date +%s); sleep 3; echo $(( $(date +%s) - x)) ---- .. code:: shell-session 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``