Bash-Skripte – Teil 4 – Eingabe und Ausgabe

Mikhail Raevskiy
Mikhail Raevskiy

Follow

Jul 28, 2020 – 11 min read

Letztes Mal, in Teil 3 dieser Bash-Scripting-Serie, haben wir über Kommandozeilenparameter und Schalter gesprochen. Unser heutiges Thema ist Eingabe, Ausgabe und alles, was damit zusammenhängt.

Foto von Ben Kolde auf Unsplash

Sie kennen bereits zwei Methoden, um mit dem zu arbeiten, was Befehls-Zeilen-Skripte ausgeben:

  • Anzeigen der Ausgabedaten auf dem Bildschirm.
  • Ausgabe in eine Datei umleiten.

Manchmal muss etwas auf dem Bildschirm angezeigt und etwas in eine Datei geschrieben werden, also müssen Sie verstehen, wie Ein- und Ausgabe in Linux gehandhabt werden, was bedeutet, dass Sie lernen müssen, wie Sie die Ergebnisse der Skripte dorthin schicken können, wo Sie sie brauchen. Beginnen wir damit, über Standard-Dateideskriptoren zu sprechen.

Alles in Linux sind Dateien, einschließlich Ein- und Ausgabe. Das Betriebssystem identifiziert Dateien mithilfe von Deskriptoren.

Jeder Prozess darf bis zu neun offene Dateideskriptoren haben. Die Bash-Shell reserviert die ersten drei Deskriptoren mit den IDs 0, 1 und 2. Hier ist, was sie bedeuten.

  • 0STDIN– standard input stream.
  • 1STDOUT– standard output stream.
  • 2STDERR– Standard-Fehler-Stream.

Diese drei speziellen Deskriptoren behandeln Skript-Ein- und -Ausgaben.
Sie müssen die Standard-Streams gut verstehen. Man kann sie mit dem Fundament vergleichen, auf dem die Interaktion von Skripten mit der Außenwelt aufgebaut ist. Betrachten wir die Details über sie.

STDIN

STDIN– das ist die Standardeingabe der Shell. Bei einem Terminal ist die Standardeingabe die Tastatur. Wenn Skripte das Zeichen für die Eingabeumleitung verwenden – < -, ersetzt Linux den Dateideskriptor der Standardeingabe durch den im Befehl angegebenen. Das System liest die Datei und verarbeitet die Daten so, als wären sie über die Tastatur eingegeben worden.

Viele Bash-Befehle nehmen Eingaben von STDINan, es sei denn, in der Befehlszeile wird eine Datei angegeben, aus der Daten abgerufen werden sollen. Das gilt zum Beispiel für ein Team cat.

Wenn Sie einen Befehl catauf der Kommandozeile ohne Angabe von Parametern eingeben, nimmt er die Eingabe von STDIN an. Nachdem Sie die nächste Zeile eingegeben haben, gibt sie catdiese einfach auf dem Bildschirm aus.

STDOUT

STDOUT– die Standardausgabe der Shell. Standardmäßig ist dies der Bildschirm. Die meisten Bash-Befehle geben Daten STDOUTan die Konsole aus, wodurch sie in der Konsole erscheinen. Die Daten können in eine Datei umgeleitet werden, indem sie mit dem Befehl >> an deren Inhalt angehängt werden.

Wir haben also eine bestimmte Datendatei, der wir mit diesem Befehl weitere Daten hinzufügen können:

pwd >> myfile

Was er ausgibt pwd, wird der Datei myfile hinzugefügt, während die Daten, die sich bereits darin befinden, nirgendwo hingehen.

raevskym@DESKTOP-JNF3L6H:~/bash_course$ pwd >> myfile
raevskym@DESKTOP-JNF3L6H:~/bash_course$ cat ./myfile
this is old file content
/home/raevskym/Desktop

So weit, so gut, aber was ist, wenn Sie versuchen, etwas wie das unten gezeigte zu tun, indem Sie auf eine nicht existierende Dateixfile zugreifen und dabei denken, dassmyfiledie Fehlermeldung in die Datei gelangt.

ls –l xfile > myfile

Nach dem Ausführen dieses Befehls sehen wir Fehlermeldungen auf dem Bildschirm.

raevskym@DESKTOP-JNF3L6H:~/bash_course$ ls -l xfile > myfile
ls: cannot access '-l': No sudh file or directory
ls: cannot access 'xfile': No such file or directory

