Wie vermeide ich das Ersetzen von Bash-Befehlen, um das Zeilenumbruchzeichen zu entfernen?

77

Um die Ausführung von Bash-Skripten zu beschleunigen, möchte ich das Ergebnis eines Befehls mithilfe der Befehlssubstitution in einer Variablen behalten, aber die Befehlssubstitution ersetzt das 0x0AZeilenumbruchzeichen durch ein Leerzeichen. Zum Beispiel:

a=`df -H`

oder

a=$( df -H )

Wenn ich weiter verarbeiten möchte, werden $adie Zeilenumbruchzeichen durch ein Leerzeichen ersetzt und alle Zeilen befinden sich jetzt in einer Zeile, was viel schwieriger zu erfassen ist:

echo $a

Was wären die einfachen Tricks, um zu verhindern, dass das Zeilenumbruchzeichen durch die Befehlsersetzung entfernt wird?

Laurent
quelle

Antworten:

119

Nicht nachfolgende Zeilenumbrüche werden nicht entfernt

Die gesuchten Zeilenumbrüche sind da, Sie sehen sie einfach nicht, weil Sie sie verwenden, echoohne die Variable in Anführungszeichen zu setzen .

Validierung :

$ a=$( df -H )
$ echo $a
Filesystem Size Used Avail Use% Mounted on /dev/sda3 276G 50G 213G 19% / udev 2.1G 4.1k 2.1G 1% /dev tmpfs 832M 820k 832M 1% /run none 5.3M 0 5.3M 0% /run/lock none 2.1G 320k 2.1G 1% /run/shm
$ echo "$a"
Filesystem      Size  Used Avail Use% Mounted on
/dev/sda3       276G   50G  213G  19% /
udev            2.1G  4.1k  2.1G   1% /dev
tmpfs           832M  820k  832M   1% /run
none            5.3M     0  5.3M   0% /run/lock
none            2.1G  320k  2.1G   1% /run/shm
$ 

Nachgestellte Zeilenumbrüche werden entfernt

Wie @ user4815162342 richtig hervorhob , werden nachfolgende Zeilenumbrüche durch Befehlsersetzung entfernt, obwohl Zeilenumbrüche in der Ausgabe nicht entfernt werden. Siehe Experiment unten:

$ a=$'test\n\n'
$ echo "$a"
test


$ b=$(echo "$a")
$ echo "$b"
test
$

In den meisten Fällen spielt dies keine Rolle, da echodie entfernte neue Zeile hinzugefügt wird (es sei denn, sie wird mit der -nOption aufgerufen ). In einigen Randfällen gibt es jedoch mehr als eine nachfolgende Zeile in der Ausgabe eines Programms, und sie sind für diese von Bedeutung irgendein Grund.

Problemumgehungen

1. Fügen Sie ein Dummy-Zeichen hinzu

In diesem Fall können Sie, wie bei @Scrutinizer erwähnt, die folgende Problemumgehung verwenden:

$ a=$(printf 'test\n\n'; printf x); a=${a%x}
$ echo "$a"
test


$ 

Erläuterung: Nach den Zeilenumbrüchen wirdxder Ausgabe (usingprintf x) einZeichenhinzugefügt. Da die Zeilenumbrüche nicht mehr nachlaufen , werden sie durch die Befehlsersetzung nicht entfernt. Der nächste Schritt besteht darin, dasxhinzugefügte Element mithilfe des%Operators inzu entfernen${a%x}. Jetzt haben wir die Originalausgabe mit allen Zeilenumbrüchen !!!

2. Lesen Sie mit Prozessersetzung

Anstatt die Befehlssubstitution zu verwenden, um die Ausgabe eines Programms einer Variablen zuzuweisen, können wir stattdessen die Prozessersetzung verwenden , um die Ausgabe des Programms dem readintegrierten Befehl zuzuführen (Gutschrift an @ormaaj ). Durch die Prozessersetzung bleiben alle Zeilenumbrüche erhalten. Das Lesen der Ausgabe in eine Variable ist etwas schwierig, aber Sie können dies folgendermaßen tun:

$ IFS= read -rd '' var < <( printf 'test\n\n' ) 
$ echo "$var"
test


$ 

Erläuterung:

  • Wir setzen das interne Feldtrennzeichen für den Lesebefehl auf null mit IFS=. Andernfalls readwürde nicht die gesamte Ausgabe zugewiesen var, sondern nur das erste Token.
  • Wir rufen readmit Optionen auf -rd ''. Das rist zum Verhindern des umgekehrten Schrägstrich als Sonderzeichen zu handeln und mit d ''den Begrenzer zu nichts festgelegt, so dass Lesen Sie die gesamte Ausgabe liest, statt nur die erste Zeile.

3. Lesen Sie aus einem Rohr

