Longitud de la cuerda en bash
¿Cómo se obtiene la longitud de una cadena almacenada en una variable y se asigna a otra variable?
myvar="some string"
echo ${#myvar}
# 11
¿Cómo se configura otra variable para la salida 11
?
Para obtener la longitud de una cadena almacenada en una variable, digamos:
myvar="some string"
size=${#myvar}
Para confirmar que se guardó correctamente echo
,:
$ echo "$size"
11
Longitud de cadena UTF-8
Mediante el usowc
usando wc
, podrías (de man bc
):
-c, --bytes print the byte counts -m, --chars print the character counts
Así que podrías bajoposix caparazón:
echo -n Généralité | wc -c
13
echo -n Généralité | wc -m
10
echo -n Généralité | wc -cm
10 13
for string in Généralités Language Théorème Février "Left: ←" "Yin Yang ☯";do
strlens=$(echo -n "$string"|wc -mc)
chrs=$((${strlens% *}))
byts=$((${strlens#*$chrs }))
printf " - %-*s is %2d chars length, but uses %2d bytes\n" \
$(( 14 + $byts - $chrs )) "$string" $chrs $byts
done
- Généralités is 11 chars length, but uses 14 bytes
- Language is 8 chars length, but uses 8 bytes
- Théorème is 8 chars length, but uses 10 bytes
- Février is 7 chars length, but uses 8 bytes
- Left: ← is 7 chars length, but uses 9 bytes
- Yin Yang ☯ is 10 chars length, but uses 12 bytes
Consulte más adelante, en Útil herramienta de corrección de printf , para obtener una explicación sobre esta sintaxis.
Bajointento, podrías dividir wc
la salida de directamente:
for string in Généralités Language Théorème Février "Left: ←" "Yin Yang ☯";do
read -r chrs byts < <(wc -mc <<<"$string")
printf " - %-$((14+$byts-chrs))s is %2d chars length, but uses %2d bytes\n" \
"$string" $((chrs-1)) $((byts-1))
done
Pero tener que bifurcar cadawc
cadena podría consumir muchos recursos del sistema. ¡Prefiero usar la forma bash pura ! ¡Eche un vistazo al final de esta respuesta para saber por qué!
Usando purointento
La primera idea que tuve fue cambiar el entorno local para obligar a bash a considerar cada carácter como bytes:
myvar='Généralités'
chrlen=${#myvar}
oLang=$LANG oLcAll=$LC_ALL
LANG=C LC_ALL=C
bytlen=${#myvar}
LANG=$oLang LC_ALL=$oLcAll
printf "%s is %d char len, but %d bytes len.\n" "${myvar}" $chrlen $bytlen
rendirá:
Généralités is 11 char len, but 14 bytes len.
Incluso podrías echar un vistazo a los caracteres almacenados:
myvar='Généralités'
chrlen=${#myvar}
oLang=$LANG oLcAll=$LC_ALL
LANG=C LC_ALL=C
bytlen=${#myvar}
printf -v myreal "%q" "$myvar"
LANG=$oLang LC_ALL=$oLcAll
printf "%s has %d chars, %d bytes: (%s).\n" "${myvar}" $chrlen $bytlen "$myreal"
Responderé:
Généralités has 11 chars, 14 bytes: ($'G\303\251n\303\251ralit\303\251s').
Nota: Según el comentario de Isabell Cowan , agregué una configuración $LC_ALL
junto con $LANG
.
Entonces la función podría ser:
strU8DiffLen() {
local chLen=${#1} LANG=C LC_ALL=C
return $((${#1}-chLen))
}
Pero sorprendentemente esta no es la forma más rápida :
Lo mismo, pero sin tener que jugar con locales
Recientemente aprendí el %n
formato de printf
comando (integrado):
myvar='Généralités'
chrlen=${#myvar}
printf -v _ %s%n "$myvar" bytlen
printf "%s is %d char len, but %d bytes len.\n" "${myvar}" $chrlen $bytlen
Généralités is 11 char len, but 14 bytes len.
printf -v _
Dígale a printf que almacene el resultado en una variable_
en lugar de mostrarloSTDOUT
._
es una variable basura en este uso.%n
Indíquele a printf que almacene el recuento de bytes de la cadena ya procesada en el nombre de la variable en el lugar correspondiente de los argumentos.
La sintaxis es un poco contraria a la intuición, ¡pero es muy eficiente! (La función adicional strU8DiffLen
es aproximadamente 2 veces más rápida printf
que la versión anterior local LANG=C
).
Longitud de un argumento, muestra de trabajo
El argumento funciona igual que las variables regulares.
showStrLen() {
local -i chrlen=${#1} bytlen
printf -v _ %s%n "$1" bytlen
LANG=$oLang LC_ALL=$oLcAll
printf "String '%s' is %d bytes, but %d chars len: %q.\n" "$1" $bytlen $chrlen "$1"
}
funcionará como
showStrLen théorème
String 'théorème' is 10 bytes, but 8 chars len: $'th\303\251or\303\250me'
Útil printf
herramienta de corrección:
Si usted:
for string in Généralités Language Théorème Février "Left: ←" "Yin Yang ☯";do
printf " - %-14s is %2d char length\n" "'$string'" ${#string}
done
- 'Généralités' is 11 char length
- 'Language' is 8 char length
- 'Théorème' is 8 char length
- 'Février' is 7 char length
- 'Left: ←' is 7 char length
- 'Yin Yang ☯' is 10 char length
¡ No es un resultado realmente bonito !
Para ello, aquí tienes una pequeña función:
strU8DiffLen() {
local -i bytlen
printf -v _ %s%n "$1" bytlen
return $(( bytlen - ${#1} ))
}
o escrito en una línea:
strU8DiffLen() { local -i _bl;printf -v _ %s%n "$1" _bl;return $((_bl-${#1}));}
Entonces ahora:
for string in Généralités Language Théorème Février "Left: ←" "Yin Yang ☯";do
strU8DiffLen "$string"
printf " - %-*s is %2d chars length, but uses %2d bytes\n" \
$((14+$?)) "'$string'" ${#string} $((${#string}+$?))
done
- 'Généralités' is 11 chars length, but uses 14 bytes
- 'Language' is 8 chars length, but uses 8 bytes
- 'Théorème' is 8 chars length, but uses 10 bytes
- 'Février' is 7 chars length, but uses 8 bytes
- 'Left: ←' is 7 chars length, but uses 9 bytes
- 'Yin Yang ☯' is 10 chars length, but uses 12 bytes
¡Desafortunadamente, esto no es perfecto!
Pero quedó algún comportamiento extraño en UTF-8, como caracteres a doble espacio, caracteres con espacio cero, desplazamiento inverso y otros que no podrían ser tan simples...
Eche un vistazo a diffU8test.sh o diffU8test.sh.txt para conocer más limitaciones.
Comparación: tenedor awc
vs purointento:
Haciendo un pequeño bucle de 1'000 consultas de longitud de cadena :
string="Généralité"
time for i in {1..1000};do strlens=$(echo -n "$string"|wc -mc);done;echo $strlens
real 0m2.637s
user 0m2.256s
sys 0m0.906s
10 13
string="Généralité"
time for i in {1..1000};do printf -v _ %s%n "$string" bytlen;chrlen=${#string};done;echo $chrlen $bytlen
real 0m0.005s
user 0m0.005s
sys 0m0.000s
10 13
Con suerte, el resultado ( 10 13
) es el mismo, pero el tiempo de ejecución difiere mucho, ¡algo así como 500 veces más rápido usando bash puro !
Quería el caso más simple, finalmente este es el resultado:
echo -n 'Tell me the length of this sentence.' | wc -m;
36
Puedes usar:
MYSTRING="abc123"
MYLENGTH=$(printf "%s" "$MYSTRING" | wc -c)
wc -c
owc --bytes
para recuentos de bytes = los caracteres Unicode se cuentan con 2, 3 o más bytes.wc -m
owc --chars
para recuentos de caracteres = los caracteres Unicode se cuentan individualmente hasta que utilizan más bytes.
En respuesta a la publicación que comienza:
Si desea utilizar esto con la línea de comando o argumentos de función...
con el código:
size=${#1}
Puede darse el caso en el que solo desee comprobar si hay un argumento de longitud cero y no necesita almacenar una variable. Creo que puedes usar este tipo de sintaxis:
if [ -z "$1" ]; then
#zero length argument
else
#non-zero length
fi
Consulte GNU y Wooledge para obtener una lista más completa de expresiones condicionales de Bash.