Wie erfasse ich stdin in einer Variablen, ohne nachfolgende Zeilenumbrüche zu entfernen?

9

In einem Shell-Skript ...

Wie erfasse ich stdin in einer Variablen, ohne nachfolgende Zeilenumbrüche zu entfernen?

Im Moment habe ich versucht:

var=`cat`
var=`tee`
var=$(tee)

In allen Fällen $varwird nicht die nachfolgende Newline des Eingabestreams angezeigt. Vielen Dank.

AUCH: Wenn die Eingabe keinen nachgestellten Zeilenumbruch enthält, darf die Lösung keinen hinzufügen .

UPDATE IM LICHT DER AKZEPTIERTEN ANTWORT:

Die endgültige Lösung, die ich in meinem Code verwendet habe, lautet wie folgt:

function filter() {
    #do lots of sed operations
    #see https://github.com/gistya/expandr for full code
}

GIT_INPUT=`cat; echo x`
FILTERED_OUTPUT=$(printf '%s' "$GIT_INPUT" | filter)
FILTERED_OUTPUT=${FILTERED_OUTPUT%x}
printf '%s' "$FILTERED_OUTPUT"

Wenn Sie den vollständigen Code sehen möchten, besuchen Sie bitte die Github-Seite für expandr , ein kleines Open-Source -Filter- Shell-Skript zur Erweiterung von Git-Schlüsselwörtern , das ich aus Gründen der Informationssicherheit entwickelt habe. Gemäß den Regeln, die in .gitattributes- Dateien (die branchenspezifisch sein können) und in git config festgelegt sind , leitet git jede Datei beim Ein- und Auschecken aus dem Repository durch das Shell-Skript expandr.sh . (Aus diesem Grund war es wichtig, nachfolgende oder fehlende Zeilenumbrüche beizubehalten.) Auf diese Weise können Sie vertrauliche Informationen bereinigen und verschiedene Sätze umgebungsspezifischer Werte für Test-, Staging- und Live-Zweige austauschen.

CommaToast
quelle
Was Sie hier tun, ist nicht notwendig. filterdauert stdin- es läuft sed. Sie fangen stdinin $GIT_INPUTdann das zurück drucken stdoutüber ein Rohr filterund seine fangen stdoutin $FILTERED_OUTPUTund es dann drucken zurück stdout. Alle 4 Zeilen am Ende Ihres obigen Beispiels könnten durch Folgendes ersetzt werden : filter. Hier ist nichts für ungut gemeint, es ist nur ... du arbeitest zu hart. Sie brauchen die Shell-Variablen die meiste Zeit nicht - leiten Sie die Eingabe einfach an die richtige Stelle und geben Sie sie weiter.
Mikesserv
Nein, was ich hier mache, ist notwendig, denn wenn ich es nur mache filter, werden an den Enden von Eingabestreams, die ursprünglich nicht in Zeilenumbrüchen endeten, Zeilenumbrüche hinzugefügt. Eigentlich habe ich es nur getan filter, bin aber auf dieses Problem gestoßen, das mich zu dieser Lösung geführt hat, weil weder "immer neue Zeilen hinzufügen" noch "immer neue Zeilen entfernen" akzeptable Lösungen sind.
CommaToast
sedwird wahrscheinlich die zusätzliche Zeilenumbruch machen - aber Sie sollten das filternicht mit allen anderen erledigen . Und all diese Funktionen, die Sie haben, tun im Grunde das Gleiche - a sed s///. Sie verwenden die Shell, um Daten, die sie in ihrem Speicher gespeichert hat sed, sedweiterzuleiten, damit diese Daten möglicherweise durch andere Daten ersetzt werden, die die Shell in ihrem Speicher gespeichert hat, damit sedsie an die Shell zurückgeleitet werden können. Warum nicht einfach [ "$var" = "$condition" ] && var=new_value? Ich bekomme die Arrays auch nicht - speichern Sie den Array-Namen in und verwenden Sie ihn [0]dann sed, um ihn durch den Wert in zu ersetzen [1]? Vielleicht chatten?
Mikesserv
@mikeserv - Was wäre der Vorteil, wenn Sie diesen Code nach innen verschieben würden filter? Es funktioniert perfekt wie es ist. In Bezug darauf, wie der Code unter meinem Link funktioniert und warum ich ihn so eingerichtet habe, wie ich es getan habe, lassen Sie uns in einem Chatroom darüber sprechen.
CommaToast

Antworten:

7

Die nachfolgenden Zeilenumbrüche werden entfernt, bevor der Wert in der Variablen gespeichert wird. Vielleicht möchten Sie etwas tun wie:

var=`cat; echo x`

und verwenden ${var%x}statt $var. Zum Beispiel:

printf "%s" "${var%x}"

Beachten Sie, dass dies das Problem der nachfolgenden Zeilenumbrüche löst, jedoch nicht das Null-Byte-Problem (wenn die Standardeingabe kein Text ist), da gemäß der POSIX-Befehlssubstitution :

Wenn die Ausgabe Null-Bytes enthält, ist das Verhalten nicht angegeben.

Shell-Implementierungen können jedoch Null-Bytes beibehalten.