Anstatt eine Befehls- oder Prozessersetzung zu verwenden, um die Ausgabe eines Programms einer Variablen zuzuweisen, können wir stattdessen die Ausgabe des Programms an den readBefehl weiterleiten (Gutschrift an @ormaaj ). Durch die Rohrleitungen bleiben auch alle Zeilenumbrüche erhalten. Beachten Sie jedoch, dass wir diesmal das lastpipeoptionale Verhalten der Shell mithilfe der integrierten Funktion shoptfestlegen . Dies ist erforderlich, damit der readBefehl in der aktuellen Shell-Umgebung ausgeführt wird. Andernfalls wird die Variable in einer Subshell zugewiesen und kann vom Rest des Skripts nicht aufgerufen werden.

$ cat test.sh 
#!/bin/bash
shopt -s lastpipe
printf "test\n\n" | IFS= read -rd '' var
echo "$var"
$ ./test.sh 
test


$
user000001
quelle
3
Die Hinterzeilenumbrüche sind noch entfernt, aber, und ich fürchte , es nicht vermieden werden kann. (Das OP kümmert sich wahrscheinlich nicht um diese, aber das ist im Allgemeinen gut zu wissen.)
user4815162342
6
@ user4815162342: Interessant, eine beschissene Lösung könnte seina=$(printf 'test\n\n'; printf x); echo "${a%x}"
Scrutinizer
1
Es ist keine sehr gute Problemumgehung. Es gibt keinen guten Weg, um zu vermeiden, dass nachfolgende Zeilenumbrüche durch eine Befehlsersetzung entfernt werden. Die beste Lösung besteht darin, readnicht standardmäßige Optionen für die Zuweisung zu verwenden. shopt -s lastpipe; printf %s "$myData" | IFS= read -rd '' var. Bash's printf -vist auch in ähnlichen Situationen nützlich.
Ormaaj
1
@ user000001 In Bash ist es viel üblicher und abwärtskompatibler, eine Prozessersetzung in einer Umleitung zu verwenden, als sich darauf zu verlassen lastpipe(obwohl ich sie normalerweise lastpipesowieso verwende). Der Grund, warum Sie dies bemerken, liegt in der Jobkontrolle. Interaktive Shells haben set -mdie Möglichkeit, Pipelines in separaten Prozessgruppen auszuführen, was eine Unter-Shell für das letzte Element erforderlich macht. Da die Jobsteuerung nicht für Kinder einer Subshell gilt, ist das Einpacken alles eine Problemumgehung (...)(was ich sowieso fast immer mache, wenn ich interaktiv teste).
Ormaaj
5
Hinweis: readWird mit Null beendet, wenn EOF erreicht wird. Dies führt dazu, dass die Methoden 2 und 3 mit einem Wert ungleich Null beendet werden, wodurch der Beendigungsstatus des Hauptbefehls maskiert wird. In Methode 1 main_cmd ; printf xüberschreibt die Verwendung auch den Exit-Code der Hauptbefehle und stört die Fehlerbehandlung. Versuchen Sie es main_cmd && printf xstattdessen (wenn nachfolgende Zeilen nur beim Beenden des Erfolgs wichtig sind) oder main_cmd ; ec=$?; printf x; exit $ecum nachgestellte Leerzeichen sowohl in Erfolgs- als auch in Fehlerfällen beizubehalten.
AnyDev
1

Ich habe versucht, meinen Kopf darum zu wickeln, weil ich Bash verwendet habe, um das Ergebnis des Ausführens des Interpreters in einem F # -Skript zu streamen. Nach einigem Ausprobieren stellte sich heraus, dass dies das Problem löste:

$ cat fsi.ch
#!/bin/bash
echo "$(fsharpi --quiet --exec --nologo $1)"

$ fsi.ch messages.fsx
Welcome to my program. Choose from the menu:
new | show | remove

Vorausgesetzt natürlich, Sie müssen ein Terminalprogramm ausführen. Hoffe das hilft.

ワ イ き ん ぐ
quelle
1

Ein weiterer "netter Trick" ist die Verwendung des Wagenrücklaufzeichens, das verhindert, dass die neue Zeile entfernt wird, aber nichts zur Ausgabe hinzufügt:

$ my_func_1 () {
>     echo "This newline is squashed"
> }
$ my_func_2 () {
>     echo "This newline is not squashed"
>     echo -n $'\r'
> }
$ echo -n "$(my_func_1)" && echo -n "$(my_func_2)" && echo done
This newline is squashedThis newline is not squashed
done
$

Aber Käufer aufgepasst: Wie in den Kommentaren erwähnt, kann dies gut für Ausgaben funktionieren, die einfach an das Terminal gesendet werden. Wenn Sie dies jedoch an einen anderen Prozess weitergeben, können Sie es verwirren, da es wahrscheinlich nicht mit dem seltsamen Beenden rechnen wird '\r'.

Daphtdazz
quelle
1
Netter Trick, wenn die Ausgabe nur visuell und nicht programmgesteuert verarbeitet werden soll. Wenn Sie sich jedoch die Ausgabe von echo -n "$(my_func_2)" | cat -Aansehen, werden Sie feststellen, dass die $'\r'Ausgabe in der Ausgabe erhalten bleibt. Dies kann zu Problemen führen, wenn der Empfängerprozess dies nicht erwartet.
user000001