Bash: So entfernen Sie ein Komma am Ende einer von einer Schleife generierten Variablen

Bash: So entfernen Sie ein Komma am Ende einer von einer Schleife generierten Variablen

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, $varmit 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 varVariable einen String aus der Erweiterung von $RANDOM. In Bash $RANDOMwird zu einer Dezimalzahl aus dem Bereich von 0bis 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 trueanstelle 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:

  1. 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.
  2. 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 Fall sedhängt es von der Implementierung ab.
    • Wenn sedes 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.
    • sedmuss 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.
  3. 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 printfsich um ein integriertes Steuerelement handelt, kann eswahrscheinlichhandhaben printf "%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.)
  4. 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".)
  5. 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.
  6. 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,21295751,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=1vor der Schleife, dann am Ende jeder Iteration um eins erhöhen) und seinen Wert gegen zu testen 1; 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.
  7. 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 sedoder einem anderen Filter). Wenn Sie die gesamte Ausgabe benötigen, um eine ordnungsgemäß abgeschlossene Textzeile zu bilden, führen Sie nach der Schleife run printf '\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 $vardie Iteration zu einer leeren Zeichenfolge erweitert wird. In unserem Fall $varwird 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.

verwandte Informationen