Der Versuch, auf eine nicht existierende Datei zuzugreifen, erzeugt einen Fehler, aber die Shell hat die Fehlermeldungen nicht in die Datei umgeleitet und zeigt sie an. Wir wollten aber, dass die Fehlermeldungen an die Datei gehen. Was ist zu tun? Die Antwort ist einfach – verwenden Sie den dritten Standarddeskriptor.

STDERR

STDERR ist der Standardfehlerstrom der Shell. Standardmäßig zeigt dieses Handle auf dasselbe wie STDOUT, weshalb wir beim Auftreten eines Fehlers eine Meldung auf dem Bildschirm sehen.

Angenommen, Sie möchten Fehlermeldungen umleiten, z. B. in eine Protokolldatei oder an einen anderen Ort, anstatt sie auf dem Bildschirm auszugeben.

Fehlerstrom umleiten

Wie Sie bereits wissen, ist ein Dateideskriptor fürSTDERR 2. Wir können Fehler umleiten, indem wir diesen Deskriptor vor den Umleitungsbefehl setzen:

ls -l xfile 2>myfile
cat ./myfile

Die Fehlermeldung wird nun in die Datei myfile umgeleitet.

raevskym@DESKTOP-JNF3L6H:~/bash_course$ ls -l xfile 2> myfile
raevskym@DESKTOP-JNF3L6H:~/bash_course$ cat ./myfile
ls: cannot access 'xfile': No such flie or directory

Umleitung von Fehler- und Ausgabeströmen

Beim Schreiben von Befehlszeilenskripten kann eine Situation entstehen, in der Sie sowohl die Umleitung von Fehlermeldungen als auch die Umleitung der Standardausgabe organisieren müssen. Um dies zu erreichen, müssen Sie die Umleitungsbefehle für die entsprechenden Deskriptoren verwenden, die die Dateien angeben, in die die Fehlermeldungen und die Standardausgabe gehen sollen:

ls –l myfile xfile anotherfile 2> errorcontent 1> correctcontent

Lassen Sie uns das näher betrachten:

raevskym@DESKTOP-JNF3L6H:~/bash_course$ ls -l myfile xfile anotherfile 2> errorcontent 1> correctcontent
raevskym@DESKTOP-JNF3L6H:~/bash_course$ cat ./correctcontent
myfile
raevskym@DESKTOP-JNF3L6H:~/bash_course$ cat ./errorcontent
ls: cannot access '-l': No sudh file or directory
ls: cannot access 'xfile': No such file or directory
ls: cannot access 'anotherfile': No such file or directory

Die Shell wird das, was der Befehllsnormalerweise sendet, anSTDOUTeine Dateicorrectcontentumleiten. Die Fehlermeldungen, dieSTDERRin der Datei gelandet wären, sinderrorcontentauf den Umleitungsbefehl2>zurückzuführen.

Bei Bedarf können undSTDERR, undSTDOUTmit dem Befehl&> auf die gleiche Datei umgeleitet werden:

raevskym@DESKTOP-JNF3L6H:~/bash_course$ ls -l myfile xfile anotherfile &> content
raevskym@DESKTOP-JNF3L6H:~/bash_course$ cat ./content
ls: cannot access '-l': No sudh file or directory
ls: cannot access 'xfile': No such file or directory
ls: cannot access 'anotherfile': No such file or directory
myfile

Nach Ausführung des Befehls befindet sich das, was fürSTDERRundSTDOUT bestimmt ist, in der Dateicontent.

Umleitung von Ausgaben in Skripten

Es gibt zwei Methoden zur Umleitung von Ausgaben in Befehlszeilenskripten:

  • Temporäre Umleitung, oder Umleitung der Ausgabe einer Zeile.
  • Dauerhafte Umleitung, oder die Umleitung der gesamten oder eines Teils der Ausgabe im Skript.

Temporäre Umleitung der Ausgabe

In einem Skript können Sie die Ausgabe einer einzelnen Zeile auf STDERR umleiten. Dazu genügt es, den Befehl redirection zu verwenden, einen Deskriptor STDERR anzugeben und vor die Deskriptornummer ein kaufmännisches Und-Zeichen ( &) zu setzen:

#!/bin/bash
echo "This is an error" >&2
echo "This is normal output"

Wenn Sie das Skript ausführen, werden beide Zeilen auf dem Bildschirm angezeigt, da, wie Sie bereits wissen, Fehler standardmäßig an der gleichen Stelle wie normale Daten angezeigt werden.

