Tengo una variable como esta:
words="这是一条狗。"
Quiero hacer un bucle en cada uno de los personajes, uno a la vez, por ejemplo, en primer lugar character="这", a continuación character="是", character="一", etc.
La única forma que conozco es generar cada carácter en una línea separada en un archivo y luego usarlo while read line, pero esto parece muy ineficiente.
- ¿Cómo puedo procesar cada carácter de una cadena a través de un bucle for?

Respuestas:
Con
sedeldashcaparazón deLANG=en_US.UTF-8, Tengo las siguientes funcione bien:$ echo "你好嗎 新年好。全型句號" | sed -e 's/\(.\)/\1\n/g' 你 好 嗎 新 年 好 。 全 型 句 號y
$ echo "Hello world" | sed -e 's/\(.\)/\1\n/g' H e l l o w o r l dPor lo tanto, la salida se puede enlazar con
while read ... ; do ... ; doneeditado para texto de muestra traducir al inglés:
"你好嗎 新年好。全型句號" 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 descriptionfuente
Puede utilizar un
forbucle de estilo C :foo=string for (( i=0; i<${#foo}; i++ )); do echo "${foo:$i:1}" done${#foo}se expande a la longitud defoo.${foo:$i:1}se expande a la subcadena comenzando en la posición$ide longitud 1.fuente
bashrequiere.for (( _expr_ ; _expr_ ; _expr_ )) ; do _command_ ; doney no es lo mismo que $ (( expr )) ni (( expr )). En las tres construcciones de bash, expr se trata de la misma manera y $ (( expr )) también es POSIX.bashque se evalúan en un contexto aritmético.${#var}devuelve la longitud devar${var:pos:N}devuelve N caracteres enposadelanteEjemplos:
$ words="abc" $ echo ${words:0:1} a $ echo ${words:1:1} b $ echo ${words:2:1} cpor lo que es fácil de iterar.
de otra manera:
$ grep -o . <<< "abc" a b co
$ grep -o . <<< "abc" | while read letter; do echo "my letter is $letter" ; done my letter is a my letter is b my letter is cfuente
Me sorprende que nadie haya mencionado la
bashsolución obvia utilizando solowhileyread.while read -n1 character; do echo "$character" done < <(echo -n "$words")Tenga en cuenta el uso de
echo -npara evitar el salto de línea extraño al final.printfes otra buena opción y puede ser más adecuada para sus necesidades particulares. Si desea ignorar los espacios en blanco, reemplace"$words"con"${words// /}".Otra opción es
fold. Sin embargo, tenga en cuenta que nunca debe introducirse en un bucle for. Más bien, use un ciclo while de la siguiente manera:while read char; do echo "$char" done < <(fold -w1 <<<"$words")El beneficio principal de usar el
foldcomando externo (del paquete coreutils ) sería la brevedad. Puede alimentar su salida a otro comando comoxargs(parte del paquete findutils ) de la siguiente manera:fold -w1 <<<"$words" | xargs -I% -- echo %Querrá reemplazar el
echocomando utilizado en el ejemplo anterior con el comando que le gustaría ejecutar contra cada personaje. Tenga en cuenta quexargsdescartará los espacios en blanco de forma predeterminada. Puede utilizar-d '\n'para deshabilitar ese comportamiento.Internacionalización
Acabo de probar
foldcon algunos de los caracteres asiáticos y me di cuenta de que no es compatible con Unicode. Entonces, aunque está bien para las necesidades de ASCII, no funcionará para todos. En ese caso existen algunas alternativas.Probablemente lo reemplazaría
fold -w1con una matriz awk:awk 'BEGIN{FS=""} {for (i=1;i<=NF;i++) print $i}'O el
grepcomando mencionado en otra respuesta:Actuación
FYI, comparé las 3 opciones mencionadas anteriormente. Los dos primeros fueron rápidos, casi atados, con el bucle de plegado un poco más rápido que el bucle while. Como
xargsera de esperar, fue el más lento ... 75 veces más lento.Aquí está el código de prueba (abreviado):
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 100Aquí están los resultados:
fuente
characterestá vacío para espacios en blanco con lawhile readsolución simple , lo que puede ser problemático si se deben distinguir diferentes tipos de espacios en blanco.read -n1aread -N1para manejar los caracteres de espacio correctamente.Creo que todavía no existe una solución ideal que preserve correctamente todos los caracteres de espacio en blanco y sea lo suficientemente rápida, así que publicaré mi respuesta. El uso
${foo:$i:1}funciona, pero es muy lento, lo que se nota especialmente con cuerdas grandes, como mostraré a continuación.Mi idea es una expansión de un método propuesto por Six , que implica
read -n1, con algunos cambios para mantener todos los caracteres y funcionar correctamente para cualquier cadena:while IFS='' read -r -d '' -n 1 char; do # do something with $char done < <(printf %s "$string")Cómo funciona:
IFS=''- La redefinición del separador de campo interno a una cadena vacía evita la eliminación de espacios y pestañas. Hacerlo en la misma líneareadsignifica que no afectará a otros comandos de shell.-r- Significa "raw", lo que evita que sereadtrate\al final de la línea como un carácter de concatenación de línea especial.-d ''- Pasar una cadena vacía como delimitador evita que se eliminen losreadcaracteres de nueva línea. En realidad, significa que el byte nulo se utiliza como delimitador.-d ''es igual a-d $'\0'.-n 1- Significa que se leerá un carácter a la vez.printf %s "$string"- Usar enprintflugar deecho -nes más seguro, porqueechotrata-ny-ecomo opciones. Si pasa "-e" como una cadena,echono imprimirá nada.< <(...)- Pasar cadena al bucle mediante sustitución de procesos. Si usa here-strings en su lugar (done <<< "$string"), se agrega un carácter de nueva línea adicional al final. Además, pasar una cadena a través de una tubería (printf %s "$string" | while ...) haría que el bucle se ejecutara en una subcapa, lo que significa que todas las operaciones de variables son locales dentro del bucle.Ahora, probemos el rendimiento con una cuerda enorme. Usé el siguiente archivo como fuente:
https://www.kernel.org/doc/Documentation/kbuild/makefiles.txt
Se llamó al siguiente script a través del
timecomando:#!/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")Y el resultado es:
Como podemos ver, es bastante rápido.
A continuación, reemplacé el bucle con uno que usa la expansión de parámetros:
for (( i=0 ; i<${#string}; i++ )); do new_string+="${string:$i:1}" doneEl resultado muestra exactamente qué tan mala es la pérdida de rendimiento:
Los números exactos pueden variar en diferentes sistemas, pero la imagen general debería ser similar.
fuente
Solo probé esto con cadenas ascii, pero podrías hacer algo como:
while test -n "$words"; do c=${words:0:1} # Get the first character echo character is "'$c'" words=${words:1} # trim the first character donefuente
El bucle de estilo C en la respuesta de @ chepner está en la función de shell
update_terminal_cwd, y lagrep -o .solución es inteligente, pero me sorprendió no ver una solución usandoseq. Aquí está el mío:read word for i in $(seq 1 ${#word}); do echo "${word:i-1:1}" donefuente
También es posible dividir la cadena en una matriz de caracteres usando
foldy luego iterar sobre esta matriz:for char in `echo "这是一条狗。" | fold -w1`; do echo $char donefuente
#!/bin/bash word=$(echo 'Your Message' |fold -w 1) for letter in ${word} ; do echo "${letter} is a letter"; doneAquí está el resultado:
Y es una letra o es una letra u es una letra r es una letra M es una letra e es una letra s es una letra s es una letra a es una letra g es una letra e es una letra
fuente
Otro enfoque, si no le importa que se ignoren los espacios en blanco:
for char in $(sed -E s/'(.)'/'\1 '/g <<<"$your_string"); do # Handle $char here donefuente
Otra forma es:
Characters="TESTING" index=1 while [ $index -le ${#Characters} ] do echo ${Characters} | cut -c${index}-${index} index=$(expr $index + 1) donefuente
Comparto mi solución:
read word for char in $(grep -o . <<<"$word") ; do echo $char donefuente
*, obtendrá archivos en el directorio actual.TEXT="hello world" for i in {1..${#TEXT}}; do echo ${TEXT[i]} donedonde
{1..N}es un rango inclusivo${#TEXT}es un número de letras en una cadena${TEXT[i]}- puede obtener char de una cadena como un elemento de una matrizfuente