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
sed
eldash
caparazó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 d
Por lo tanto, la salida se puede enlazar con
while read ... ; do ... ; done
editado 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 description
fuente
Puede utilizar un
for
bucle 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$i
de longitud 1.fuente
bash
requiere.for (( _expr_ ; _expr_ ; _expr_ )) ; do _command_ ; done
y 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.bash
que se evalúan en un contexto aritmético.${#var}
devuelve la longitud devar
${var:pos:N}
devuelve N caracteres enpos
adelanteEjemplos:
$ words="abc" $ echo ${words:0:1} a $ echo ${words:1:1} b $ echo ${words:2:1} c
por lo que es fácil de iterar.
de otra manera:
$ grep -o . <<< "abc" a b c
o
$ 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
fuente
Me sorprende que nadie haya mencionado la
bash
solución obvia utilizando solowhile
yread
.while read -n1 character; do echo "$character" done < <(echo -n "$words")
Tenga en cuenta el uso de
echo -n
para evitar el salto de línea extraño al final.printf
es 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
fold
comando 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
echo
comando utilizado en el ejemplo anterior con el comando que le gustaría ejecutar contra cada personaje. Tenga en cuenta quexargs
descartará los espacios en blanco de forma predeterminada. Puede utilizar-d '\n'
para deshabilitar ese comportamiento.Internacionalización
Acabo de probar
fold
con 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 -w1
con una matriz awk:awk 'BEGIN{FS=""} {for (i=1;i<=NF;i++) print $i}'
O el
grep
comando 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
xargs
era 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 100
Aquí están los resultados:
fuente
character
está vacío para espacios en blanco con lawhile read
solución simple , lo que puede ser problemático si se deben distinguir diferentes tipos de espacios en blanco.read -n1
aread -N1
para 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ínearead
significa que no afectará a otros comandos de shell.-r
- Significa "raw", lo que evita que seread
trate\
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 losread
caracteres 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 enprintf
lugar deecho -n
es más seguro, porqueecho
trata-n
y-e
como opciones. Si pasa "-e" como una cadena,echo
no 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
time
comando:#!/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}" done
El 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 done
fuente
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}" done
fuente
También es posible dividir la cadena en una matriz de caracteres usando
fold
y luego iterar sobre esta matriz:for char in `echo "这是一条狗。" | fold -w1`; do echo $char done
fuente
#!/bin/bash word=$(echo 'Your Message' |fold -w 1) for letter in ${word} ; do echo "${letter} is a letter"; done
Aquí 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 done
fuente
Otra forma es:
Characters="TESTING" index=1 while [ $index -le ${#Characters} ] do echo ${Characters} | cut -c${index}-${index} index=$(expr $index + 1) done
fuente
Comparto mi solución:
read word for char in $(grep -o . <<<"$word") ; do echo $char done
fuente
*
, obtendrá archivos en el directorio actual.TEXT="hello world" for i in {1..${#TEXT}}; do echo ${TEXT[i]} done
donde
{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