raevskym@DESKTOP-JNF3L6H:~/bash_course$./myscript
This is an error
This is normal output

Lassen Sie das Skript so laufen, dass die AusgabeSTDERRin eine Datei geht.

./myscript 2> myfile

Wie Sie sehen, erfolgt jetzt die übliche Ausgabe auf die Konsole, und Fehlermeldungen werden in die Datei gesendet.

raevskym@DESKTOP-JNF3L6H:~/bash_course$./myscript 2> content
This is normal output
raevskym@DESKTOP-JNF3L6H:~/bash_course$ cat ./content
This is an error

Dauerhafte Ausgabeumleitung

Wenn ein Skript viele Ausgabedaten umleiten muss, ist es echounpraktisch, bei jedem Aufruf den entsprechenden Befehl hinzuzufügen. Stattdessen können Sie mit dem Befehl exec festlegen, dass die Ausgabe für die Dauer des Skripts an einen bestimmten Deskriptor umgeleitet wird:

#!/bin/bash
exec 1>outfile
echo "This is a test of redirecting all output"
echo "from a shell script to another file."
echo "without having to redirect every line"

Lassen Sie das Skript laufen:

raevskym@DESKTOP-JNF3L6H:~/bash_course$./myscript
raevskym@DESKTOP-JNF3L6H:~/bash_course$ cat outfile
This is a test of redirecting all output
from a shell script to another file.
without having to redirect every line

Wenn Sie sich die Datei ansehen, die in der Ausgabeumleitung angegeben wurde, stellt sich heraus, dass alles, was die Ausgabe des Befehlsecho war, in diese Datei ging.

Der Befehlexec kann nicht nur am Anfang des Skripts, sondern auch an anderen Stellen verwendet werden:

#!/bin/bash
exec 2>myerror
echo "This is the start of the script"
echo "now redirecting all output to another location"
exec 1>myfile
echo "This should go to the myfile file"
echo "and this should go to the myerror file" >&2

Das passiert, nachdem wir das Skript ausgeführt und die Dateien, in die wir die Ausgabe umgeleitet haben, angezeigt haben.

raevskym@DESKTOP-JNF3L6H:~/bash_course$./myscript
This is the start of the script
now redirecting all output to another location
raevskym@DESKTOP-JNF3L6H:~/bash_course$ cat ./myerror
and this should go to the myerror file
raevskym@DESKTOP-JNF3L6H:~/bash_course$ cat ./myfile
This should go to the myfile file

Zunächst wird mit dem Befehlexec die Umleitung der Ausgabe vonSTDERRauf eine Dateimyerror eingestellt. Dann wird die Ausgabe mehrerer Befehle anechogesendet und auf dem Bildschirm angezeigt. Danach wird mit dem Befehlexecalles gesendet, was anSTDOUTeine Dateimyfile geht, und schließlich, verwenden wir den UmleitungsbefehlSTDERRim Befehlecho, der dazu führt, dass die entsprechende Zeile in die Datei geschrieben wird.myerror.

Wenn Sie dies gemeistert haben, können Sie die Ausgabe dorthin umleiten, wo Sie wollen. Lassen Sie uns nun über die Umleitung von Eingaben sprechen.

Eingaben in Skripten umleiten

Um Eingaben umzuleiten, können Sie die gleiche Technik verwenden, die wir für die Umleitung von Ausgaben verwendet haben. Zum Beispiel können Sie mit dem Befehl execeine Datenquelle für STDINeine Datei erstellen:

exec 0< myfile

Dieser Befehl weist die Shell an, ihre Eingabe aus einer Datei myfile zu beziehen, nicht aus einer regulären STDIN. Sehen wir uns die Eingabeumleitung in Aktion an:

#!/bin/bash
exec 0< testfile
count=1
while read line
do
echo "Line #$count: $line"
count=$(( $count + 1 ))
done

So sieht es auf dem Bildschirm aus, nachdem das Skript ausgeführt wurde.

raevskym@DESKTOP-JNF3L6H:~/bash_course$./myscript
Line #1: this is the first line
Line #2: this is the second line
Line #3: this is the third line

In einem der vorherigen Artikel haben Sie gelernt, wie man einen Befehlreadverwendet, um Benutzereingaben von der Tastatur zu lesen. Wenn Sie die Eingabe umleiten, indem Sie die Datenquelle zu einer Datei machen, dann wird der Befehlread, wenn er versucht, Daten vonSTDIN zu lesen, diese aus der Datei lesen und nicht von der Tastatur.

