Ich möchte in der Lage sein, die genaue Ausgabe einer Befehlsersetzung zu erfassen, einschließlich der nachfolgenden neuen Zeilenzeichen .
Mir ist klar, dass sie standardmäßig entfernt werden, so dass möglicherweise einige Manipulationen erforderlich sind, um sie zu behalten, und ich möchte den ursprünglichen Beendigungscode beibehalten .
Beispiel: Ein Befehl mit einer variablen Anzahl von nachgestellten Zeilenumbrüchen und Exit-Code:
f(){ for i in $(seq "$((RANDOM % 3))"); do echo; done; return $((RANDOM % 256));}
export -f f
Ich möchte etwas laufen wie:
exact_output f
Und muss die Ausgabe sein:
Output: $'\n\n'
Exit: 5
Ich interessiere mich für beide bash
und POSIX sh
.
$IFS
, daher wird es nicht als Argument erfasst.IFS
(versuchen( IFS=:; subst=$(printf 'x\n\n\n'); printf '%s' "$subst" )
. Nur Zeilenumbrüche werden entfernt.\t
Und `` nicht undIFS
hat keinen Einfluss darauf.tcsh
Antworten:
POSIX-Schalen
Der übliche Trick ( 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ), um die vollständige Standardausgabe eines Befehls zu erhalten, ist:
Die Idee ist hinzuzufügen und zu ergänzen
.\n
. Die Befehlsersetzung entfernt nur das\n
. Und du ziehst das.
mit aus${output%.}
.Beachten Sie, dass dies in anderen Shells als
zsh
nicht funktioniert, wenn die Ausgabe NUL-Bytes enthält. Mityash
funktioniert das nicht, wenn die Ausgabe kein Text ist.Beachten Sie auch, dass es in einigen Gebietsschemas darauf ankommt, welches Zeichen Sie am Ende einfügen.
.
sollte in der Regel in Ordnung sein, aber einige andere möglicherweise nicht. Zum Beispielx
(wie in einigen anderen Antworten verwendet) oder@
würde in einem Gebietsschema mit den Zeichensätzen BIG5, GB18030 oder BIG5HKSCS nicht funktionieren. In diesen Zeichensätzen endet die Codierung einer Anzahl von Zeichen im selben Byte wie die Codierung vonx
oder@
(0x78, 0x40).Beispielsweise ist
ū
in BIG5HKSCS 0x88 0x78 (undx
0x78 wie in ASCII müssen alle Zeichensätze auf einem System für alle Zeichen des portablen Zeichensatzes, der englische Buchstaben@
und enthält, dieselbe Codierung haben.
). Also , wenncmd
warprintf '\x88'
und wir eingeführt ,x
nachdem er,${output%x}
würde nicht abzustreifen , dassx
als$output
würde tatsächlich enthaltenū
.Die Verwendung von
.
stattdessen könnte theoretisch zu demselben Problem führen, wenn es Zeichen gibt, deren Codierung mit derselben Codierung endet wie.
, aber da ich dies vor einiger Zeit überprüft habe, kann ich feststellen, dass keiner der Zeichensätze für die Verwendung in einem Gebietsschema in verfügbar ist Ein Debian-, FreeBSD- oder Solaris-System hat solche Zeichen, was für mich gut genug ist (und warum ich mich entschieden habe.
, ist auch das Symbol, um das Ende eines Satzes auf Englisch zu markieren, scheint angebracht).Ein korrekterer Ansatz, wie er von @Arrow diskutiert wird, besteht darin, das Gebietsschema nur für das Entfernen des letzten Zeichens (
${output%.}
) in C zu ändern, wodurch sichergestellt wird, dass nur ein Byte entfernt wird. Dies würde den Code jedoch erheblich komplizieren und möglicherweise Kompatibilitätsprobleme verursachen seine eigene.bash / zsh Alternativen
Mit
bash
undzsh
können Sie unter der Annahme, dass die Ausgabe keine NULs enthält, auch Folgendes tun:Um den Exit-Status von zu erhalten
cmd
, können Siewait "$!"; ret=$?
inbash
aber nicht in tunzsh
.rc / es / akanaga
Der Vollständigkeit halber sei angemerkt, dass
rc
/es
/akanga
dafür einen Operator hat. In ihnen gibt die Befehlsersetzung, ausgedrückt als`cmd
(oder`{cmd}
für komplexere Befehle), eine Liste zurück (durch Aufteilen auf$ifs
, Leerzeichen-Tabulator-Neuzeile standardmäßig). In diesen Shells (im Gegensatz zu Bourne-ähnlichen Shells) erfolgt das Entfernen von Zeilenumbrüchen nur als Teil dieser$ifs
Aufteilung. Sie können also entweder$ifs
das``(seps){cmd}
Formular leeren oder das Formular verwenden, in dem Sie die Trennzeichen angeben:oder:
In jedem Fall geht der Beendigungsstatus des Befehls verloren. Sie müssten es in die Ausgabe einbetten und anschließend extrahieren, was hässlich werden würde.
Fisch
Bei Fischen erfolgt die Befehlsersetzung mit
(cmd)
und ohne Unterschale.Erstellt ein
$var
Array mit allen Zeilen in der Ausgabe voncmd
if,$IFS
die nicht leer sind, oder mit der Ausgabe voncmd
stripped von bis zu einem (im Gegensatz zu allen in den meisten anderen Shells) Newline-Zeichen, wenn sie$IFS
leer sind.Darin liegt also immer noch ein Problem,
(printf 'a\nb')
und(printf 'a\nb\n')
das gleiche gilt auch für leere$IFS
.Um das zu umgehen, war das Beste, was ich mir einfallen lassen konnte:
Eine Alternative ist zu tun:
Borowski-Schale
Die Bourne-Shell hat weder das
$(...)
Formular noch den${var%pattern}
Operator unterstützt, daher kann es sehr schwierig sein, dies zu erreichen. Ein Ansatz ist die Verwendung von eval und quoting:Hier erzeugen wir eine
übergeben werden an
eval
. Wie für den POSIX - Ansatz, wenn'
war eine jener Figuren , deren Codierung am Ende der anderen Zeichen gefunden werden können, würden wir ein Problem haben (ein viel schlimmer ein als es ein Befehl Injection - Schwachstelle werden würde), aber zum Glück, wie.
, Es ist keine von denen, und diese Anführungszeichen-Technik wird im Allgemeinen von allen verwendet, die Shell-Code zitieren. (Beachten Sie, dass\
das Problem besteht. Sie sollten daher nicht verwendet werden."..."
) Hier verwenden wir es erst nach a,'
was in Ordnung ist.tcsh
Siehe tcsh, um Zeilenumbrüche in der Befehlsersetzung "..." zu erhalten
(kümmert sich nicht um den Exit-Status, den Sie beheben können, indem Sie ihn in einer temporären Datei speichern (
echo $status > $tempfile:q
nach dem Befehl))quelle
zsh
gespeichert werdenNUL
kann, warum würde das nichtIFS= read -rd '' output < <(cmd)
funktionieren? Es muss in der Lage sein, die Länge eines Strings zu speichern.''
Wird der String\0
eher als 1-Byte-String als als 0-Byte-String codiert ?read -d ''
wird behandelt alsread -d $'\0'
(bash
auch wenn es$'\0'
das gleiche wie''
überall gibt).x
wenn es das ist, was hinzugefügt wurde. Bitte werfen Sie einen Blick auf meine bearbeitete Antwort.var=value command eval
Trick wurde hier ( auch ) und auf der Austin-Group-Mailingliste schon mal besprochen . Sie werden feststellen, dass es nicht portabel ist (und es ist ziemlich offensichtlich, wenn Sie Dinge wiea=1 command eval 'unset a; a=2'
oder noch schlimmer ausprobieren, dass es nicht für solche Zwecke gedacht ist). Das gleiche gilt für dassavedVAR=$VAR;...;VAR=$savedVAR
, was Sie nicht tun möchten, wenn$VAR
es anfangs deaktiviert war. Wenn das nur ein theoretisches Problem umgeht (ein Fehler, der in der Praxis nicht gefunden werden kann), lohnt sich die Mühe nicht. Trotzdem werde ich dich beim Ausprobieren unterstützen.LANG=C
zum Entfernen eines Bytes aus einer Zeichenfolge verworfen haben ? Sie äußern Bedenken in Bezug auf den eigentlichen Punkt, alle sind leicht zu lösen. (1) es wird kein Unset verwendet. (2) Testen Sie die Variable, bevor Sie sie ändern. @ StéphaneChazelasFür die neue Frage funktioniert dieses Skript:
Bei der Ausführung:
Die längere Beschreibung
Die übliche Weisheit für POSIX-Shells, sich mit dem Entfernen von zu befassen,
\n
ist:Das ist erforderlich , weil die letzte neue Linie ( S ) durch den Befehl Erweiterung entfernt werden pro POSIX - Spezifikation :
Über ein Trailing
x
.Es wurde in dieser Frage gesagt, dass ein
x
mit dem nachfolgenden Byte eines Zeichens in einer Codierung verwechselt werden könnte. Aber wie sollen wir raten, welches oder welches Zeichen in einer Sprache in einer möglichen Kodierung besser ist, das ist, gelinde gesagt, eine schwierige Aussage.Jedoch; Das ist einfach falsch .
Die einzige Regel, der wir folgen müssen, ist, genau das hinzuzufügen , was wir entfernen.
Es sollte leicht zu verstehen sein , dass , wenn wir etwas zu einer vorhandenen Zeichenfolge (oder Byte - Reihenfolge) hinzufügen und später entfernen wir genau das gleiche , etwas, die ursprüngliche Zeichenfolge (oder Byte - Reihenfolge) müssen gleich sein.
Wohin gehen wir falsch? Wenn wir Zeichen und Bytes mischen .
Wenn wir ein Byte hinzufügen, müssen wir ein Byte entfernen. Wenn wir ein Zeichen hinzufügen, müssen wir genau dasselbe Zeichen entfernen .
Die zweite Option, ein Zeichen hinzuzufügen (und später genau dasselbe Zeichen zu entfernen), kann kompliziert und kompliziert werden, und Codepages und Codierungen können im Weg stehen.
Die erste Option ist jedoch durchaus möglich, und nachdem sie erklärt wurde, wird sie ganz einfach.
Fügen wir ein Byte, ein ASCII-Byte (<127) hinzu und lassen Sie uns ein ASCII-Zeichen im Bereich von az angeben, um die Verschachtelung so gering wie möglich zu halten. Oder wie wir es sagen sollten, ein Byte im Hex-Bereich
0x61
-0x7a
. Wählen wir eines davon, vielleicht ein x (wirklich ein Byte Wert0x78
). Wir können ein solches Byte hinzufügen, indem wir ein x mit einem String verketten (nehmen wir an, einé
):Wenn wir die Zeichenfolge als Folge von Bytes betrachten, sehen wir:
Eine Zeichenfolge, die mit einem x endet.
Wenn wir dieses x (Byte-Wert
0x78
) entfernen , erhalten wir:Das funktioniert ohne Probleme.
Ein etwas schwierigeres Beispiel.
Nehmen wir an, die Zeichenfolge, an der wir interessiert sind, endet in Byte
0xc3
:Und lassen Sie uns ein Byte Wert hinzufügen
0xa9
Die Zeichenfolge ist jetzt so geworden:
Genau das, was ich wollte, die letzten zwei Bytes sind ein Zeichen in utf8 (damit jeder diese Ergebnisse in seiner utf8-Konsole reproduzieren kann).
Wenn wir ein Zeichen entfernen, wird die ursprüngliche Zeichenfolge geändert. Aber das haben wir nicht hinzugefügt, wir haben einen Bytewert hinzugefügt, der zufällig als x geschrieben wird, aber trotzdem als Byte.
Was wir brauchen, um eine Fehlinterpretation von Bytes als Zeichen zu vermeiden. Was wir brauchen, ist eine Aktion, die das verwendete Byte entfernt
0xa9
. Tatsächlich scheinen ash, bash, lksh und mksh genau das zu tun:Aber nicht ksh oder zsh.
Das ist jedoch sehr einfach zu lösen. Sagen wir all diesen Shells, dass sie das Entfernen von Bytes durchführen sollen:
Das war's, alle Shells getestet Arbeit (außer Yash) (für den letzten Teil der Zeichenfolge):
Weisen Sie die Shell einfach an, ein LC_ALL = C-Zeichen zu entfernen. Dies ist genau ein Byte für alle Bytewerte von
0x00
bis0xff
.Lösung für Kommentare:
Für das in den Kommentaren diskutierte Beispiel ist eine mögliche Lösung (die in zsh fehlschlägt):
Dadurch wird das Codierungsproblem behoben.
quelle
zsh
Zurprintf -v
Kompatibilität hinzugefügt mitbash
im Dezember 2015${var%?}
immer ein Byte entfernt wird, theoretisch korrekter ist, aber: 1-LC_ALL
undLC_CTYPE
überschreiben$LANG
, so dass SieLC_ALL=C
2- festlegen müssen, können Sie dasvar=${var%?}
in einer Subshell nicht wie die Änderung durchführen Sie müssen also den Wert und den Status vonLC_ALL
(oder Funktionen, die nicht im POSIX-local
Bereich verfügbar sind) speichern und wiederherstellen. 3- Das Ändern des Gebietsschemas in der Mitte des Skripts wird in einigen Shells wie Yash nicht vollständig unterstützt. Auf der anderen Seite ist die Verwendung in der Praxis.
in Zeichensätzen des realen Lebens nie ein Problem, sodass eine Vermischung mit LC_ALL vermieden wird.Sie können ein Zeichen nach der normalen Ausgabe ausgeben und dann entfernen:
Dies ist eine POSIX-kompatible Lösung.
quelle