[gelöst] Wie feststellen, ob stdin leer ist oder nicht? (bash)

Vom einfachen Programm zum fertigen Debian-Paket, Fragen rund um Programmiersprachen, Scripting und Lizenzierung.
Antworten
Benutzeravatar
MartinV
Beiträge: 788
Registriert: 31.07.2015 19:38:52
Wohnort: Hyperion
Kontaktdaten:

[gelöst] Wie feststellen, ob stdin leer ist oder nicht? (bash)

Beitrag von MartinV » 28.08.2017 13:10:50

Hallo!

Wie kann ich feststellen, ob in stdin Daten liegen oder nicht?
Mein Problem mit tools wie cat:

Code: Alles auswählen

echo foo | cat > datei 
ergibt die Ausgabe foo bzw. schreibt foo in datei.
Aber wenn cat keinen Input von stdin bekommt, wartet es für immer, und mein Skript geht nicht weiter:

Code: Alles auswählen

cat > datei    # cat wartet 100 Jahre auf Input von stdin
Ich habe einen etwas häßlichen workaround: Ich lese ein Byte mit read (und timeout 0.1 Sekunden), und falls es ein Byte gibt, rufe ich cat auf.

Code: Alles auswählen

IFS="" read -t 0.1 -rn 1 Byte
[ -n "$Byte" ] && {
  echo -n $Byte
  cat
} > datei
Ein anderer workaround:

Code: Alles auswählen

cat <(cat) > datei
Wenn stdin leer ist, bricht <(cat) meistens mit einem I/O-Fehler ab, und das Skript geht weiter. Ich habe aber auch Situationen gehabt, wo cat nicht abbrach (müßte ich noch einmal nachstellen, was die genauen Umstände sind).

Als Hintergrundprozeß bekommt cat gar keine Daten, auch wenn es welche in stdin gibt:

Code: Alles auswählen

cat > datei &
Es gibt noch "ifne", um stdin zu überprüfen. Es ist jedoch kein Bestandteil der coreutils und muß erst nachinstalliert werden (Debianmoreutils). Auf diese Abhängigkeit will ich nicht angewiesen sein. (Edit: Ein Test ergab, daß ifne genauso festhängt wie cat).
Ein simples [ -s /dev/stdin ] funktioniert leider auch nicht.

Ein paar Threads, in denen ich keine zufriedenstellende Lösung gefunden habe:
https://superuser.com/questions/210054/ ... pty-return
https://unix.stackexchange.com/question ... if-it-isnt

Gibt es eine saubere Lösung?
Zuletzt geändert von MartinV am 07.05.2018 11:28:26, insgesamt 3-mal geändert.
Die Vernunft kann einem schon leidtun. Sie verliert eigentlich immer.

scientific
Beiträge: 3020
Registriert: 03.11.2009 13:45:23
Lizenz eigener Beiträge: Artistic Lizenz
Kontaktdaten:

Re: Wie feststellen, ob stdin leer ist oder nicht? (bash)

Beitrag von scientific » 28.08.2017 18:32:43

Was ist mit

Code: Alles auswählen

 echo foo > datei
?
dann putze ich hier mal nur...

Eine Auswahl meiner Skripte und systemd-units.
https://github.com/xundeenergie

auch als Debian-Repo für Testing einbindbar:
deb http://debian.xundeenergie.at/xundeenergie testing main

inne
Beiträge: 3273
Registriert: 29.06.2013 17:32:10
Lizenz eigener Beiträge: GNU General Public License
Kontaktdaten:

Re: Wie feststellen, ob stdin leer ist oder nicht? (bash)

Beitrag von inne » 28.08.2017 19:14:34

Hallo,
irgendwo im Forum ist das beantwortet:

Code: Alles auswählen

#!/bin/bash
if [[ ! -t 0 ]]; then
	cat
else
	:
fi

Benutzeravatar
MartinV
Beiträge: 788
Registriert: 31.07.2015 19:38:52
Wohnort: Hyperion
Kontaktdaten:

Re: Wie feststellen, ob stdin leer ist oder nicht? (bash)

