Ich habe das folgende Bash-Skript, das zwei Zufallszahlen in derselben Zeile ausgibt.
#!/bin/bash
for i in 1 2; do
unset var
until [ "$var" -lt 10000 ] 2>/dev/null; do
var="$RANDOM"
done
printf "%s," "${var/%,/}"
done
Die Ausgabe ist:
5751,2129,
Ich versuche, das Komma am Ende zu entfernen, $var
mit dem ich es versucht habe, "${var/%,/}"
damit die Ausgabe von $var = 5751,2129 verwendet werden kann. Kann mir jemand dabei helfen?
Antwort1
Nachdem Sie wie folgt zugewiesen haben: enthält var="$RANDOM"
die var
Variable einen String aus der Erweiterung von $RANDOM
. In Bash $RANDOM
wird zu einer Dezimalzahl aus dem Bereich von 0
bis erweitert 32767
. Da dort kein Zeichen steht , ist es sinnlos, später zu versuchen, es aus der Erweiterung von ,
zu entfernen .,
$var
Die Kommazeichen, die Sie in der Ausgabe beobachtet haben, stammen aus diesem Teil des Codes:
printf "%s," "${var/%,/}"
# here ^
Dieser Befehl wurde in jeder Iteration der Schleife aufgerufen, sodass jede Iteration der Ausgabe ein Kommazeichen hinzufügte.
Was gedruckt wurde, kann nicht mehr rückgängig gemacht werden. Es gibt Nuancen:
- Sie können die Ausgabe an einen Filter weiterleiten, der dann Teile davon entfernt. Der Filter kann sich außerhalb des Skripts befinden (z. B. rufen Sie auf
the_script | the_filter
); oder Sie können die Ausgabe einesTeildes Skripts zu einem Filter innerhalb des Skripts. Im letzteren Fall wird die Ausgabe des gesamten Skripts gefiltert; dennoch wird das, was von dem Teil des Skripts gedruckt wurde, nicht ungedruckt; ich meine, estutzum Filter gelangen. Der Filter entfernt es später. - Wenn Sie auf einem Terminal drucken, können Sie einige Teile der vorherigen Ausgabe mit neuen Daten überschreiben lassen. Es gibt Zeichen und Sequenzen, um den Cursor zu bewegen; sie gelangen trotzdem alle zum Terminal. Sie können die vorherige Ausgabe fast sofort optisch verbergen, aber wenn Sie die Ausgabe in eine Datei umleiten (oder auf ein Terminal, das die verwendeten Sequenzen nicht versteht), werden Sie feststellen, dass alles da ist.
Der richtige Weg, das unerwünschte Komma loszuwerden, besteht darin, es gar nicht erst auszudrucken oder es im Skript zu filtern. Es gibt mehrere Möglichkeiten, dies zu tun, und es ist nicht meine Absicht, sie alle aufzuzählen. Ich werde einige davon besprechen. Ich gehe davon aus, dass Sie auch mögliche Ansätze für Schleifen mit mehr als zwei Iterationen kennenlernen möchten; vielleicht für Schleifen, bei denen die Anzahl der Iterationen nicht im Voraus bekannt ist; vielleicht für Schleifen, die nicht durch Zahlen aufgezählt werden; vielleicht für Schleifen, die möglicherweise nie enden (z. B. while true
anstelle von for
).
Hinweis: Sie haben verwendet printf "%s," "${var/%,/}"
, es wird kein abschließendes Zeilenumbruchzeichen gedruckt. Ich werde versuchen, dieses Verhalten nach Möglichkeit zu reproduzieren.
Einige mögliche Ansätze:
Das Innere Ihrer Schleife hängt nicht von ab
$i
. Sie können die Schleife entfernen und zwei separate Variablen verwenden:unset var1 until [ "$var1" -lt 10000 ] 2>/dev/null; do var1="$RANDOM" done unset var2 until [ "$var2" -lt 10000 ] 2>/dev/null; do var2="$RANDOM" done printf '%s,%s' "$var1" "$var2"
Anmerkungen:
- Es ist nichtTROCKEN.
- Es ist nicht gut skalierbar. (Was wäre, wenn Sie hätten
for i in {1..100}
?) - Es wird umständlich, wenn die Anzahl der Iterationen nicht im Voraus bekannt ist.
Sie können Ihren aktuellen Code in eine Pipe einfügen und das abschließende Komma herausfiltern. Beispiel:
for i in 1 2; do unset var until [ "$var" -lt 10000 ] 2>/dev/null; do var="$RANDOM" done printf "%s," "$var" done | sed 's/,$//'
Anmerkungen:
sed
(oder welchen Filter Sie auch immer verwenden) kann die unvollständige Zeile (eine Zeile, die nicht durch das Zeilenumbruchzeichen abgeschlossen wird), die die Schleife erzeugt, verarbeiten oder nicht. In diesem Fallsed
hängt es von der Implementierung ab.- Wenn
sed
es damit umgeht, kann die Ausgabe dennoch mit einer neuen Zeile beendet werden. sed
(oder welchen Filter Sie auch immer verwenden) kann möglicherweise eine zu lange Zeile nicht verarbeiten. Der obige spezielle Code erzeugt eine einigermaßen kurze Zeile, aber im Allgemeinen (stellen Sie sich viele Iterationen vor) kann die Länge ein Problem darstellen.sed
muss als zeilenorientiertes Tool eine ganze Zeile lesen, bevor es sie verarbeitet. In diesem Fall muss es die gesamte Eingabe lesen. Sie erhalten nichts davon, bis alle Iterationen abgeschlossen sind.- Die Schleife wird in einer Untershell ausgeführt. Im Allgemeinen möchten Sie möglicherweise, dass sie aus irgendeinem Grund in der Hauptshell ausgeführt wird.
Sie können die Ausgabe Ihres aktuellen Codes in einer Variablen erfassen. Am Ende entfernen Sie das abschließende Komma, während Sie die Variable erweitern:
capture="$(for i in 1 2; do unset var until [ "$var" -lt 10000 ] 2>/dev/null; do var="$RANDOM" done printf "%s," "$var" done)" printf "%s" "${capture%,}"
Anmerkungen:
- Die Schleife wird in einer Untershell ausgeführt. Im Allgemeinen möchten Sie möglicherweise, dass sie aus irgendeinem Grund in der Hauptshell ausgeführt wird.
- Der Code ist still, bis er das Ende erreicht
printf
(außerhalb der Schleife). Sie erhalten nichts, bis alle Iterationen abgeschlossen sind. - Im Allgemeinen kann eine Schleife eine beliebige Anzahl von Bytes ausgeben. Ich denke, Bash kann eine beträchtliche Menge an Daten in einer Variablen speichern. Da es
printf
sich um ein integriertes Steuerelement handelt, kann eswahrscheinlichhandhabenprintf "%s" "${capture%,}"
ohne zu schlagendie Begrenzung der Länge einer Befehlszeile. Ich habe das nicht gründlich getestet, da es meiner Meinung nach ohnehin nicht die beste Vorgehensweise ist, große Datenmengen in einer Shell-Variable zu speichern. Dennoch kann diese Vorgehensweise gerechtfertigt sein, wenn Sie wissen, dass die Ausgabelänge begrenzt ist. (Zur Info: Der obige Code erzeugt mit Sicherheit eine sehr kurze Ausgabe.) - Bash kann keine NUL-Zeichen in einer Variable speichern (die meisten Shells können das nicht, zsh schon). Außerdem
$()
werden alle nachfolgenden Zeilenumbrüche entfernt. Das bedeutet, dass Sie eine Variable nicht verwenden können, um einwillkürlichAusgabe und spätere genaue Reproduktion. (Zur Erinnerung: Im obigen Code$()
generiert das Fragment darin weder NULs noch abschließende Zeilenumbrüche.)
Anstatt die Ausgabe zu erfassen, können Sie jede Iteration an eine Variable anhängen:
capture='' for i in 1 2; do unset var until [ "$var" -lt 10000 ] 2>/dev/null; do var="$RANDOM" done capture="$capture$var," done printf "%s" "${capture%,}"
Anmerkungen:
- Der Code wird in der Hauptshell ausgeführt (nicht in einer Untershell).
- Einschränkungen beim Speichern von Daten in einer Variablen (siehe vorherige Methode) gelten weiterhin.
- In Bash können Sie an die Variable mit anhängen
capture+="$var,"
. (Hinweis: Wenn das Integer-Attribut für die Variable gesetzt wurde, dann=+
bedeutet "hinzufügen", nicht "anhängen".)
Sie können die letzte Iteration erkennen und ein Format ohne verwenden
,
:# this example is more educative with more than two iterations for i in {1..5}; do unset var until [ "$var" -lt 10000 ] 2>/dev/null; do var="$RANDOM" done if [ "$i" -eq 5 ]; then printf "%s" "$var" else printf "%s," "$var" fi done
Anmerkungen:
- Keine Unterschale.
- Das Erkennen der letzten Iteration ist schwieriger, wenn Sie die Nummer nicht im Voraus kennen.
- Noch schwieriger ist es, wenn Sie über ein Array iterieren (z. B.
for i in "${arr[@]}"
). - Jede Iteration wird sofort gedruckt, Sie erhalten die Ausgabe sequenziell. Dies würde auch funktionieren, wenn die Schleife unendlich wäre.
Sie können die erste Iteration erkennen und ein Format ohne verwenden . Beachten Sie, dass Sie in Ihrem ursprünglichen Code anstelle von
,
hätten verwenden können ; dann würden Sie anstelle von erhalten . Mit dieser Änderung kann jede der oben genannten Methoden, die das abschließende Komma vermeidet oder entfernt, in eine Methode umgewandelt werden, die das führende Komma vermeidet oder entfernt. Die allerletzte Methode wird:,%s
%s,
,5751,2129
5751,2129,
# this example is more educative with more than two iterations for i in {1..5}; do unset var until [ "$var" -lt 10000 ] 2>/dev/null; do var="$RANDOM" done if [ "$i" -eq 1 ]; then printf "%s" "$var" else printf ",%s" "$var" fi done
Anmerkungen:
- Keine Unterschale.
- Das Erkennen der ersten Iteration ist einfach, wenn Sie immer mit
1
(oder im Allgemeinen mit einer festen eindeutigen Zeichenfolge) beginnen. - Aber es ist schwieriger, wenn Sie über ein Array iterieren, z. B.
for i in "${arr[@]}"
. Sie sollten nicht prüfen,if [ "$i" = "${arr[1]}" ]
da es"${arr[1]}"
später im Array ein identisches Element geben könnte. Eine einfache Möglichkeit, damit umzugehen, besteht darin, einen Index für die Schleife beizubehalten (index=1
vor der Schleife, dann am Ende jeder Iteration um eins erhöhen) und seinen Wert gegen zu testen1
; ich fände einen solchen Code jedoch etwas umständlich. - Jede Iteration wird sofort gedruckt, Sie erhalten die Ausgabe sequenziell. Dies würde auch funktionieren, wenn die Schleife unendlich wäre.
Sie können
,
sich selbst aus einer Variablen erstellen. Starten Sie die Schleife mit einer leeren Variable und setzen Sie sie,
am Ende jeder Iteration auf . Dadurch wird der Wert effektiv nur einmal am Ende derErsteIteration. Beispiel:# this example is more educative with more than two iterations separator='' for i in {1..5}; do unset var until [ "$var" -lt 10000 ] 2>/dev/null; do var="$RANDOM" done printf "%s%s" "$separator" "$var" separator=, done
Anmerkungen:
- Keine Unterschale.
- Funktioniert gut, auch wenn über ein Array iteriert wird.
- Jede Iteration wird sofort gedruckt, Sie erhalten die Ausgabe sequenziell. Dies würde auch funktionieren, wenn die Schleife unendlich wäre.
Ich persönlich finde diese Methode recht elegant.
Allgemeine Hinweise:
- Jeder Ausschnitt erzeugt eine Ausgabe ohne abschließende Zeilenumbruchzeile (mit Ausnahme des Ausschnitts mit
sed
oder einem anderen Filter). Wenn Sie die gesamte Ausgabe benötigen, um eine ordnungsgemäß abgeschlossene Textzeile zu bilden, führen Sie nach der Schleife runprintf '\n'
(oder sole ) aus.echo
- Sie möchten
,
ein Trennzeichen sein, kein Terminator. Das bedeutet, dass eine Schleife mit genau null Iterationen dieselbe leere Ausgabe erzeugt wie eine Schleife mit genau einer Iteration, wenn$var
die Iteration zu einer leeren Zeichenfolge erweitert wird. In unserem Fall$var
wird jedes Mal eine nicht leere Zeichenfolge erweitert und wir wissen, dass wir mehr als null Iterationen haben. Im Allgemeinen kann die Verwendung eines Trennzeichens anstelle eines Terminators jedoch zu Mehrdeutigkeiten führen.