Publi

9 Trucos para manejar cadenas de caracteres en BASH y no morir en el intento

156091_471903058359_4935894_n

Aunque este tema lo he tratado alguna vez un poco más de pasada o con algún ejemplo concreto, vamos a ver ejemplos concretos de cosas que podemos hacer con una cadena de caracteres en Bash, para mejorar nuestros scripts, y sobre todo, para tener una pequeña documentación para mí, que siempre que necesito algo parecido me tiro un rato buscando…
El caso es que en versiones más o menos nuevas de BASH (más o menos desde hace 6 años o así), podemos usar muchos de estos trucos y no tenemos necesidad de utilizar programas externos (como tr, awk, sed y otros) en muchos casos, por lo que la ejecución se realizará mucho más rápida.

1 – Reemplazar subcadenas

Siempre podemos usar sed o awk, pero, si lo hacemos de forma nativa y nuestras necesidades no son muy grandes, podemos hacerlo desde bash, para ello podemos usar:

1
echo ${variable/subcadena/reemplazo}

bueno, mejor con un ejemplo

1
2
tigres="Un tigre, dos tigres, tres tigres"
echo "${tigres/tigre/gato}"

Aquí sustituiremos la palabra tigre por gato dentro de $tigres:

Un gato, dos tigres, tres tigres

pero claro, lo suyo es que se reemplacen todas, para ello, podemos usar:

1
2
tigres="Un tigre, dos tigres, tres tigres"
echo "${tigres//tigre/gato}"

que da como salida lo siguiente,

Un gato, dos gatos, tres gatos

No tenemos tanta potencia como con sed, por ejemplo, no podemos utilizar expresiones regulares, aunque sí que podemos usar * (lo que sea y los caracteres que sean) y ? (el carácter que sea) como comodines: «t*e» podrá ser tigre, torre, tarde, tirante, toreo… o «ti??e» podrá ser tigre, tilde, tinte, tintero…

También podemos utilizar corchetes [] para especificar rangos de caracteres como [a-z] de esta forma:

1
2
tigres="Un tigre, dos tigres, tres tigres"
echo "${tigres//[a-z]/*}"

** *****, *** ******, **** ******

o

1
2
tigres="Un tigre, dos tigres, tres tigres"
echo "${tigres//[aeiou]/*}"

Un t*gr*, d*s t*gr*s, tr*s t*gr*s

Tenemos dos modificadores más para reemplazar cadenas al principio y al final y sólo si encontramos la subcadena en esas posiciones. Por ejemplo:

1
2
bytes="123 bytes"
echo ${bytes/#[0-9]* /muchos }

muchos bytes

reemplazo que no se producirá si la cadena es «tengo 123 bytes por ejemplo», sólo funcionará si encuentra un número, para hacerlo mejor podemos utilizar, por ejemplo:

1
2
bytes="tengo 123 bytes"
echo ${bytes/#*([0-9])/muchos}

Para lo que he utilizado extglob, una forma de extender patrones que nos puede resultar muy útil, *([0-9]) concordará con ninguno o más números entre el 0 y el 9, podemos utilizarlos con letras, por ejemplo, o incluir algún símbolo si queremos que cumpla la condición. como caracteres comodín podemos utilizar ? (que coincidan cero o uno) , + (que coincidan uno o más entre otros (nos podemos extender en otro post con ejemplos de esto.

Por último, podemos hacer que se reemplace una coincidencia basándonos en la terminación de la cadena:

1
2
bytes="tengo 123 bytes"
echo ${bytes/%bytes/megas}

tengo 123 megas

2 – Extraer la ruta de un archivo

Tenemos el archivo, /usr/share/icons/hicolor/64×64/mimetypes/application-wireshark-doc.png, por coger uno. Y queremos extraer la ruta del archivo: /usr/share/icons/hicolor/64×64/mimetypes/, ¿para qué? tal vez sea un directorio donde tenemos más cosas que necesitemos, o queremos crear un archivo de log en el mismo directorio. Para sacarlo, debemos hacer:

1
2
myfile="/usr/share/icons/hicolor/64x64/mimetypes/application-wireshark-doc.png"
echo "${myfile%/*}"

lo cual nos devolverá:

/usr/share/icons/hicolor/64×64/mimetypes

3 – Extraer el archivo sin la ruta

Tenemos el mismo nombre de archivo /usr/share/icons/hicolor/64×64/mimetypes/application-wireshark-doc.png. Y desde nuestro script, necesitáis extraer el nombre de archivo, eliminando la ruta /usr/share/icons/hicolor/64×64/mimetypes/.
¿ Para qué puede servir ? Por ejemplo, si nuestro script se dedica a convertir archivos, o a copiar o mover archivos de un sitio a otro, copias de seguridad, enviarlos por la red… en esos casos puede interesarnos mantener el nombre original, pero la ruta no va a ser la misma.
Podemos hacerlo de la siguiente manera:

1
2
myfile="/usr/share/icons/hicolor/64x64/mimetypes/application-wireshark-doc.png"
echo "${myfile##*/}"

lo cual nos devolverá:

application-wireshark-doc.png

4 – ¡Ey! Un momento !! %% y ## son cosas especiales

Pues sí, son caracteres de control que podemos usar en bash para extraer partes de una cadena según un patrón, en realidad el funcionamiento interno lo veo poco intuitivo, y creo que se entiende mejor de esta forma), así:

${variable##patron}

Elimina todo lo que encuentra desde el principio de la cadena hasta la última aparición del patrón. Si el patrón es «*/» (o lo que sea, y luego una /), como es el caso de las rutas, eliminará todo hasta la última barra quedándonos con lo que hay detrás (el nombre de archivo).

¿Qué pasa si en nuestro patrón hay asteriscos? Por ejemplo si tenemos:

1
test="tengo*asteriscos*a*cascoporro"

Para extraer «cascoporro» deberíamos hacer:

1
echo "${test##*\*}"

En efecto, escapamos el asterisco final, que es el que queremos que no se interprete.

${variable#patron}

Es lo mismo que el apartado anterior, pero eliminará todo desde el principio de la cadena hasta la primera aparición del patrón, en el caso de las rutas, nos quitaría el texto desde el principio hasta la primera /, en el caso de los asteriscos, quitaría «tengo*»

${variable%patron}

Ahora vamos al revés, en lugar de empezar desde el principio, empezaremos desde el final, por lo tanto eliminaremos la otra parte de la cadena. En el caso de la extracción de la ruta, leyendo hacia atrás, eliminamos la cadena desde el final hasta la primera aparición del patrón (hacia atrás, si leemos desde la izquierda, será la última).

${variable%%patron}

Es lo mismo que el anterior, pero eliminaremos hasta la última aparición del patrón (desde el final) o la primera, si miramos desde la izquierda.

5 – Extraer el nombre de archivo sin la extensión

Ayudándonos de %. una vez eliminada la ruta del archivo, debemos eliminar la extensión, nos puede servir para cambiar la extensión de un archivo (si estamos convirtiendo) o generar un archivo .log derivado del mismo nombre, etc.

1
2
3
myfile="/usr/share/icons/hicolor/64x64/mimetypes/application-wireshark-doc.png"
filename="${myfile##*/}"
echo ${filename%.*}

Lo que nos devolverá

application-wireshark-doc

Pero seamos malos, si el archivo fuera www/projects/poesiabinaria/js/jquery.min.js

1
2
3
myfile="www/projects/poesiabinaria/js/jquery.min.js"
filename="${myfile##*/}"
echo ${filename%.*}

El resultado será

jquery.min

Si quisiéramos extraer .min.js, deberíamos poner echo ${filename%%.*}

6 – Sacar sólo la extensión del archivo

Si, en este caso, dependiendo de la extensión del archivo tenemos que variar el funcionamiento de nuestro script, podemos hacer lo siguiente:

1
2
myfile="www/projects/poesiabinaria/js/jquery.min.js"
echo ${filename##*.}

En este caso, no necesitamos extraer la ruta primero, porque eliminaremos el contenido desde delante. Obtendremos:

js

Si nos quisiéramos quedar con min.js, sí que es recomendable eliminar la ruta porque algún directorio puede tener puntos y nos devolvería algo extraño:

1
2
3
myfile="www/projects/poesiabinaria/js/jquery.min.js"
filename="${myfile##*/}"
echo ${filename#*.}

y obtendremos:

min.js

7 – Contar caracteres de una cadena

Basta de usar:

1
2
cadena="murciélago"
echo $cadena | wc -l

Además, porque puede dar un fallo con los caracteres especiales (UTF-8 frente a ISO8859-1 y esas cosas), lo que puede que cuente un número diferente de letras, al contar los bytes de la palabra. Por otro lado, es mucho más eficiente esto:

1
2
cadena="murciélago"
echo ${#cadena}

Sencillo, ¿no?

8 – Recortar cadenas

Un ejemplo vale más que 1000 explicaciones:

1
2
bienvenido="Bienvenido al Blog Poesía Binaria, Programación, Tecnología y Software libre"
echo ${bienvenido:18:15}

Éste empezará en la posición 18 y contará 15 caracteres.

Poesía Binaria

Ahora, podemos jugar con los valores:

1
2
bienvenido="Bienvenido al Blog Poesía Binaria, Programación, Tecnología y Software libre"
echo ${bienvenido:18}

Éste recortará desde la posición 18 hasta el final.

Poesía Binaria, Programación, Tecnología y Software libre

o

1
2
bienvenido="Bienvenido al Blog Poesía Binaria, Programación, Tecnología y Software libre"
echo ${bienvenido:18:-16}

Éste recortará empezando por el final.

Poesía Binaria, Programación, Tecnología

9 – Valor por defecto

Es una buena técnica inicializar todas las variables y que todas tengan un valor conocido. También es muy útil que si una variable no se ha definido con anterioridad, imaginad que se define gracias a un valor de usuario que no se ha especificado, adopte un valor por defecto. De esta forma, imaginad la variable host, si no se especifica:

1
echo ${host:=localhost}

Sólo escribiendo esto, la variable host se define y su valor será «localhost», pero, si por ejemplo, ya se encontraba definida:

1
2
host="totaki.com"
echo ${host:=localhost}

Aquí obtenemos directamente, «totaki.com» y la variable host no se ve modificada. Esto nos puede ahorrar unos cuantos if 🙂
Si, por ejemplo, no queremos asignar a la variable host el valor por defecto, podemos utilizar:

1
echo ${host:-localhost}

de este modo, visualizaremos el valor por defecto si la variable no está definida, pero no le asignaremos el nuevo valor a la variable.

¿ Tienes algún truco o ejemplo que suelas usar con cadenas en bash ?

También podría interesarte....

There are 33 comments left Ir a comentario

  1. Pingback: 9 Trucos para manejar cadenas de caracteres en BASH y no morir en el intento | PlanetaLibre /

  2. nerdvio /
    Usando Mozilla Firefox Mozilla Firefox 49.0 en Linux Linux

    Muchas gracias. Sos un capo! excelente trabajo.

    1. Gaspar Fernández / Post Author
      Usando Mozilla Firefox Mozilla Firefox 56.0 en Ubuntu Linux Ubuntu Linux

      Muchas gracias!

  3. Pingback: Leer ficheros de configuración INI desde nuestros scripts en BASH – Poesía Binaria /

  4. Post Author /
    Usando Mozilla Firefox Mozilla Firefox 52.0 en Linux Linux

    Maravilloso a la par que confusa tu explicacion.

    Gracias y Dios te lo pague con muchos hijos tuertos.

    1. Gaspar Fernández / Post Author
      Usando Mozilla Firefox Mozilla Firefox 57.0 en Linux Linux

      Bueno, por lo menos, si son muchos hijos, que sean pares y así pueden compartir ojos de dos en dos.

      Por cierto, si te pierdes o te lías algo con la explicación, siempre puedes dar tu correo de verdad (que no se publica), así recibes las respuestas a los comentarios. Podemos hablar de ello, sin miedo 🙂

  5. Fco /
    Usando Google Chrome Google Chrome 67.0.3396.87 en Linux Linux

    nunca había entrado en esta página para manejo de string con bash, me ha sorprendido gratamente. Y en español…, la tengo en mis favoritas por completa y concisa, y falta de memoria, obviamente … 🙂

    Saludos

    1. Gaspar Fernández / Post Author
      Usando Mozilla Firefox Mozilla Firefox 61.0 en Ubuntu Linux Ubuntu Linux

      Muchas gracias Francisco !!! Un abrazo!!!

  6. cesar /
    Usando Mozilla Firefox Mozilla Firefox 65.0 en Ubuntu Linux Ubuntu Linux

    Muy merecido nombre tiene esta página, Sr. Poeta Binario 🙂

  7. Santiago /
    Usando iOS iOS 12.1.4

    Gaspar, siempre un placer leerte, saludos.

  8. william /
    Usando Google Chrome Google Chrome 74.0.3729.169 en Windows Windows NT

    se puede hacer algo asi? ${$C_ARC_FIN/$C_FILENAME/$FILENAME}

  9. Edgar /
    Usando Google Chrome Google Chrome 74.0.3729.169 en Windows Windows NT

    Una pregunta como puedo guardar el numero de caracteres de una cadena en una variable en lugar de imprimirla.

    Por ejemplo:

    cadena=»murciélago»
    echo ${#cadena}

    este comando me imprime: 10
    He intentado guardandolo de la siguiente forma:

    ncaracter= ${#cadena}

    y despues imprimir

    echo «$ncaracter»

    pero me da un error.

    1. Edgar /
      Usando Google Chrome Google Chrome 74.0.3729.169 en Windows Windows NT

      Ya encontre la manera correcta :

      #!/bin/bash
      cadena=»murciélago»
      ncaracter=${#cadena}
      echo «$ncaracter»

      Saludos

    2. SilReon /
      Usando Mozilla Firefox Mozilla Firefox 90.0 en Ubuntu Linux Ubuntu Linux

      algo muy tarde pero bueno, creo q te tiraba el error debido a que agregaste un espacio a la hora de darle valor a la variable «ncaracter= ${#cadena}»
      A mi me funciono quitandole ese espacio ncaracter=${#cadena}

    3. Walter /
      Usando Mozilla Firefox Mozilla Firefox 122.0 en Ubuntu Linux Ubuntu Linux

      Podés utilizar lo que se conoce como «command substitution»:
      $(comando)
      Esto te permite ejecutar un comando y almacenar la salida del mismo en una variable:
      variable=$(comando)
      Si hubieras encerrado el «echo ${#cadena}» de tu primer intento de esta manera, habrías obtenido lo que buscabas:
      ncaracter=$(echo ${#cadena})

  10. Hevert /
    Usando Google Chrome Google Chrome 76.0.3809.100 en Windows Windows NT

    Excelente post, muy útil, estaba buscando una forma eficiente de manejar cadenas en bash y con esto fue mas que suficiente…
    Gaspar, De verdad, agradecido por la publicación…

  11. Carlos /
    Usando Google Chrome Google Chrome 79.0.3945.88 en Windows Windows NT

    Hola, consulta, si quiero reemplazar por ejemplo «,» solo cuando se encuentren juntos, que debo usar, porque actualmente lo hago asi linea2=${linea//»,»/|} y no funciona
    saludos

    1. Gaspar Fernández / Post Author
      Usando Google Chrome Google Chrome 79.0.3945.79 en Ubuntu Linux Ubuntu Linux

      Hola Carlos, cuéntame tu problema. Por ejemplo esto:

      1
      2
      linea="hola »,» mundo »,» mundial"
      echo ${linea//»,»/|}

      En Bash funciona. Si quieres, mira la versión (bash –version) a lo mejor es muy antigua, aunque es complicado que sea tan antigua. Pero intentaremos solucionarlo.

  12. Juan Nadie /
    Usando Mozilla Firefox Mozilla Firefox 72.0 en Ubuntu Linux Ubuntu Linux

    Hola, siempre se agradecen páginas en español para aprender.
    Estoy tratando de hacer un script muy sencillo, que me liste los directorios de una ubicación, con control del nivel de detalle, y me cuente los archivos que contiene cada uno.

    for i in $(find . -maxdepth 1 -type d)
    do
    echo $i » –>: »
    done

    y esto me funciona muy bien, salvo en el caso que el directorio que encuentra tenga espacios en medio del nombre, ya que trata cada palabra de forma independiente y no me da la información.
    Te agradecería alguna idea para lograrlo.
    Un saludo.

    1. Gaspar Fernández / Post Author
      Usando Google Chrome Google Chrome 79.0.3945.79 en Ubuntu Linux Ubuntu Linux

      Hola Juan,
      Perdona mi tardanza, creo que no se ha plasmado bien tu script en el comentario (me he inventado lo que falta, no sé si coincidirá con tu script). De todas formas, el problema está en la variable IFS (Internal Field Separator) que corta las palabras indistintamente cuando encuentra un espacio o una línea nueva. Tienes dos opciones, lo primero es cambiar el IFS, para eso te recomiendo hacer un backup de la variable y restaurarla después:

      1
      2
      3
      4
      OLDIFS=$IFS
      IFS=$'\n' # Aquí le decimos que cuente como separador solo las nuevas líneas
      for i in $(find . -maxdepth 1 -type d); do echo $i » –\>: » $(ls -l "$i" | grep -v -e ^l -e ^d | wc -l); done
      IFS=$OLDIFS # Volvemos a poner el IFS como estaba, para que no haya problema con más scripts

      Otra opción es utilizar un bucle while con read de esta forma:

      1
      find . -maxdepth 1 -type d | while read dir; do  echo $dir » –\>: » $(ls -l "$dir" | grep -v -e ^l -e ^d | wc -l); done

      Con read nos aseguramos de leer las líneas completas que nos va pasando find.

      Muchas gracias por tu comentario !!

  13. Cristopher /
    Usando Google Chrome Google Chrome 84.0.4147.135 en Windows Windows NT

    Hola,
    se ve que eres un capo de bash… y sabes que tengo un problema que no he podido solucionas.. te comento…
    Actualmente tengo un script que obtiene una ruta de un archivo especifico.
    Path = /user01/integration/service/Legacy Resource/archivo.biz

    esta ruta viene con un espacio (si lo se.. esta mal !) pero por el momento no se puede cambiar.
    esta ruta la cargo en un archivo tmp.
    echo $path > archivo.tmp
    y posteriormente en un ciclo trato de hacer un grep sobre esta ruta
    for valor in $(cat archivo.tmp)
    do
    grep -Poz ‘\s*\K[\s\S]*(?=)’ ${valor}
    done

    pero actualmente me arroja error por que la ruta con espacios en blanco la toma como 2 string diferentes.

    he intentado :
    – escapar con \
    – agregar comillas dobles a la variable «$valor»
    – tratar el string con sed y awk

    y no me funciona.
    cabe mencionar que si hago lo siguiente en la consola :

    grep -Poz ‘\s*\K[\s\S]*(?=)’ «/user01/integration/service/Legacy Resource/archivo.biz»

    si funciona.. entonces no se por donde está el problema.
    Desde el script me sigue arrojando error.
    por favor suplico tu ayuda para poder solucionar el problema 🙁

    Gracias de antemano.

  14. WilliamSEO /
    Usando Google Chrome Google Chrome 120.0.0.0 en Windows Windows NT

    I utilize just astounding materials – you can see them at: pasar123 gacor

  15. WilliamSEO /
    Usando Google Chrome Google Chrome 120.0.0.0 en Windows Windows NT

    A debt of gratitude is in order for composing such a decent article, I bumbled onto your blog and read a couple of post. I like your style of composing… 안전놀이터

  16. jsimitseo /
    Usando Google Chrome Google Chrome 120.0.0.0 en Windows Windows NT

    I’m hooked! The biggest web slots here are a true spectacle. สล็อตเว็บใหญ่ที่สุด

  17. WilliamSEO /
    Usando Google Chrome Google Chrome 120.0.0.0 en Windows Windows NT

    You bear through a wonderful opening. I rational soundness unquestionably quarry it besides by and by propose to my buddys. I am reserved they assurance be profited from this scene. Travel booking platform

  18. jsimitseo /
    Usando Google Chrome Google Chrome 120.0.0.0 en Windows Windows NT

    Slots that break easily can ruin the gaming experience. เว็บตรง แตกง่าย

  19. WilliamSEO /
    Usando Google Chrome Google Chrome 120.0.0.0 en Windows Windows NT

    Beaver says I additionally have such intrigue, you can read my profile here: 파워볼사이트

  20. jsimitseo /
    Usando Google Chrome Google Chrome 120.0.0.0 en Windows Windows NT

    wow this principled anyway ,I cherish your enter in addition to decent pics may be part personss contrary love being defrent mind add up to poeple , รวมสล็อตทุกค่าย

  21. jsimitseo /
    Usando Google Chrome Google Chrome 120.0.0.0 en Windows Windows NT

    I expounded on a comparable issue, I give you the connection to my site. 블로그

  22. jsimitseo /
    Usando Google Chrome Google Chrome 120.0.0.0 en Windows Windows NT

    Astounding, this is awesome as you need to take in more, I welcome to This is my page. ป๊อกเด้ง ออนไลน์

  23. jsimitseo /
    Usando Google Chrome Google Chrome 120.0.0.0 en Windows Windows NT

    llo there mates, it is incomprehensible shaped piece completely depicted, proceed with the impressive work constantly. เกมสล็อตโรม่า

  24. WilliamSEO /
    Usando Google Chrome Google Chrome 120.0.0.0 en Windows Windows NT

    I at last discovered grand post here.I will get back here. I just added your blog to my bookmark districts. thanks.Quality presents is the major on welcome the guests to visit the site page, that is the thing that this site page is giving. 토토사이트 가입코드

  25. jsimitseo /
    Usando Google Chrome Google Chrome 121.0.0.0 en Windows Windows NT

    I just idea it might be a plan to post incase any other person was having issues inquiring about yet I am somewhat uncertain in the event that I am permitted to put names and addresses on here. concierge doctor

Leave a Reply