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.
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.
-
0
STDIN
– standard input stream. -
1
STDOUT
– standard output stream. -
2
STDERR
– 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 STDIN
an, 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 cat
auf der Kommandozeile ohne Angabe von Parametern eingeben, nimmt er die Eingabe von STDIN
an. Nachdem Sie die nächste Zeile eingegeben haben, gibt sie cat
diese einfach auf dem Bildschirm aus.
STDOUT
STDOUT
– die Standardausgabe der Shell. Standardmäßig ist dies der Bildschirm. Die meisten Bash-Befehle geben Daten STDOUT
an 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, dassmyfile
die 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 Befehlls
normalerweise sendet, anSTDOUT
eine Dateicorrectcontent
umleiten. Die Fehlermeldungen, dieSTDERR
in der Datei gelandet wären, sinderrorcontent
auf den Umleitungsbefehl2>
zurückzuführen.
Bei Bedarf können undSTDERR
, undSTDOUT
mit 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ürSTDERR
undSTDOUT
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 AusgabeSTDERR
in 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 echo
unpraktisch, 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 vonSTDERR
auf eine Dateimyerror
eingestellt. Dann wird die Ausgabe mehrerer Befehle anecho
gesendet und auf dem Bildschirm angezeigt. Danach wird mit dem Befehlexec
alles gesendet, was anSTDOUT
eine Dateimyfile
geht, und schließlich, verwenden wir den UmleitungsbefehlSTDERR
im 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 exec
eine Datenquelle für STDIN
eine 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 Befehlread
verwendet, 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 STDIN
in 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 dieSTDIN
Datei wurde. Danach kam die Eingabe für den Befehlread
aus dem umgeleitetenSTDIN
, also aus der Datei.
Nach dem Lesen der Datei kehren wirSTDIN
zu 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 lsof
in /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.
-
-p
Ermöglicht es Ihnen, einenID
Prozess anzugeben. -
-d
Ermö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 -a
verwendet, um eine boolesche Operation AND
auf 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 mitSTDIN
STDOUT
undSTDERR —
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 Befehlslsof
aus 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 (3
und6
) 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.