vinc17
quelle
Würden Textdateien normalerweise Null-Bytes enthalten? Ich kann nicht sehen, warum sie es tun würden. Aber das Skript, das Sie gerade erwähnt haben, scheint nicht zu funktionieren.
CommaToast
@CommaToast Textdateien enthalten keine Nullbytes. Aber die Frage sagt nur stdin / input stream, was im allgemeinsten Fall möglicherweise kein Text ist.
vinc17
OK. Nun, ich habe es über die Befehlszeile versucht und es hat nichts getan, und in meinem Skript selbst schlägt Ihr Vorschlag fehl, weil am Ende der Datei "..." hinzugefügt wird. Auch wenn dort keine neue Zeile vorhanden war, wird immer noch eine hinzugefügt.
CommaToast
@CommaToast Das "..." war nur ein Beispiel. Ich habe meine Antwort klargestellt. Es wird keine neue Zeile hinzugefügt (siehe den Text vor dem "..." im Beispiel).
vinc17
1
Nun, Muscheln sollten keine Dinge verbergen, das ist nicht cool. Diese Granaten sollten abgefeuert werden. Ich mag es nicht, wenn mein Computer denkt, er weiß es besser als ich.
CommaToast
4

Sie können die readintegrierte Funktion verwenden, um dies zu erreichen:

$ IFS='' read -d '' -r foo < <(echo bar)

$ echo "<$foo>"
<bar
>

Für ein Skript zum Lesen von STDIN wäre es einfach:

IFS='' read -d '' -r foo

 

Ich bin mir nicht sicher, in welchen Shells das funktionieren wird. Funktioniert aber gut in Bash und Zsh.

Patrick
quelle
Weder -dnoch die Prozesssubstitution ( <(...)) sind portabel; Dieser Code funktioniert dashbeispielsweise nicht.
Chepper
Nun, die Prozessersetzung ist nicht Teil der Antwort, das war nur ein Teil des Beispiels, das zeigt, dass es funktioniert. Was -d, das ist , warum ich den Haftungsausschluss unten setzen. Das OP gibt die Shell nicht an.
Patrick
@chepner - während sich der Stil leicht unterscheidet, funktioniert das Konzept sicherlich dash. Sie verwenden nur <<HEREDOC\n$(gen input)\nHEREDOC\n- in dash- das Rohre für Heredocs genauso verwendet wie andere Shells sie für die Prozessersetzung - es macht keinen Unterschied. Die read -dSache ist nur die Angabe eines Trennzeichens - Sie können das gleiche auf ein Dutzend Arten tun - seien Sie sich einfach sicher. Obwohl Sie etwas Schwanz brauchen werden gen input.
Mikesserv
Sie setzen IFS = '', damit keine Leerzeichen zwischen die Zeilen eingefügt werden, die eh liest? Cooler Trick.
CommaToast
Eigentlich ist in diesem Fall IFS=''wohl nicht nötig. Es ist so gemeint, dass readLeerzeichen nicht zusammenbrechen. Aber wenn es in eine einzelne Variable liest, hat es keine Auswirkung (an die ich mich erinnern kann). Aber ich fühle mich einfach sicherer, wenn ich es anlasse :-)
Patrick
2

Sie können wie folgt tun:

input | { var=$(sed '$s/$/./'); var=${var%.}; }

Was auch immer Sie tun, $varverschwindet, sobald Sie diese { current shell ; }Gruppierung verlassen. Es könnte aber auch so funktionieren:

var=$(input | sed '$s/$/./'); var=${var%.}
mikeserv
quelle
1
Es ist zu beachten, dass mit der ersten Lösung, dh der Verwendung $varin der { ... }Gruppierung, dies nicht immer möglich ist. Zum Beispiel, wenn dieser Befehl innerhalb einer Schleife ausgeführt wird und $varaußerhalb der Schleife benötigt wird.
vinc17
@ vinc17 - wenn es sich um eine Schleife I gewünscht ist, zu verwenden , dann würde ich es anstelle der Verwendung {}Klammern .Es ist wahr - und in der Antwort ausdrücklich darauf hingewiesen wird - , dass der Wert für $varist sehr wahrscheinlich , ganz zu verschwinden , wenn die { current shell; }Gruppierung geschlossen. Gibt es eine explizitere Möglichkeit, es zu sagen als: Was auch immer Sie tun, $varverschwindet ...?
Mikesserv
@ vinc17 - wahrscheinlich der beste Weg, aber:input | sed "s/'"'/&"&"&/g;s/.*/process2 '"'-> &'/" | sh
mikeserv
1
Es gibt auch die _variablesFunktion von bash_completion, die das Ergebnis einer Befehlsersetzung in einer globalen Variablen speichert COMPREPLY. Wenn eine Pipeline-Lösung zum Halten von Zeilenumbrüchen verwendet würde, würde das Ergebnis verloren gehen. In Ihrer Antwort hat man den Eindruck, dass beide Lösungen gleich gut sind. Darüber hinaus sollte beachtet werden, dass das Verhalten der Pipeline-Lösung stark von der Shell abhängt: Ein Benutzer könnte echo foo | { var=$(sed '$s/$/./'); var=${var%.}; } ; echo $varmit ksh93 und zsh testen und denkt, dass dies in Ordnung ist, während dieser Code fehlerhaft ist.
vinc17
1
Sie haben nicht gesagt "es funktioniert nicht". Sie haben gerade gesagt " $varverschwindet" (was eigentlich nicht stimmt, da dies von der Shell abhängt - das Verhalten wird von POSIX nicht spezifiziert), was ein eher neutraler Satz ist. Die zweite Lösung ist besser, da sie nicht unter diesem Problem leidet und ihr Verhalten in allen POSIX-Shells konsistent ist.
vinc17