HOWTO zur Standard-Ein/Ausgabe in der Shell

(C) 2004-2017 T.Birnthaler/H.Gottschalk <howtos(at)ostc.de>
              OSTC Open Source Training and Consulting GmbH
              www.ostc.de

$Id: shell-stdio-HOWTO.txt,v 1.7 2019/11/26 19:37:07 tsbirn Exp $

Dieses Dokument beschreibt die Eigenschaften der Standard-Ein/AusgabekanÀle von
Linux-Programmen/Prozessen und ihre Steuerung per Umlenkung und Pipen durch die
Shell.

Inhaltsverzeichnis

1) Standard-KanÀle
2) Filter und Pipeline
3) Umlenkung (Redirection) und Pipen
3.1) Fehlermöglichkeiten
3.2) Beispiele fÃŒr Dateiumlenkung
3.3) Beispiele fÃŒr Pipes
3.4) Option "noclobber"
4) Here-Dokument
5) Linux-Filterprogramme
6) Grosse Beispiele
6.1) WorthÀufigkeit ermitteln
6.2) Nicht in Wörterbuch enthaltene Worte ausgeben

1) Standard-KanÀle   (Toc)

Jedes Linux-Kommando bzw. jeder aus einem Linux-Kommando erzeugte Linux-Prozess
kennt 3 Standard-KanÀle zur Daten-Ein/Ausgabe, die von der Shell automatisch
beim Erzeugen des Prozesses angelegt werden (zum Lesen bzw. Schreiben geöffnete
Dateien):

  +------------------+-------------+---+------------+--------------------------+
  | Bezeichnung      | Dateiname   | n | Standard   | Einsatzzweck             |
  +------------------+-------------+---+------------+--------------------------+
  | Standard-Eingabe | /dev/stdin  | 0 | Tastatur   | Eingaben                 |
  | Standard-Ausgabe | /dev/stdout | 1 | Bildschirm | Normale Ausgaben (Daten) |
  | Fehlerkanal      | /dev/stderr | 2 | Bildschirm | Fehlermeldungen          |
  +------------------+-------------+---+------------+--------------------------+

Grafisch:
                                  CMD
                   stdin (0)  +---------+  stdout (1)
                  ----------->| Prozess |------------>
                              +---------+
                                   |
                                   v
                               stderr (2)

Diese 3 KanÀle sind standardmÀßig wie angegeben mit der Tastatur oder dem
Bildschirm verbunden, können aber per Umlenkung oder Pipe beliebig mit anderen
Dateien oder Kommandos verbunden werden

Die Kommandos lesen dann nicht mehr von der Tastatur bzw. schreiben nicht mehr
auf den Bildschirm, sondern lesen/schreiben von/auf Datei bzw. von/an andere
Kommandos (sie bemerken die Umlenkung der KanÀle aber NICHT, da diese von der
Shell eingerichtet werden, BEVOR das Kommando ÃŒberhaupt gestartet wird).

HINWEIS: Die Shell ist ebenfalls nur ein Kommando, das Eingaben von stdin liest
(Kommandos) und Ausgaben auf stdout/stderr schreibt (beim Login eingerichtet).
Ein/AusgabekanÀle werden an den Kindprozess vererbt (in Shell aufgerufenes
Kommando), wenn beim Aufruf keine Umlenkung und kein Pipen erfolgt.

2) Filter und Pipeline   (Toc)

Programme die Daten von der Standard-Eingabe lesen, sie filtern und
manipulieren und auf der Standard-Ausgabe wieder ausgeben, werden auch "Filter"
genannt, weil sie wie ein "Kaffeefilter" arbeiten. Typische Filter sind z.B.
folgende Programme:

  grep, sort, uniq, tail, head, cut, paste, split, wc, sed, awk, tee, tr, ...

Mit Hilfe der "Filter" und Pipes kann das "Baukastenprinzip" von Linux sehr
einfach realisiert werden. Mehrere ÃŒber Pipes verbundene Kommandos ergeben ein
kombiniertes (komplexes) Filter, eine sogenannten "Pipeline" (Filterkette,
Verarbeitungskette). Die Daten fließen vom 1. bis zum letzten Kommando durch
die gesamte Verarbeitungskette und werden dabei verarbeitet.

  CMD1 | CMD2 | ... | CMDn-1 | CMDn