Beitrag von MartinV » 28.08.2017 20:22:27

inne hat geschrieben: ↑ zum Beitrag ↑
28.08.2017 19:14:34

Code: Alles auswählen

#!/bin/bash
if [[ ! -t 0 ]]; then
	cat
else
	:
fi
Cool! Das funktioniert! :D
Ich habe es reduziert auf:

Code: Alles auswählen

[ -t 0 ] || cat > datei
Ich vermute, Deine Schreibweise ist allgemeingültiger, auch für andere Shells? (Ich bin mit den doppelten [[ ]] nicht vertraut.)
scientific hat geschrieben: ↑ zum Beitrag ↑
28.08.2017 18:32:43
Was ist mit

Code: Alles auswählen

 echo foo > datei
?
Vielleicht habe ich es in der Frage nicht deutlich gemacht: Ich habe ein Skript, das mit oder ohne Input von stdin aufgerufen werden kann. Ich darf "cat > datei" aber nur im Skript ausführen, wenn es auch tatsächlich Daten zu lesen gibt. Wenn stdin leer ist, wartet cat für immer, und mein Skript hängt fest.

Danke!
Die Vernunft kann einem schon leidtun. Sie verliert eigentlich immer.

Benutzeravatar
MartinV
Beiträge: 788
Registriert: 31.07.2015 19:38:52
Wohnort: Hyperion
Kontaktdaten:

Re: Wie feststellen, ob stdin leer ist oder nicht? (bash)

Beitrag von MartinV » 06.05.2018 11:34:47

MartinV hat geschrieben: ↑ zum Beitrag ↑
28.08.2017 20:22:27
inne hat geschrieben: ↑ zum Beitrag ↑
28.08.2017 19:14:34

Code: Alles auswählen

#!/bin/bash
if [[ ! -t 0 ]]; then
	cat
else
	:
