Ich habe eine Variable wie diese:
words="这是一条狗。"
Ich möchte eine auf jedes der Zeichen für Schleife machen, einer nach dem anderen, zum Beispiel zuerst character="这"
, dann character="是"
, character="一"
usw.
Die einzige Möglichkeit, die ich kenne, besteht darin, jedes Zeichen in einer separaten Zeile in einer Datei auszugeben und dann zu verwenden while read line
. Dies scheint jedoch sehr ineffizient zu sein.
- Wie kann ich jedes Zeichen in einer Zeichenfolge durch eine for-Schleife verarbeiten?
Antworten:
Mit
sed
ondash
shell of habeLANG=en_US.UTF-8
ich Folgendes richtig gemacht:$ echo "你好嗎 新年好。全型句號" | sed -e 's/\(.\)/\1\n/g' 你 好 嗎 新 年 好 。 全 型 句 號
und
$ echo "Hello world" | sed -e 's/\(.\)/\1\n/g' H e l l o w o r l d
Somit kann die Ausgabe mit wiederholt werden
while read ... ; do ... ; done
bearbeitet für Beispieltext ins Englische übersetzen:
"你好嗎 新年好。全型句號" is zh_TW.UTF-8 encoding for: "你好嗎" = How are you[ doing] " " = a normal space character "新年好" = Happy new year "。全型空格" = a double-byte-sized full-stop followed by text description
quelle
Sie können eine
for
Schleife im C-Stil verwenden :foo=string for (( i=0; i<${#foo}; i++ )); do echo "${foo:$i:1}" done
${#foo}
erweitert sich auf die Länge vonfoo
.${foo:$i:1}
dehnt sich ab Position$i
der Länge 1 zum Teilstring aus.quelle
bash
benötigt wird.for (( _expr_ ; _expr_ ; _expr_ )) ; do _command_ ; done
und nicht dasselbe wie $ (( Ausdruck )) oder (( Ausdruck )). In allen drei Bash-Konstrukten wird expr gleich behandelt und $ (( expr )) ist ebenfalls POSIX.bash
, die in einem arithmetischen Kontext ausgewertet werden.${#var}
gibt die Länge von zurückvar
${var:pos:N}
Zeichen kehrt N vonpos
vorwärtsBeispiele:
$ words="abc" $ echo ${words:0:1} a $ echo ${words:1:1} b $ echo ${words:2:1} c
so ist es einfach zu iterieren.
ein anderer Weg:
$ grep -o . <<< "abc" a b c
oder
$ grep -o . <<< "abc" | while read letter; do echo "my letter is $letter" ; done my letter is a my letter is b my letter is c
quelle
Ich bin überrascht, dass niemand die offensichtliche
bash
Lösung erwähnt hat, die nurwhile
und verwendetread
.while read -n1 character; do echo "$character" done < <(echo -n "$words")
Beachten Sie die Verwendung von
echo -n
, um die überflüssige neue Zeile am Ende zu vermeiden.printf
ist eine weitere gute Option und möglicherweise besser für Ihre speziellen Bedürfnisse geeignet. Wenn Sie Leerzeichen ignorieren möchten, ersetzen Sie diese"$words"
durch"${words// /}"
.Eine andere Option ist
fold
. Bitte beachten Sie jedoch, dass es niemals in eine for-Schleife eingespeist werden sollte. Verwenden Sie stattdessen eine while-Schleife wie folgt:while read char; do echo "$char" done < <(fold -w1 <<<"$words")
Der Hauptvorteil der Verwendung des externen
fold
Befehls (des Coreutils- Pakets) wäre die Kürze. Sie können die Ausgabe wie folgt einem anderen Befehl wiexargs
(Teil des findutils- Pakets) zuführen :fold -w1 <<<"$words" | xargs -I% -- echo %
Sie möchten den
echo
im obigen Beispiel verwendeten Befehl durch den Befehl ersetzen, den Sie für jedes Zeichen ausführen möchten . Beachten Sie, dassxargs
Leerzeichen standardmäßig verworfen werden. Sie können-d '\n'
dieses Verhalten deaktivieren.Internationalisierung
Ich habe gerade
fold
mit einigen asiatischen Charakteren getestet und festgestellt, dass es keine Unicode-Unterstützung gibt. Obwohl es für ASCII-Anforderungen in Ordnung ist, funktioniert es nicht für alle. In diesem Fall gibt es einige Alternativen.Ich würde wahrscheinlich durch
fold -w1
ein awk-Array ersetzen :awk 'BEGIN{FS=""} {for (i=1;i<=NF;i++) print $i}'
Oder der
grep
in einer anderen Antwort erwähnte Befehl:Performance
Zu Ihrer Information, ich habe die 3 oben genannten Optionen verglichen. Die ersten beiden waren schnell und fast gebunden, wobei die Faltschleife etwas schneller war als die while-Schleife. Es überrascht nicht, dass
xargs
es am langsamsten war ... 75x langsamer.Hier ist der (abgekürzte) Testcode:
words=$(python -c 'from string import ascii_letters as l; print(l * 100)') testrunner(){ for test in test_while_loop test_fold_loop test_fold_xargs test_awk_loop test_grep_loop; do echo "$test" (time for (( i=1; i<$((${1:-100} + 1)); i++ )); do "$test"; done >/dev/null) 2>&1 | sed '/^$/d' echo done } testrunner 100
Hier sind die Ergebnisse:
quelle
character
ist für Leerzeichen mit der einfachenwhile read
Lösung leer , was problematisch sein kann, wenn verschiedene Arten von Leerzeichen voneinander unterschieden werden müssen.read -n1
zuread -N1
erforderlich war, um Leerzeichen korrekt zu behandeln.Ich glaube, es gibt immer noch keine ideale Lösung, die alle Leerzeichen korrekt beibehält und schnell genug ist. Deshalb werde ich meine Antwort veröffentlichen. Die Verwendung
${foo:$i:1}
funktioniert, ist aber sehr langsam, was sich besonders bei großen Saiten bemerkbar macht, wie ich weiter unten zeigen werde.Meine Idee ist eine Erweiterung einer von Six vorgeschlagenen Methode , die
read -n1
einige Änderungen beinhaltet , um alle Zeichen beizubehalten und für jede Zeichenfolge korrekt zu funktionieren:while IFS='' read -r -d '' -n 1 char; do # do something with $char done < <(printf %s "$string")
Wie es funktioniert:
IFS=''
- Durch die Neudefinition des internen Feldtrennzeichens in eine leere Zeichenfolge wird das Entfernen von Leerzeichen und Tabulatoren verhindert. Wenn Sie dies in derselben Zeile wie tunread
, hat dies keine Auswirkungen auf andere Shell-Befehle.-r
- Mittel „raw“, das verhindert , dassread
von der Behandlung\
am Ende der Zeile als ein spezieller Zeilenverkettungs Charakter.-d ''
- Wenn Sie eine leere Zeichenfolge als Trennzeichen übergeben, wird verhindert, dassread
Zeilenumbrüche entfernt werden. Bedeutet eigentlich, dass Nullbyte als Trennzeichen verwendet wird.-d ''
ist gleich-d $'\0'
.-n 1
- Bedeutet, dass jeweils ein Zeichen gelesen wird.printf %s "$string"
- Verwendenprintf
stattecho -n
ist sicherer, weilecho
behandelt-n
und-e
als Optionen. Wenn Sie "-e" als Zeichenfolge übergeben,echo
wird nichts gedruckt.< <(...)
- Übergabe der Zeichenfolge an die Schleife mithilfe der Prozessersetzung. Wenn Sie stattdessen Here-Strings verwenden (done <<< "$string"
), wird am Ende ein zusätzliches Zeilenumbruchzeichen angehängt.printf %s "$string" | while ...
Wenn Sie einen String durch eine pipe ( ) übergeben, wird die Schleife in einer Unterschale ausgeführt, was bedeutet, dass alle variablen Operationen innerhalb der Schleife lokal sind.Lassen Sie uns nun die Leistung mit einer riesigen Zeichenfolge testen. Ich habe die folgende Datei als Quelle verwendet:
https://www.kernel.org/doc/Documentation/kbuild/makefiles.txt
Das folgende Skript wurde über den
time
Befehl aufgerufen :#!/bin/bash # Saving contents of the file into a variable named `string'. # This is for test purposes only. In real code, you should use # `done < "filename"' construct if you wish to read from a file. # Using `string="$(cat makefiles.txt)"' would strip trailing newlines. IFS='' read -r -d '' string < makefiles.txt while IFS='' read -r -d '' -n 1 char; do # remake the string by adding one character at a time new_string+="$char" done < <(printf %s "$string") # confirm that new string is identical to the original diff -u makefiles.txt <(printf %s "$new_string")
Und das Ergebnis ist:
Wie wir sehen können, ist es ziemlich schnell.
Als nächstes habe ich die Schleife durch eine ersetzt, die die Parametererweiterung verwendet:
for (( i=0 ; i<${#string}; i++ )); do new_string+="${string:$i:1}" done
Die Ausgabe zeigt genau, wie schlecht der Leistungsverlust ist:
Die genauen Zahlen können auf verschiedenen Systemen sehr unterschiedlich sein, aber das Gesamtbild sollte ähnlich sein.
quelle
Ich habe dies nur mit ASCII-Zeichenfolgen getestet, aber Sie können Folgendes tun:
while test -n "$words"; do c=${words:0:1} # Get the first character echo character is "'$c'" words=${words:1} # trim the first character done
quelle
Die Schleife im C-Stil in @ chepners Antwort befindet sich in der Shell-Funktion
update_terminal_cwd
, und diegrep -o .
Lösung ist clever, aber ich war überrascht, dass keine Lösung verwendet wurdeseq
. Hier ist meins:read word for i in $(seq 1 ${#word}); do echo "${word:i-1:1}" done
quelle
Es ist auch möglich, die Zeichenfolge mithilfe
fold
dieses Arrays in ein Zeichenarray aufzuteilen und anschließend zu durchlaufen:for char in `echo "这是一条狗。" | fold -w1`; do echo $char done
quelle
#!/bin/bash word=$(echo 'Your Message' |fold -w 1) for letter in ${word} ; do echo "${letter} is a letter"; done
Hier ist die Ausgabe:
Y ist ein Buchstabe o ist ein Buchstabe u ist ein Buchstabe r ist ein Buchstabe M ist ein Buchstabe e ist ein Buchstabe s ist ein Buchstabe s ist ein Buchstabe a ist ein Buchstabe g ist ein Buchstabe e ist ein Buchstabe
quelle
Ein anderer Ansatz, wenn Sie nicht möchten, dass Leerzeichen ignoriert werden:
for char in $(sed -E s/'(.)'/'\1 '/g <<<"$your_string"); do # Handle $char here done
quelle
Ein anderer Weg ist:
Characters="TESTING" index=1 while [ $index -le ${#Characters} ] do echo ${Characters} | cut -c${index}-${index} index=$(expr $index + 1) done
quelle
Ich teile meine Lösung:
read word for char in $(grep -o . <<<"$word") ; do echo $char done
quelle
*
. Sie erhalten Dateien im aktuellen Verzeichnis.TEXT="hello world" for i in {1..${#TEXT}}; do echo ${TEXT[i]} done
Wo
{1..N}
ist ein inklusive Bereich${#TEXT}
ist eine Anzahl von Buchstaben in einer Zeichenfolge${TEXT[i]}
- Sie können char aus einem String wie ein Element aus einem Array abrufenquelle