it-swarm.com.de

Bash: String in Zeichenarray aufteilen

Ich habe eine Zeichenfolge in einem Bash-Shell-Skript, die ich in ein Array von Zeichen aufteilen möchte, das nicht auf einem Trennzeichen basiert, sondern nur ein Zeichen pro Array-Index. Wie kann ich das machen? Idealerweise werden keine externen Programme verwendet. Lassen Sie mich das anders formulieren. Mein Ziel ist die Portabilität, also sind Dinge wie sed, die sich wahrscheinlich auf jedem POSIX-kompatiblen System befinden, in Ordnung.

50
extropic-engine

Versuchen

echo "abcdefg" | fold -w1

Bearbeiten: Eine elegantere Lösung wurde in Kommentaren vorgeschlagen.

echo "abcdefg" | grep -o .
85
xdazz

Sie können auf jeden Buchstaben bereits ohne Array-Konvertierung einzeln zugreifen:

$ foo="bar"
$ echo ${foo:0:1}
b
$ echo ${foo:1:1}
a
$ echo ${foo:2:1}
r

Wenn das nicht genug ist, könnten Sie so etwas verwenden:

$ bar=($(echo $foo|sed  's/\(.\)/\1 /g'))
$ echo ${bar[1]}
a

Wenn Sie sed oder etwas Ähnliches nicht einmal verwenden können, können Sie die erste oben beschriebene Technik in Kombination mit einer while-Schleife verwenden, wobei die Länge des ursprünglichen Strings (${#foo}) zum Erstellen des Arrays verwendet wird.

Warnung: Der folgende Code funktioniert nicht, wenn die Zeichenfolge Leerzeichen enthält. Ich denke, Vaughn Catos Antwort hat bessere Überlebenschancen mit besonderen Zeichen.

thing=($(i=0; while [ $i -lt ${#foo} ] ; do echo ${foo:$i:1} ; i=$((i+1)) ; done))
28
Mat

Wenn Ihre Zeichenfolge in Variable x gespeichert ist, wird ein Array y mit den einzelnen Zeichen erzeugt:

i=0
while [ $i -lt ${#x} ]; do y[$i]=${x:$i:1};  i=$((i+1));done
9
Vaughn Cato

Als Alternative zum Durchlaufen von 0 .. ${#string}-1 Mit einer for/while-Schleife gibt es zwei andere Möglichkeiten, die ich mir vorstellen kann, um dies mit only bash : mit =~ und mit printf. (Es gibt eine dritte Möglichkeit, eval und einen Sequenzausdruck {..} Zu verwenden, aber dies ist nicht klar genug.)

Wenn die richtige Umgebung und NLS in bash aktiviert sind, funktionieren diese wie erhofft mit Nicht-ASCII-Dateien, wodurch potenzielle Fehlerquellen mit älteren Systemtools wie sed beseitigt werden, falls dies ein Problem darstellt. Diese werden ab bash-3.0 (veröffentlicht 2005) funktionieren.

Konvertieren eines Strings in ein Array in einem einzelnen Ausdruck mithilfe von =~ Und regulären Ausdrücken:

string="wonkabars"
[[ "$string" =~ ${string//?/(.)} ]]       # splits into array
printf "%s\n" "${BASH_REMATCH[@]:1}"      # loop free: reuse fmtstr
declare -a arr=( "${BASH_REMATCH[@]:1}" ) # copy array for later

Auf diese Weise wird eine Erweiterung von string durchgeführt, die jedes einzelne Zeichen für (.) Ersetzt. Anschließend wird dieser generierte reguläre Ausdruck mit einer Gruppierung abgeglichen, um jedes einzelne Zeichen in BASH_REMATCH[] Zu erfassen. Index 0 wird auf die gesamte Zeichenfolge gesetzt, da dieses spezielle Array schreibgeschützt ist. Sie können es nicht entfernen. Beachten Sie :1, Wenn das Array erweitert wird, um Index 0 zu überspringen, falls erforderlich. Einige schnelle Tests auf nicht triviale Zeichenfolgen (> 64 Zeichen) haben ergeben, dass diese Methode wesentlich schneller ist als eine, die Bash-Zeichenfolgen- und Array-Operationen verwendet.

Das Obige funktioniert mit Zeichenfolgen, die Zeilenumbrüche enthalten. =~ Unterstützt POSIX ERE, wobei . Mit Ausnahme von NUL übereinstimmt standardmäßig, dh der reguläre Ausdruck wird ohne REG_NEWLINE. (Das Verhalten von POSIX-Textverarbeitungsprogrammen darf in dieser Hinsicht standardmäßig anders sein und ist es normalerweise.)

Zweite Option mit printf:

string="wonkabars"
ii=0
while printf "%s%n" "${string:ii++:1}" xx; do 
  ((xx)) && printf "\n" || break
done 

Diese Schleife erhöht den Index ii, um jeweils ein Zeichen zu drucken, und bricht ab, wenn keine Zeichen mehr vorhanden sind. Dies wäre noch einfacher, wenn die Bash printf die Anzahl der gedruckten Zeichen (wie in C) anstelle eines Fehlerstatus zurückliefern würde. Stattdessen wird die Anzahl der gedruckten Zeichen in xx mit %n. (Dies funktioniert zumindest bis zu bash-2.05b.)

Mit bash-3.1 und printf -v var Haben Sie etwas mehr Flexibilität und können vermeiden, dass das Ende der Zeichenkette abfällt, wenn Sie etwas anderes als das Drucken der Zeichen tun, z. So erstellen Sie ein Array:

declare -a arr
ii=0
while printf -v cc "%s%n" "${string:(ii++):1}" xx; do 
    ((xx)) && arr+=("$cc") || break
done
8
mr.spuratic

Die einfachste, umfassendste und eleganteste Lösung: 

$ read -a ARRAY <<< $(echo "abcdefg" | sed 's/./& /g')  

und testen 

$ echo ${ARRAY[0]}
  a

$ echo ${ARRAY[1]}
  b

Erklärung: read -a liest das stdin als Array und weist es der Variablen ARRAY zu, die Leerzeichen als Trennzeichen für jedes Arrayelement behandelt. 

Bei der Auswertung, dass der String wiederholt wird, fügen Sie einfach die erforderlichen Leerzeichen zwischen den einzelnen Zeichen hinzu. 

Wir verwenden Here String (<<<), um die Standardeingabe für den Lesebefehl einzugeben. 

string=hello123

for i in $(seq 0 ${#string})
    do array[$i]=${string:$i:1}
done

echo "zero element of array is [${array[0]}]"
echo "entire array is [${array[@]}]"

Das Nullelement des Arrays ist [h]. Das gesamte Array ist [h e l l o 1 2 3 ].

2
0x00

Wenn der Text Leerzeichen enthalten kann:

eval a=( $(echo "this is a test" | sed "s/\(.\)/'\1' /g") )
1
Karoly Horvath
$ echo hello | awk NF=NF FS=
h e l l o

Oder

$ echo hello | awk '$0=RT' RS=[[:alnum:]]
h
e
l
l
o
1
Steven Penny

AWK ist sehr praktisch:

a='123'; echo $a | awk 'BEGIN{FS="";OFS=" "} {print $1,$2,$3}'

dabei ist FS und OFS Trennzeichen für das Einlesen und Ausdrucken

0
Tony Xu

zsh Lösung: Um die Skalarvariable string in arr zu setzen, wird dies ein Array sein:

arr=(${(ps::)string})
0
Ed Grimm

Für diejenigen, die hier gelandet sind und nach dem Verfahren in fish suchen:

Wir können den eingebauten Befehl string (seit v2.3.0) für die Bearbeitung von Strings verwenden.

↪ string split '' abc
a
b
c

Die Ausgabe ist eine Liste, sodass Array-Operationen funktionieren.

↪ for c in (string split '' abc)
      echo char is $c
  end
char is a
char is b
char is c

Hier ist ein komplexeres Beispiel, das die Zeichenfolge mit einem Index durchläuft.

↪ set --local chars (string split '' abc)
  for i in (seq (count $chars))
      echo $i: $chars[$i]
  end
1: a
2: b
3: c
0
Dennis

Als Antwort auf Alexandro de Oliveira halte ich Folgendes für eleganter oder zumindest intuitiver:

while read -r -n1 c ; do arr+=("$c") ; done <<<"hejsan"
0
methuselah-0

Wenn Sie dies in einem Array speichern möchten, können Sie Folgendes tun:

string=foo
unset chars
declare -a chars
while read -N 1
do
    chars[${#chars[@]}]="$REPLY"
done <<<"$string"x
unset chars[$((${#chars[@]} - 1))]
unset chars[$((${#chars[@]} - 1))]

echo "Array: ${chars[@]}"
Array: f o o
echo "Array length: ${#chars[@]}"
Array length: 3

Die letzte x ist erforderlich, um die Tatsache zu berücksichtigen, dass eine neue Zeile nach $string angehängt wird, wenn sie keine enthält.

Wenn Sie NUL-getrennte Zeichen verwenden möchten, können Sie Folgendes versuchen:

echo -n "$string" | while read -N 1
do
    printf %s "$REPLY"
    printf '\0'
done
0
l0b0