Eventuell wird die Eingabe von Datei gelesen und die Ausgabe auf Datei
geschrieben:

  CMD1 < INPUT | CMD2 | ... | CMDn-1 | CMDn > OUTPUT

Der Fehlerkanal jedes Kommandos wird dabei nicht umgelenkt. Die Fehlermeldungen
erscheinen also auf dem Terminal, sie werden nicht an das nÀchste Kommando
weitergeleitet:

HINWEIS: Pipes haben eine relativ kleine Größe (4/8/16 KByte), liegen im
Speicher und synchronisieren ÃŒber den Datenfluß damit verbundene Prozesse
(laufen parallel). Sie belegen keinen Plattenplatz und sind extrem schnell.

                 CMD1      stdout (1) = stdin (0)     CMD2
   stdin (0)  +----------+     ,-.----------.     +----------+  stdout (1)
  ----------->| Prozess1 |----(-> )  PIPE  --)--->| Prozess2 |------------>
              +----------+     `-'----------'     +----------+
                   |                                  |
                   v                                  v
               stderr (2)                         stderr (2)

HINWEIS: Da Linux kaum BinÀrdaten kennt, sondern fast alle Daten in
zeilenorientierten (ASCII)-Textdateien ablegt (Zeilen werden durch Newline "\n"
begrenzt), werden solche Textdateien als Standard-Datenformat von fast allen
Kommandos gelesen und erzeugt.

3) Umlenkung (Redirection) und Pipen   (Toc)

Folgende Syntax zur Ein/Ausgabe-Umlenkung gibt es:

  +------------------+---------------------------------------------------------+
  | Syntax           | Bedeutung                                               |
  +------------------+---------------------------------------------------------+
  | CMD < FILE       | stdin von Datei FILE lesen                              |
  | CMD << EOF       | stdin von CMD-Zeile bis Zeile mit EOF                   |
  | ...TEXT...       |   beliebiger Text (Here-Dokument)                       |
  | ...TEXT...       |   beliebiger Text (Here-Dokument)                       |
  | EOF              | Text "EOF" von oben am Zeilenanfang!                    |
  +------------------+---------------------------------------------------------+
  | CMD > FILE       | stdout auf Datei FILE schreiben (vorher löschen!)       |
  | CMD >> FILE      | stdout an Datei FILE anhÀngen                           |
  | CMD 2> FILE      | stderr auf Datei FILE schreiben (vorher löschen!)       |
  | CMD 2>> FILE     | stderr an Datei FILE anhÀngen                           |
  +------------------+---------------------------------------------------------+
  | CMD 1>&2         | stdin zu stderr hinzufÃŒgen (kombinieren)                |
  | CMD 2>&1         | stderr zu stdin hinzufÃŒgen (kombinieren)                |
  | CMD &> FILE      | stdout+stderr auf Datei FILE schreiben (vorher leeren!) |
  |                  |   entspricht "> FILE 2>&1" oder "2> FILE 1>&2" (bash)   |
  | CMD >& FILE      | stdout+stderr auf Datei FILE schreiben         (csh)    |
  +------------------+---------------------------------------------------------+
  | CMD1 | CMD2      | stdout von CMD1 mit stdin von CMD2 verbinden            |
  | CMD1 2>&1 | CMD2 | stdout+stderr von CMD1 mit stdin von CMD2 verb. (bash)  |
  | CMD1 |& CMD2     | stdout+stderr von CMD1 mit stdin von CMD2 verb. (csh)   |
  +------------------+---------------------------------------------------------+
  | CMD >| FILE      | stdout auf Datei FILE schreiben (auch bei noclobber!)   |
  +------------------+---------------------------------------------------------+

HINWEIS:

* Eine Umlenkung verknÃŒpft ein Kommando und eine Datei,
  Eine Pipe verknÃŒpft zwei Kommandos.

* Nach dem Pipe-Symbol darf ein Zeilenumbruch erfolgen,
  nach dem Umlenk-Symbol darf KEIN Zeilenumbruch erfolgen.

* Eine Umlenkung darf irgendwo vor/im/hinter dem Kommando stehen:

    CMD OPT PARARM < FILE   # OK (typische Schreibweise)
    < FILE CMD OPT PARARM   # OK
    CMD OPT < FILE PARARM   # OK

* Eine Pipe muss zwischen 2 Kommandos stehen (werden von ihr verknÃŒpft):

    CMD1 OPT1 PARAM1 | CMD2 OPT2 PARAM2   # OK (einzeilig)
    CMD1 OPT2 PARAM1 |                    # OK (zweizeilig)
      CMD2 OPT1 PARAM2                    #

* Ohne Eingabeumlenkung (stdin) lesen praktisch alle Kommandos (Filter) die
  nach dem Kommando aufgelisteten (Eingabe)Dateien automatisch ein:

    CMD FILE...

* Ohne Angabe von Eingabeumlenkung, Pipe und Eingabedatei liest ein Kommando
  automatisch von der Tastatur. Das Dateiende wÀhrend der manuellen Eingabe
  ist durch "Strg/Ctrl-D" anzugeben.

* Der explizite Name "-" steht als Dateiname fÃŒr Standardeingabe/ausgabe:

    CMD                # Liest von Standardeingabe (Tastatur)
    CMD -              # Liest von Standardeingabe (Tastatur)
    CMD /dev/stdin     # Liest von Standardeingabe (Tastatur)
    CMD < /dev/stdin   # Liest von Standardeingabe (Tastatur)

* Die 3 Standard-Ein/ausgabekanÀle haben pro Prozess auch die festen Dateinamen
  "/dev/stdin", "/dev/stdout" und "/dev/stderr".

    CMD                                             # Standardverhalten
    CMD < /dev/stdin > /dev/stdout 2> /dev/stderr   # Analog

* Der Name "/dev/null" kann als (leere) Eingabedatei angegeben werden, wenn
  diese fÃŒr ein CMD notwendig ist, aber das Lesen sofort beendet werden soll:

    CMD /dev/null

* Der Name "/dev/null" kann als Ausgabedatei angegeben werden, wenn Ausgaben
  ignoriert (weggeworfen) werden sollen (verschluckt die Daten einfach):

    CMD > /dev/null                # Standardausgabe wegwerfen
    CMD 2> /dev/null               # Standardfehlerausgabe wegwerfen
    CMD > /dev/null 2> /dev/null   # Beide Standardausgaben wegwerfen

  Er wird hÀufig zum Ignorieren der Fehlermeldungen angewendet oder wenn nur
  der Exit-Status eines Kommandos relevant ist (nicht aber seine Ausgaben):

    rm /tmp/FILE 2> /dev/null          # Fehlermeldungen wegwerfen
    if CMD > /dev/null 2> /dev/null    # Nur Exit-Status relevant
    then
        ...
    fi

3.1) Fehlermöglichkeiten   (Toc)

* Jedem Kanal ist per Umlenkung/Pipe nur EINE Quelle/Ziel zuordenbar:

    grep "text" < datei1 datei2       # Fehler (zweideutig)
    grep "text" < datei1              # OK
    grep "text" datei2                # OK
    cat /etc/passwd > datei | less    # Fehler (zweideutig)
    cat /etc/passwd > datei           # OK
    cat /etc/passwd | less            # OK

* Ein Filter kann eine Datei nur lesen ODER schreiben
  (nicht beides gleichzeitig):

    grep < datei > datei     # Falsch ("datei" anschliessend leer)
    grep < datei > datei2    # OK

* Eine Datei kann nur fÃŒr EINE Umlenkung als Ziel verwendet werden:

    grep > datei 2> datei    # Falsch (Kanal 1 oder 2 gewinnt)
    grep > datei 2> datei2   # OK (zwei verschiedene Dateien)
    grep > datei 2>&1        # OK (eine Datei, AusgabekanÀle zusammenfÌgen)
    grep 2> datei 1>&2       # OK (eine Datei, AusgabekanÀle zusammenfÌgen)

3.2) Beispiele fÃŒr Dateiumlenkung   (Toc)

  +---------------------+-----------------------------------------------------+
  | Kommando            | Beschreibung                                        |
  +---------------------+-----------------------------------------------------+
  | cat                 | stdout auf stdin schreiben (Tastatur -> Bildschirm) |
  | cat > FILE          | stdout auf FILE schreiben (vorher leeren!)          |
  | cat < FILE          | stdin von FILE lesen                                |
  | cat < FILE1 > FILE2 | stdin von FILE1 lesen, stdout auf FILE2 schreiben   |
  | cat > FILE2 < FILE1 | Analog (d.h. Reihenfolge der Umlenkungen ist egal!) |
  | cat < FILE > FILE   | FEHLER (Datei FILE ist anschließend leer!)          |
  | cat >> FILE         | stdout an FILE anhÀngen                             |
  | cat < FILE >> FILE  | FEHLER (Datei FILE wird beliebig lang!)             |
  | cat 2> FILE         | stderr auf FILE schreiben                           |
  | cat < xyz 2> error  | Fehlermeldung "Datei xyz unbekannt" am Bildschirm   |
  | cat 2> error < xyz  | Fehlermeldung "Datei xyz unbekannt" in "error"      |
  | less FILE           | Datei FILE seitenweise anzeigen                     |
  | cat < FILE | less   |   analog (2 Prozesse)                               |
  | cat FILE | less     |   analog (2 Prozesse)                               |
  +---------------------+-----------------------------------------------------+

HINWEIS: Nicht den "Useless use of cat award" versuchen zu gewinnen...

3.3) Beispiele fÃŒr Pipes   (Toc)

  +------------------------------------+--------------------------------------+
  | Kommando                           | Beschreibung                         |
  +------------------------------------+--------------------------------------+
  | ls -l /bin | sort                  | Datei nach Typ und Rechten sortieren |
  | ls -l /bin | sort | less           | ... + seitenweise anzeigen           |
  | ls -l /bin | sort | head           | ... + die ersten 10 aufsteigend      |
  | ls -l /bin | head | sort           | ... + 10 beliebige aufsteigend       |
  | ls -l /bin | sort | tail           | ... + die letzten 10 aufsteigend     |
  | ls -l /bin | sort -r | head        | ... + die letzten 10 absteigend      |
  | ls -l /bin | sort -r | head | sort | ... + die letzten 10 aufsteigend     |
  | ls -l /bin | sort +4nr             | Dateien nach Größe sortieren         |
  | ls -l /bin | sort +4nr | head      | ... + die größten 10 absteigend      |
  | ls -l /bin | sort +4n | head       | ... + die kleinsten 10 aufsteigend   |
  +------------------------------------+--------------------------------------+

3.4) Option "noclobber"   (Toc)

Per Option "noclobber" (nicht zusammenschlagen) kann verhindert werden, dass
bereits existierende Dateien versehentlich per Dateiumlenkung ÃŒberschrieben
werden:

  set -o noclobber

Das ZurÃŒcksetzen der Option erfolgt mittels:

  set +o noclobber

Beispiel:

  echo "aaa" > FILE      # OK
  echo "bbb" > FILE      # OK (FILE wird ÃŒberschrieben)
  echo "ccc" >> FILE     # OK (FILE wird verlÀngert)
  set -o noclobber       # Option "noclobber" setzen
  echo "ddd" > FILE      # klappt NICHT (FILE wird nicht ÃŒberschrieben)
  echo "eee" >> FILE     # OK (FILE wird verlÀngert)
  set +o noclobber       # Option "noclobber" löschen
  echo "fff" > FILE      # OK (FILE wird ÃŒberschrieben)
  echo "ggg" >> FILE     # OK (FILE wird verlÀngert)

4) Here-Dokument   (Toc)

Um Kommandoaufrufe und Daten in einem Skript gemeinsam pflegen zu können, ist
die Angabe von Daten zu einem Kommando direkt im Skript beim Kommandoaufruf als
sogenanntes "Here-Dokument" möglich. Das folgende Beispiel (EOF = "end of file")

  sort << EOF
  ...Text...
  ...Text...
  ...Text...
  EOF

ÃŒbergibt dem sort-Kommando die Textzeilen zwischen den beiden "Begrenzern"
"EOF" zum Sortieren. Das Begrenzer-Wort "EOF" ist frei wÀhlbar und muß als
Dokumentabschluss auf einer Zeile fÃŒr sich alleine am Zeilenanfang stehen,
damit er erkannt wird.

Im Datenteil werden noch Variablen- ($VAR) und Kommando-Substitutionen
(`CMD...` bzw. $(CMD...) durchgefÃŒhrt. Gibt man VOR dem SchlÃŒsselwort einen
Backslash an oder setzt das SchlÃŒsselwort in "..." oder '...', finden diese
Ersetzungen nicht statt:

  sort << \EOF          sort << "EOF"        sort << 'EOF'
  ...Text...            ...Text...           ...Text...
  ...Text...            ...Text...           ...Text...
  ...Text...            ...Text...           ...Text...
  EOF                   EOF                  EOF

Ein "-" nach dem "<<" erlaubt das EinrÃŒcken der Datenzeilen mit Tabulatoren,
diese Tabulatoren werden bei der Übergabe an das Kommando ignoriert:

  sort <<- EOF
  <TAB> Text...
  <TAB> Text...
  <TAB> Text...
  EOF

HINWEIS: Probiert man diese Umlenkung auf der Kommandozeile aus, so gibt die
Shell nach der 1. Zeile bis zum EOF den sogenannten "Fortsetzungsprompt" ">"
aus (definiert in Variable "PS2").

5) Linux-Filterprogramme   (Toc)

Filterprogramme lesen zeilenorientierte ASCII-Daten von der Standard-Eingabe,
verarbeiten sie gemÀß den angegebenen Optionen und/oder Argumenten und geben die
bearbeiteten Daten auf der Standardausgabe wieder aus. Durch Pipe-Symbole
können beliebig viele dieser Filter miteinander verbunden werden, um aus
einfachen Programmen eine leistungsfÀhige Verarbeitungskette zu kombinierten,
die ein bestimmtes Problem schrittweise löst.

VORGEHEN: Üblicherweise werden derartige Verarbeitungsketten schrittweise StÃŒck
fÃŒr StÃŒck interaktiv in der Kommandozeile aufgebaut, indem immer wieder neue
Filter an die bisherige Verarbeitungskette (Pipeline) angefÃŒgt werden. Ist die
gewÌnschte FunktionalitÀt erfolgreich realisiert, kann die Filterkette in einer
Datei abgespeichert werden, um sie in Zukunft als Shell-Skript aufrufen zu
können.

Bekannte Linux-Filterprogramme (relativ feste FunktionalitÀt):

  +-------+-------------------------------------------------------------+
  | Prog  | Beschreibung                                                |
  +-------+-------------------------------------------------------------+
  | cat   | HÀngt Dateien aneinander, kopiert Eingabe [concatenate]     |
  | comm  | Vergleicht 2 Dateien binÀr                                  |
  | cut   | Schneidet Textspalten aus                                   |
  | diff  | Vergleicht 2 Dateien und zeigt Unterschiede an [difference] |
  | fmt   | Formatiert Texte auf bestimmte ZeilenlÀnge um [format]      |
  | head  | Zeigt die ersten N Zeilen an [Kopf]                         |
  | join  | VerknÃŒpft 2 Dateien ÃŒber ein SchlÃŒsselfeld                  |
  | less  | BlÀttert seitenweise durch Daten                            |
  | more  | BlÀttert seitenweise durch Daten                            |
  | nl    | Numeriert Zeilen durch [number line]                        |
  | od    | Gibt Zeichencodes aus [octal dump]                          |
  | paste | Fasst Dateien horizontal zusammen (spaltenweise)            |
  | sort  | Sortiert alphabetisch oder numerisch                        |
  | split | Zerlegt Dateien in Blöcke                                   |
  | tail  | Zeigt die letzten N Zeilen an [Schwanz]                     |
  | tee   | Dupliziert Datei [T-StÃŒck]                                  |
  | tr    | Übersetzt Zeichen in andere Zeichen [translate]             |
  | uniq  | LÀßt doppelt vorkommende Zeilen weg [unique]                |
  | wc    | ZÀhlt Zeichen, Worte und Zeilen [word count]                |
  +-------+-------------------------------------------------------------+

Programmierbare Filterprogramme (mit RegulÀren AusdrÌcken):

  +--------+----------------------------------------------------------------+
  | grep   | Filtert Zeilen per Regex [global regular expr print]           |
  | sed    | Editiert Text mit angegebenen Kommandos [stream editor]        |
  | ed     | Editiert Textdatei mit angegebenen Kommandos [editor]          |
  | awk    | Programmiersprache zur Textverarbeitung                        |
  +--------+----------------------------------------------------------------+
  | perl   | Umfangreiche Programmiersprache u.a. auch zur Textverarbeitung |
  | python | Umfangreiche Programmiersprache u.a. auch zur Textverarbeitung |
  | ruby   | Umfangreiche Programmiersprache u.a. auch zur Textverarbeitung |
  +--------+----------------------------------------------------------------+

6) Grosse Beispiele   (Toc)

6.1) WorthÀufigkeit ermitteln   (Toc)

Die folgende "Pipeline" besteht aus 8 Kommandos und selektiert aus einer ASCII-
Textdatei die 10 seltensten Wörter.

  cat FILE... |               # Angegebenen Dateien aneinanderhÀngen
      tr -c "A-Za-z" "\n" |   # Nichtbst. in Newline "\n" umwandeln [complement]
      tr "A-Z" "a-z" |        # GROSS- in Kleinbuchstaben umwandeln
      sed '/^ *$/d' |         # Leerzeilen löschen
      sort |                  # Worte sortieren
      uniq -c |               # WorthÀufigkeiten ermitteln [count]
      sort -n |               # Nach HÀufigkeit sortieren [numeric]
      head                    # Die ersten 10 Worte anzeigen
                              #   (auch sort -nr | tail)

Beschreibung: "cat" [concatenate] hÀngt Dateien die Dateien FILE... aneinander,
"tr" [translate] ÃŒbersetzt Buchstaben ("\n" ist das Zeichen "newline"), "sed"
[stream editor] editiert den Text mit den angegebenen Kommandos, "uniq"
[unique] fasst gleiche Zeilen zusammen und zÀhlt ihre HÀufigkeit, "sort"
sortiert alphabetisch oder numerisch und "head" zeigt die ersten 10 Zeilen an.

Durch Angabe folgender Optionen beim 2. "sort" und bei "head" können die 10
hÀufigsten Worte  oder eine andere Zahl als 10 Worte  selektiert werden:

  -r  bei "sort": Sortiert absteigend --> hÀufigsten 10 Worte
  -NN bei "head": Zeigt NN Zeilen an (Standard: 10)
  -NN bei "tail": Zeigt NN Zeilen an (Standard: 10)

6.2) Nicht in Wörterbuch enthaltene Worte ausgeben   (Toc)

Die folgende "Pipeline" besteht aus 10 Kommandos und selektiert aus einer
ASCII-Textdatei die 10 Worte, die NICHT im (kleingeschriebenen und
alphabetisch sortierten) Wörterbuch DICT stehen (pro Zeile ein Wort).

  cat FILE... |               # Angegebenen Dateien aneinanderhÀngen
      tr -c "A-Za-z" "\n" |   # Nichtbst. in Newline "\n" umwandeln [complement]
      tr "A-Z" "a-z" |        # GROSS- in Kleinbuchstaben umwandeln
      sed '/^ *$/d' |         # Leerzeilen löschen
      sort |                  # Worte sortieren
      uniq |                  # Gleiche Worte zusammenfassen
      diff - DICT |           # Mit Wörterbuch DICT vergleichen ("-" = stdin)
      grep "^<" |             # Worte aus Wörterbuch weglassen
      sed "s/^< *//" |        # Kennzeichen "<..." entfernen
      less                    # Seitenweise blÀttern

Beschreibung: Siehe oben + "diff" vergleicht 2 Dateien und zeigt die
Unterschiede an, "grep" wÀhlt zu einem Muster passende Zeilen aus, "less"
blÀttert seitenweise durch die Daten.