hack-hro wiki:
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

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

  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!

  ___              _   _
 / _ \ _   _  ___ | |_(_)_ __   __ _
| | | | | | |/ _ \| __| | '_ \ / _` |
| |_| | |_| | (_) | |_| | | | | (_| |
 \__\_\\__,_|\___/ \__|_|_| |_|\__, |
                               |___/

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

hack-hro wiki: Workshops/Shell (zuletzt geƤndert am 2015-04-08 18:49:17 durch josch)