Warum bricht set-o errexit diesen Lese- / Heredoc-Ausdruck?

8

Ich habe das folgende Muster verwendet, um mehrzeilige Nachrichten in einem Bash-Skript an das Terminal zu drucken.

read -d '' message <<- EOF
    this is a 
    mulitline
    message
EOF
echo "$message"

Das hat funktioniert - bis vor ein paar Tagen hat das Muster einfach aufgehört zu funktionieren. Mit aufgehört zu arbeiten meine ich, als bash auf diese Heredoc-Ausdrücke im Skript stieß - es scheint einfach nichts zu tun - keine Ausgabe.
Das einzige, was ich mir vorstellen kann, was sich in den letzten Tagen geändert hat, ist, dass die Umgebung, in der die Skripte ausgeführt werden, ein Ubuntu 14.04 Live-USB ist, im Gegensatz zu "vollständigen" Installationen.
Dann stellte ich fest, dass der Heredoc beim Verschieben vor der Skriptanweisung set -o errexitwieder funktioniert. dh das funktioniert nicht

#!/bin/bash

set -o errexit

read -d '' message <<- EOF
    this is a 
    mulitline
    message
EOF

echo "$message"

Ergebnis: (nichts)
Aber das funktioniert

#!/bin/bash

read -d '' message <<- EOF
    this is a 
    mulitline
    message
EOF

echo "$message"

Ergebnis

$ sudo ./script.sh 
this is a 
mulitline
message
  • bash --version - GNU bash, version 4.3.11(1)-release (i686-pc-linux-gnu)
the_velour_fog
quelle

Antworten:

11

read Gibt einen Exit-Status ungleich Null zurück, wenn kein Trennzeichen gefunden wird. Dies ist immer dann der Fall, wenn das Trennzeichen eine leere Zeichenfolge ist.

Barfuß IO
quelle
4
Genau genommen ist es \0keine leere Zeichenfolge.
Cuonglm
1
@cuonglm, genauer gesagt, wenn ein leeres Argument übergeben wird -d, ist das Trennzeichen \0(ein NUL-Byte). Ein Sonderfall ist meistens das Ergebnis eines Codierungsunfalls (obwohl der bashBetreuer dies nicht zugibt und erklärt, warum es nicht dokumentiert ist ).
Stéphane Chazelas
"... was immer der Fall ist, wenn das Trennzeichen eine leere Zeichenfolge ist." Wie andere bereits betont haben, ist dies tatsächlich \0der Fall - und das schlägt nicht immer fehl. Sie können beispielsweise read -d ''die Ausgabe von verarbeiten find ... -print0.
Solidsnack
9

Der Exit-Code des Lesebefehls ist 1, wenn die Markierung für das Dateiende (EOF) erreicht ist. Dies ist immer dann der Fall , wenn das Trennzeichen in diesem speziellen Fall, in dem der Quelldatenstrom ein Heredoc ist, der kein \ 0 enthalten kann, -dnull ''ist.

$ read -d '' message <<-_ThisMessageEnds_
>     this is a
>     multi line
>     message
> _ThisMessageEnds_
$ exitval=$?
$ echo "The exit val was $exitval"
The exit val was 1.

Dass der Exit-Wert ein Fehler ist (nicht 0), lässt den Skript-Exit mit einem AND / OR-Konstrukt vermieden werden:

read -d '' message <<-_ThisMessageEnds_ || echo "$message"
    this is a
    multi line
    message
_ThisMessageEnds_

Dadurch wird die Nachricht an die Konsole gesendet, und es wird dennoch vermieden, sie mit zu beenden errexit.

Aber da wir auf diesem Weg sind, um zu reduzieren, warum nicht direkt nutzen:

cat <<-_ThisMessageEnds_
    this is a
    mulitline
    message
_ThisMessageEnds_

Kein Lesebefehl ausgeführt (mehr Geschwindigkeit), keine Variable erforderlich, kein Fehler im Exit-Code, weniger zu wartender Code.


quelle
Vielen Dank, gut zu wissen, ||wie man das Beenden des Skripts verhindert. Am Ende habe ich errexitdas Skript komplett entfernt. Ich hatte Probleme damit, nicht das Beenden auszulösen, wenn es sollte - es scheint also zu schwierig, um nützlich zu sein. Ich habe auch das einfachere Muster mit berücksichtigt cat <<- EOF message EOF. Es war einfach schön, die Nachricht in einer Variablen zu haben, die bei Bedarf an eine Funktion übergeben werden konnte
the_velour_fog
7
read -d '' message

Liest stdin bis zum ersten nicht entkoppelten (wie Sie nicht hinzugefügt haben -r) NUL-Zeichen oder dem Ende der Eingabe und speichert die Daten nach $IFSund Backslash-Zeichenverarbeitung in $message(ohne das Trennzeichen).

Wenn in der Eingabe kein nicht entkoppeltes Trennzeichen gefunden wird, ist der Beendigungsstatus readungleich Null. Es wird nur 0 (Erfolg) zurückgegeben, wenn ein vollständiger, abgeschlossener Datensatz gelesen wird.

