Longitud de la cuerda en bash

Resuelto AJP asked hace 11 años • 11 respuestas

¿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?

AJP avatar Jun 28 '13 22:06 AJP
Aceptado

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
fedorqui avatar Jun 28 '2013 15:06 fedorqui

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 wcla 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_ALLjunto 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 %nformato de printfcomando (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 mostrarlo STDOUT.
  • _es una variable basura en este uso.
  • %nIndí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 strU8DiffLenes aproximadamente 2 veces más rápida printfque 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 printfherramienta 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 !

F. Hauri - Give Up GitHub avatar Jun 23 '2015 17:06 F. Hauri - Give Up GitHub

Quería el caso más simple, finalmente este es el resultado:

echo -n 'Tell me the length of this sentence.' | wc -m;
36
dmatej avatar Oct 11 '2017 08:10 dmatej

Puedes usar:

MYSTRING="abc123"
MYLENGTH=$(printf "%s" "$MYSTRING" | wc -c)
  • wc -co wc --bytespara recuentos de bytes = los caracteres Unicode se cuentan con 2, 3 o más bytes.
  • wc -mo wc --charspara recuentos de caracteres = los caracteres Unicode se cuentan individualmente hasta que utilizan más bytes.
atesin avatar May 09 '2015 03:05 atesin

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.

JGFMK avatar Jun 03 '2017 09:06 JGFMK