Einige Linux-Administratoren verwenden diesen Ansatz, um Protokolldateien zu lesen und dann zu verarbeiten.

Eigene Ausgabeumleitungen erstellen

Beim Umleiten von Ein- und Ausgaben in Skripten sind Sie nicht auf die drei Standard-Dateideskriptoren beschränkt. Wie bereits erwähnt, können Sie bis zu neun offene Deskriptoren haben. Die anderen sechs, die mit 3 bis 8 nummeriert sind, können für die Umleitung von Ein- oder Ausgaben verwendet werden. Jeder von ihnen kann einer Datei zugewiesen und im Skriptcode verwendet werden.

Mit dem Befehl exec können Sie einen Deskriptor für die Datenausgabe zuweisen:

#!/bin/bash
exec 3>myfile
echo "This should display on the screen"
echo "and this should be stored in the file" >&3
echo "And this should be back on the screen"

Nach der Ausführung des Skripts geht ein Teil der Ausgabe auf den Bildschirm und ein Teil in die Datei mit dem Deskriptor 3.

raevskym@DESKTOP-JNF3L6H:~/bash_course$./myscript
This should display on the screen
And this should be back on the screen
raevskym@DESKTOP-JNF3L6H:~/bash_course$ cat ./myfile
and this should be stored in the file

Dateideskriptoren für die Dateneingabe erstellen

Sie können die Eingabe in einem Skript auf die gleiche Weise umleiten wie die Ausgabe. Speichern Sie STDINin einem anderen Deskriptor, bevor Sie die Eingabe umleiten.

Nachdem Sie das Einlesen der Datei beendet haben, können Sie das STDIN wiederherstellen und wie gewohnt verwenden:

#!/bin/bash
exec 6<&0
exec 0< myfile
count=1
while read line
do
echo "Line #$count: $line"
count=$(( $count + 1 ))
done
exec 0<&6
read -p "Are you done now? " answer
case $answer in
y) echo "Goodbye";;
n) echo "Sorry, this is the end.";;
esac

Lassen Sie uns das Skript testen:

raevskym@DESKTOP-JNF3L6H:~/bash_course$./myscript
Line #1: First line
Line #2: Second line
Line #3: Third line
Are you done now? y
Goodbye
raevskym@DESKTOP-JNF3L6H:~/bash_course$./myscript
Line #1: First line
Line #2: Second line
Line #3: Third line
Are you done now? n
Sorry, this is the end.

In diesem Beispiel wurde der Dateideskriptor 6 verwendet, um eine Referenz aufSTDIN zu speichern. Dann wurde eine Eingabeumleitung durchgeführt, die Datenquelle für dieSTDINDatei wurde. Danach kam die Eingabe für den Befehlread aus dem umgeleitetenSTDIN, also aus der Datei.

Nach dem Lesen der Datei kehren wirSTDINzu ihrem ursprünglichen Zustand zurück, indem wir sie auf einen Deskriptor6 umleiten. Um nun zu überprüfen, ob alles korrekt funktioniert, stellt das Skript dem Benutzer eine Frage, wartet auf Eingaben über die Tastatur und verarbeitet die Eingaben.

Schließen von Dateideskriptoren

Die Shell schließt Dateideskriptoren automatisch, nachdem das Skript beendet wurde. In manchen Fällen ist es jedoch notwendig, die Deskriptoren manuell zu schließen, bevor das Skript seine Arbeit beendet. Um das Handle zu schließen, muss es auf &- umgeleitet werden. Das sieht so aus:

#!/bin/bash
exec 3> myfile
echo "This is a test line of data" >&3
exec 3>&-
echo "This won't work" >&3

Nach der Ausführung des Skripts erhalten wir eine Fehlermeldung.

raevskym@DESKTOP-JNF3L6H:~/bash_course$./myscript
./myscript: line 5: 3: Bad file descriptor

Der Punkt ist, dass wir versucht haben, auf einen nicht existierenden Dateideskriptor zuzugreifen.

Sein Sie vorsichtig, wenn Sie Dateideskriptoren in Skripten schließen. Wenn Sie Daten an eine Datei geschickt haben, dann den Handle geschlossen haben und ihn dann wieder geöffnet haben, wird die Shell die bestehende Datei durch die neue ersetzen. Das heißt, alles, was vorher in diese Datei geschrieben wurde, geht verloren.

Informationen über offene Handles abrufen