Dies ist am nützlichsten für den Umgang mit NUL-getrennten Datensätzen wie der Ausgabe von find -print0(obwohl Sie dann eine IFS= read -rd '' recordSyntax benötigen ).

Hier müssen Sie ein NUL-Trennzeichen in Ihr Here-Dokument aufnehmen read, damit Sie erfolgreich zurückkehren können. Dies ist jedoch nicht möglich, mit bashdem NUL-Zeichen aus Here-Dokumenten entfernt werden (das ist zumindest besser als das yash, was alles nach dem ersten NUL entfernt, oder ksh93, das in eine Endlosschleife einzutreten scheint, wenn ein Here-Dokument ein NUL enthält).

zshist die einzige Shell, die eine NUL in ihren hier enthaltenen Dokumenten haben oder in ihren Variablen speichern oder NUL-Zeichen in Argumenten an ihre eingebauten / Funktionen übergeben kann. In zshkönnen Sie tun:

NUL=$'\0'
IFS= read -d $NUL -r var << EOF
1
2
3$NUL
EOF

( zshVerstehe auch read -d ''als NUL-Trennzeichen wie bash. Funktioniert read -d $'\0'auch in, bashaber das übergibt ein leeres Argument, um zu readmögen, read -d ''da bashNUL-Bytes in seiner Befehlszeile nicht unterstützt werden).

(Beachten Sie, dass es danach ein zusätzliches Zeilenumbruchzeichen gibt. $NUL)

In bashkönnen Sie ein anderes Zeichen verwenden:

ONE=$'\1'
IFS= read -d "$ONE" -r var << EOF
1
2
3$ONE
EOF

Sie können aber auch Folgendes tun:

var=$(cat <<EOF
message
here
EOF
)

Das erlaubt immer noch keine NUL-Zeichen. Dies ist jedoch Standardcode, sodass Sie sich nicht auf den zsh / bash-spezifischen Code verlassen müssen read -d. Beachten Sie auch, dass alle nachgestellten Zeilenumbrüche entfernt werden. ksh93Wenn dies nicht der catFall ist, bedeutet dies, dass ein zusätzlicher Prozess und Befehl erzeugt wird.

Stéphane Chazelas
quelle
readIn diesem Fall bedeutet dies, dass ein Wert ungleich Null zurückgegeben wird, da kein Trennzeichen gefunden wurde, nicht wahr?
Cuonglm
@cuonglm, ja, das ist das Gleiche wie Ihre Antwort, nur um ein paar Dinge zu erweitern.
Stéphane Chazelas
2
@ Cuonglm, siehe auch diesen aktuellen Thread auf der Bash-Mailingliste
Stéphane Chazelas
5

Wenn Sie verwenden set -o errexitund Ihr Skript kaputt geht, bedeutet dies, dass etwas nicht stimmt.

Hier ist es read, was Ihre Eingabe nicht richtig lesen kann.

In bash, wenn Sie read -d ''die readbuiltin die Null - Zeichen verwendet \0als Leitungsabschluss. Wenn \0Ihre Eingabe keine enthält , readwerden daher alle Eingaben in die messageVariable eingelesen und ein Exit-Status ungleich Null zurückgegeben, um anzuzeigen, dass ein Fehler aufgetreten ist:

$ while read -d '' line; do echo "$line"; done < <(printf '1')

druckt nichts während:

$ while read -d '' line; do echo "$line"; done < <(printf '1\0')
1

gibt dir 1.

readgibt auch den Status ungleich Null zurück, wenn EOF erreicht wird. Dies wird jedoch verwendet, um anzuzeigen, dass bei Verwendung readmit einer whileSchleife keine Eingabe mehr zu lesen ist , sodass die whileSchleife beendet werden kann. Es ist nicht relevant für Ihr Problem.

cuonglm
quelle
Danke, die anderen Antworten erklärten, dass mein readGesichtsausdruck das Drehbuch brach. Aber das ist ein guter Punkt, der auch Nicht-Null gelesen verlässt , wenn sie mit einem gebrauchten read, whileSchleife. Aus diesem Grund halte ich es nicht für set -o errexitzuverlässig, da Befehle manchmal als Teil des normalen Programmflusses ungleich Null zurückgeben müssen
the_velour_fog
@the_velour_fog: Es ist zuverlässig, wenn Sie möchten, dass es Teil des Programmflusses ist, dann verwenden Sie die Flusskontrolle, wieif read ...
cuonglm
Die richtige Beschreibung ist bei der Suche nach einem NUL-Trennzeichen, bei dem der EOF zuerst gefunden wurde. Was beim Lesen eines EOF zu lesen ist: Melden Sie einen Fehler, was auch der Fall ist.
1
@BinaryZebra, ja, obwohl ich verstehe, dass cuonglm die while-Schleife zur Veranschaulichung verwendet hat, readdie bei einem nicht gefundenen Trennzeichen mit einem Fehler zurückkehrt (und die Schleife verlässt) (obwohl ich zustimmen würde, dass dies wahrscheinlich nur mehr Verwirrung stiftet).
Stéphane Chazelas
1
Im Wesentlichen sagen wir alle dasselbe, nur mehr oder weniger klar, es macht nicht viel Sinn, mehr darüber zu streiten.
Stéphane Chazelas