fi
Cool! Das funktioniert! :D
Ich habe festgestellt, daß diese Lösung doch nicht zuverlässig ist. :(
Starte ich z.B. mein script mit ssh, bleibt es wieder in einer Dauerwarteschleife hängen, wenn stdin leer ist. Die Abfrage [[ ! -t 0 ]] scheint stark von der gegebenen Terminal-Umgebung abzuhängen.

Aktuell bleibt mir nur der häßliche read-Workaround vom Ausgangspost. (Der aber hängen bleibt,falls es nur 1 Byte in stdin gibt).

Hat noch jemand eine Idee?

Ich denke manchmal, ich müßte nur ein paar Bytes an stdin anhängen und später wieder entfernen, damit cat auf jeden Fall Daten bekommt.

Ideal wäre ein Timeout für cat. Gibt es leider nicht als Option, schade.
Die Vernunft kann einem schon leidtun. Sie verliert eigentlich immer.

Benutzeravatar
TRex
Moderator
Beiträge: 8038
Registriert: 23.11.2006 12:23:54
Lizenz eigener Beiträge: MIT Lizenz
Wohnort: KA

Re: Wie feststellen, ob stdin leer ist oder nicht? (bash)

Beitrag von TRex » 06.05.2018 14:10:37

Code: Alles auswählen

timeout 60 cat
Allerdings muss dann der ganze Vorgang in 60 Sekunden durch sein. Alternativ könnte man vermutlich was mit dd zaubern, wo dann nur der erste Block dem Timeout unterliegt.
Jesus saves. Buddha does incremental backups.
Windows ist doof, Linux funktioniert nichtDon't break debian!Wie man widerspricht

Benutzeravatar
Meillo
Moderator
Beiträge: 8782
Registriert: 21.06.2005 14:55:06
Wohnort: Balmora
Kontaktdaten:

Re: Wie feststellen, ob stdin leer ist oder nicht? (bash)

Beitrag von Meillo » 06.05.2018 19:55:53

Meines Wissens gibt es keine richtige Loesung fuer dein Problem. Dein Programm muss wissen (z.B. per Switch), ob es von stdin lesen soll oder nicht.

Btw: `test -t' prueft nicht, ob es moeglich ist, von stdin zu lesen, sondern lediglich, ob stdin mit einem Terminal (darum `-t') anstatt mit einer Datei oder einer Pipe verbunden ist.

Die Unix-Konvention ist, dass stdin dann gelesen wird, wenn keine Dateiargumente angegeben worden sind oder wenn ein Dateiargument `-' lautet.
Use ed once in a while!

Benutzeravatar
MartinV
Beiträge: 788
Registriert: 31.07.2015 19:38:52
Wohnort: Hyperion
Kontaktdaten:

Re: Wie feststellen, ob stdin leer ist oder nicht? (bash)

Beitrag von MartinV » 06.05.2018 20:50:21

@TRex: Danke für "timeout", das kannte ich noch nicht!
Näher betrachtet ist ein Timeout doch keine gute Lösung: Es verursacht eine Verzögerung, auch wenn keine Daten vorliegen, und es bricht ab, auch wenn der Datenstrom noch nicht zuende ist.
Meillo hat geschrieben: ↑ zum Beitrag ↑
06.05.2018 19:55:53
Die Unix-Konvention ist, dass stdin dann gelesen wird, wenn keine Dateiargumente angegeben worden sind oder wenn ein Dateiargument `-' lautet.
Das ist eine Überlegung wert. Keine Argumente fällt für mein Skript aus; ob "-" mit getopt geht, müßte ich mal nachlesen. Zur Not mache ich eine Option "--stdin".

Das eigentliche Problem ist, daß mein Skript eine Art Wrapper ist, das über einige Umwege ein anderes Programm in einem docker Container startet. Das Programm im Container soll die Daten von stdin bekommen.

Ich habe gerade etwas gebastelt, das in ersten Versuchen ganz gut zu funktionieren scheint.

Code: Alles auswählen

exec 6<&0
cat <&6 >stdinsammeldatei &
stdin wird auf Kanal 6 umgeleitet. cat liest von Kanal 6 und schreibt in eine Datei. cat läuft dabei durch & als Hintergrundprozess.
Im docker Container wiederum funktioniert:

Code: Alles auswählen

cat <stdinsammeldatei
Ist das eine saubere Lösung? Oder gibt es dazu Bedenken?

Was mir bereits auffällt: Kommen die Daten in ungleichmäßigen Intervallen, bricht cat im Container mittendrin ab. Vermutlich findet es das Dateiende und hört auf. Daß die Datei weiterwachsen wird, kann es ja nicht wissen.

Beispiel für Datenübergabe in Intervallen:

Code: Alles auswählen

{ for (( i=1; i<=10; i++ )) ; do echo $i ; sleep 1; done ; } | cat
Das funktioniert so wie es ist im Terminal, aber nicht mit dem Umweg über die Datei.
Die Vernunft kann einem schon leidtun. Sie verliert eigentlich immer.

Benutzeravatar
MartinV
Beiträge: 788
Registriert: 31.07.2015 19:38:52
Wohnort: Hyperion
Kontaktdaten:

Re: Wie feststellen, ob stdin leer ist oder nicht? (bash)

Beitrag von MartinV » 07.05.2018 00:47:07

Wie es aussieht, habe ich jetzt eine Lösung, bei der auch unregelmäßiger Datenfluß übertragen wird:

stdin in eine benannte pipe umleiten:

Code: Alles auswählen

mkfifo /tmp/datei
cat <&0 >/tmp/datei &
woanders die pipe wieder auslesen:

Code: Alles auswählen

cat </tmp/datei
Edit: Ich setze den Thread auf "gelöst", da ich jetzt eine gute Lösung für mein ursächliches Problem habe. stdin sauber auf voll/leer zu prüfen scheint tatsächlich nicht möglich zu sein.

Noch schöner wäre meine jetzige Lösung, wenn ich noch "cat <&0 >/tmp/datei &" durch eine Umleitung mit "exec" ersetzen kann, um das "cat" einzusparen.
Die Vernunft kann einem schon leidtun. Sie verliert eigentlich immer.

Antworten