Um eine Liste aller offenen Deskriptoren unter Linux zu erhalten, können Sie den Befehl lsof verwenden. Viele Distributionen, wie Fedora, haben das Dienstprogramm lsofin /usr/sbin. Dieser Befehl ist sehr nützlich, da er Informationen über jedes auf dem System geöffnete Handle anzeigt. Dazu gehört, was von Prozessen geöffnet ist, die im Hintergrund laufen, und was von Benutzern geöffnet ist, die angemeldet sind.

Dieser Befehl hat viele Tasten, schauen wir uns die wichtigsten an.

  • -pErmöglicht es Ihnen, einen IDProzess anzugeben.
  • -dErmöglicht es Ihnen, die Nummer eines Deskriptors anzugeben, über den Sie Informationen erhalten möchten.

Um die PID des aktuellen Prozesses herauszufinden, können Sie eine spezielle Umgebungsvariable $$ verwenden, in die die Shell den aktuellen PID schreibt.

Der Schlüssel wird -averwendet, um eine boolesche Operation ANDauf den Ergebnissen durchzuführen, die mit den beiden anderen Schlüsseln zurückgegeben werden:

raevskym@DESKTOP-JNF3L6H:~/bash_course$ lsof -a -p $$ -d 0,1,2
COMMAND PID USER FD TYPE DEVICE SIZE NODE NAME
myscript 24 raevskym 0u CHR 4,1 25332 /dev/tty1
myscript 24 raevskym 1u CHR 4,1 25332 /dev/tty1
myscript 24 raevskym 2u CHR 4,1 25332 /dev/tty1

Der Typ der Dateien, die mitSTDINSTDOUTundSTDERR — CHR (Zeichenmodus) verbunden sind. Da sie alle auf ein Terminal zeigen, entspricht der Dateiname dem dem Terminal zugewiesenen Gerätenamen. Alle drei Standarddateien stehen zum Lesen und Schreiben zur Verfügung.

Betrachten wir den Aufruf eines Befehlslsofaus einem Skript, in dem neben dem Standard noch weitere Deskriptoren geöffnet sind:

#!/bin/bash
exec 3> myfile1
exec 6> myfile2
exec 7< myfile3
lsof -a -p $$ -d 0,1,2,3,6,7

So sieht es aus, wenn Sie dieses Skript ausführen.

raevskym@DESKTOP-JNF3L6H:~/bash_course$./myscript
COMMAND PID USER FD TYPE DEVICE SIZE NODE NAME
myscript 24 raevskym 0u CHR 4,1 25332 /dev/tty1
myscript 24 raevskym 1u CHR 4,1 25332 /dev/tty1
myscript 24 raevskym 2u CHR 4,1 25332 /dev/tty1
myscript 24 raevskym 3w REG 0,14 0 13510 ~/bash_course/myfile1
myscript 24 raevskym 6w REG 0,14 0 13229 ~/bash_course/myfile2

Das Skript öffnet zwei Deskriptoren für die Ausgabe (3und6) und einen für die Eingabe (7). Die Pfade zu den Dateien, mit denen die Deskriptoren konfiguriert werden, sind hier ebenfalls angegeben.

Ausgabeunterdrückung

Manchmal müssen Sie dafür sorgen, dass die Befehle im Skript, die z. B. als Hintergrundprozess ausgeführt werden können, nichts auf dem Bildschirm anzeigen. Um dies zu erreichen, können Sie die Ausgabe auf /dev/null umlenken. Dies ist so etwas wie ein „schwarzes Loch“.

So können Sie zum Beispiel Fehlermeldungen unterdrücken:

ls -al badfile anotherfile 2> /dev/null

Das gleiche Vorgehen kommt zum Einsatz, wenn Sie zum Beispiel eine Datei aufräumen wollen, ohne sie zu löschen:

cat /dev/null > myfile

Ergebnis

Heute haben Sie gelernt, wie Ein- und Ausgabe in der Kommandozeilen-Skripterstellung funktionieren. Sie wissen jetzt, wie man mit Dateideskriptoren umgeht, wie man sie erstellt, anzeigt und schließt, und Sie wissen, wie man Eingabe-, Ausgabe- und Fehlerströme umleitet. Diese sind alle sehr wichtig bei der Entwicklung von Bash-Skripten.

Nächstes Mal sprechen wir über Linux-Signale, wie man sie in Skripten behandelt, wie man geplante Jobs und Hintergrundaufgaben ausführt.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.