So teilen Sie einen String in Bash in ein Array auf

So teilen Sie einen String in Bash in ein Array auf

Ich habe ein Problem mit der Ausgabe eines Programms. Ich muss einen Befehl in Bash starten, dessen Ausgabe (einen String) nehmen und ihn aufteilen, um an bestimmten Stellen neue Zeilen hinzuzufügen. Der String sieht folgendermaßen aus:

battery.charge: 90 battery.charge.low: 30 battery.runtime: 3690 battery.voltage: 230.0 device.mfr: MGE UPS SYSTEMS device.model: Pulsar Evolution 500

im Grunde ist es einxxx.yy.zz:Wert, aber der Wert kann Leerzeichen enthalten. Hier ist die Ausgabe, die ich erhalten möchte

battery.charge: 90
battery.charge.low: 30
battery.runtime: 3690
battery.voltage: 230.0
device.mfr: MGE UPS SYSTEMS
device.model: Pulsar Evolution 500 

Ich habe die Idee, nach dem ersten Punkt zu suchen und dann von dieser Position aus nach einem Leerzeichen zu suchen, um dort eine neue Zeile einzufügen, aber ich bin nicht sicher, wie ich das in Bash erreichen kann.

Antwort1

Reine Bash-Lösung, keine externen Tools zur Verarbeitung der Zeichenfolgen verwendet, nur Parametererweiterung:

#! /bin/bash
str='battery.charge: 90 battery.charge.low: 30 battery.runtime: 3690 battery.voltage: 230.0 device.mfr: MGE UPS SYSTEMS device.model: Pulsar Evolution 500'

IFS=: read -a fields <<< "$str"

for (( i=0 ; i < ${#fields[@]} ; i++ )) ; do
    f=${fields[i]}

    notfirst=$(( i>0 ))
    last=$(( i+1 == ${#fields[@]} ))

    (( notfirst )) && echo -n ${f% *}

    start=('' $'\n' ' ')
    colon=('' ': ')
    echo -n "${start[notfirst + last]}${f##* }${colon[!last]}"
done
echo

Erklärung: $notfirstund $lastsind Boolesche Werte. Der Teil vor dem letzten Leerzeichen ${f% *}wird für das erste Feld nicht gedruckt, da es so etwas nicht gibt. $startund $colonenthalten verschiedene Zeichenfolgen, die die Felder trennen: Beim ersten Element notfirst + lastist 0, also wird nichts vorangestellt, für den Rest der Zeilen $notfirstist 1, also wird eine neue Zeile gedruckt, und für die letzte Zeile ergibt die Addition 2, also wird ein Leerzeichen gedruckt. Dann wird der Teil nach dem letzten Leerzeichen gedruckt ${f##* }. Doppelpunkt wird für alle Zeilen außer der letzten gedruckt.

Antwort2

Mit GNU sed können Sie jeden zusammenhängenden String (also ohne Leerzeichen), der mit endet, abgleichen :und dann vor allen außer dem ersten einen Zeilenumbruch einfügen:

sed 's/[^[:space:]]\+:/\n&/g2'

Wenn Ihre Version von sed die Erweiterung nicht unterstützt gn, können Sie einen einfachen gModifikator verwenden

sed 's/[^[:space:]]\{1,\}:/\
&/g'

das funktioniert genauso, außer dass vor dem ersten Schlüssel eine zusätzliche neue Zeile gedruckt wird. Sie können perl -pe 's/\S+:/\n$&/g'mit derselben Einschränkung verwenden (es gibt möglicherweise ein Perl-Äquivalent zu GNU sed, g2aber ich kenne es nicht).

Antwort3

Eine perlLösung:

$ perl -pe 's{\S+:}{$seen++ ? "\n$&" : "$&"}ge' file
battery.charge: 90 
battery.charge.low: 30 
battery.runtime: 3690 
battery.voltage: 230.0 
device.mfr: MGE UPS SYSTEMS 
device.model: Pulsar Evolution 500

Erläuterung

  • \S+:stimmt mit dem Zeichenfolgenende überein :.
  • Bei allen übereinstimmenden Zeichenfolgen fügen wir vor ihnen, ("\n$&")außer vor der ersten , eine neue Zeile ein ($seen++).

Antwort4

Hier ist ein naiver Ansatz, der funktionieren sollte, vorausgesetzt, es ist Ihnen egal, dass Tabulatoren und Zeilenumbrüche in der Eingabe (falls vorhanden) in einfache Leerzeichen umgewandelt werden.

Die Idee ist einfach: Teilen Sie die Eingabe bei Leerzeichen auf und drucken Sie jedes Token aus, außer dass Sie Token, die mit enden, :ein neues Zeilenende voranstellen (und vor den anderen wieder ein Leerzeichen hinzufügen). Die $countVariable und das zugehörige ifsind nur nützlich, um eine anfängliche leere Zeile zu verhindern. Könnte entfernt werden, wenn das kein Problem ist. (Das Skript geht davon aus, dass sich die Eingabe in einer Datei namens intputim aktuellen Verzeichnis befindet.)

#! /bin/bash

count=0
for i in $(<input) ; do
   fmt=
   if [[ $i =~ :$ ]] ; then
       if [[ $count -gt 0 ]] ; then
           fmt="\n%s"
       else
           fmt="%s"
       fi
       ((count++))
   else
       fmt=" %s"
   fi
   printf "$fmt" "$i"
done
echo
echo "Num items: $count"

Ich hoffe jedoch, dass jemand eine schönere Alternative findet.

$ cat input
battery.charge: 90 battery.charge.low: 30 battery.runtime: 3690 battery.voltage: 230.0 device.mfr: MGE UPS SYSTEMS device.model: Pulsar Evolution 500
$ ./t.sh
battery.charge: 90
battery.charge.low: 30
battery.runtime: 3690
battery.voltage: 230.0
device.mfr: MGE UPS SYSTEMS
device.model: Pulsar Evolution 500
Num items: 6

verwandte Informationen