Una guía para usar la coincidencia de patrones de Bash en lugar de grep y sed

Probablemente ya hayas escrito docenas de scripts Bash. Es posible que haya utilizado grep en una declaración condicional o sed para transformar pequeños bloques de texto en línea. ¿Qué pasaría si te dijera que eso es increíblemente ineficiente? Bash proporciona utilidades integradas para manejar estos casos, pero se utilizan con poca frecuencia, así que solucionemos eso.

Lo he hecho yo mismo (durante años), utilizando herramientas externas donde Bash hace mejor el trabajo. Eso no es una exageración; hay mejoras reales en el rendimiento y, a menudo, son una buena práctica. La combinación de patrones de Bash es simple y más que adecuada la mayoría de las veces. Explicaré por qué grep y sed son lentos, cuáles son las alternativas y cómo se usan.

Tux, la mascota de Linux, con gafas de sol y mirando desde detrás de una gran ventana de terminal que muestra comandos globales.

8 trucos del shell de Linux que cambian por completo el funcionamiento de los comandos

El shell hace mucho más que ejecutar comandos. Así es como Bash amplía su entrada detrás de escena para que pueda escribir comandos más limpios y confiables.

¿Por qué no utilizar grep y sed?

Tanto grep como sed son excelentes herramientas que se han mantenido sólidas durante décadas, pero cuando se usan mal, causan problemas.

Para ilustrar el problema central, comparemos 10.000 llamadas grep sucesivas:

time for ((i=0; i<10000; i++)); do
  echo 'Hello, World!' | grep 'Hello' >/dev/null
done

Y para 10.000 llamadas sed:

time for ((i = 0; i < 10000; i++)); do
  echo 'Hello, World!' | sed 's/Hello/Goodbye/' >/dev/null
done

Compárelos con sus equivalentes de Bash que usan coincidencia de patrones (que se explica a continuación). El siguiente es un sustituto puro de Bash grep:

time for ((i = 0; i < 10000; i++)); do
  [[ 'Hello, World!' == *Hello* ]] && true
done

Y un sustituto sed puro de Bash:

str='Hello, World!'
time for ((i = 0; i < 10000; i++)); do
  result=${str/Hello/Goodbye}
done

Grep y sed son más lentos porque cada llamada a un binario externo requiere:

  1. Una bifurcación del proceso Bash actual, duplicándolo

  2. Un reemplazo completo del proceso hijo (usando execve) con el ejecutable deseado

Ese proceso requiere muchos recursos.

Los ejemplos proporcionados no son representativos de todas las cargas de trabajo. A menudo, grep y sed analizan archivos completos de forma masiva, algo en lo que destacan. Sin embargo, a medida que los trabajos se vuelven más pequeños y las llamadas más frecuentes, su eficiencia disminuye.

¿Qué es la coincidencia de patrones de Bash?

La coincidencia de patrones, como sugiere el nombre, compara cadenas con las representaciones deseadas. Es una característica nativa de Bash, lo que significa que Bash no utiliza bifurcaciones costosas para realizarla.

Un patrón básico se parece a:

Hello*

Esto significa: haga coincidir cualquier cosa que comience con «Hola». Probablemente hayas usado un asterisco como este antes, pero hay más por explorar.

La coincidencia de patrones se usa comúnmente en declaraciones de cambio:

value="Hello, World!"
case "$value" in
    Hello*) echo "matched" ;;
    *) echo "I match anything not matched";;
esac

Los patrones también se pueden utilizar en declaraciones condicionales:

[[ "Hello, World!" == Hello* ]] && echo "matched"

Puede reemplazar grep en casos simples utilizando ambos enfoques.

¿Notas que «Hola*» no es una cadena? Si lo pone entre comillas, Bash intenta hacerlo coincidir literalmente, lo cual no queremos. Además, para declaraciones condicionales, debe utilizar corchetes dobles ([[...]]) al combinar patrones.

Y para reemplazar texto (como lo hace sed), podemos usar la expansión de parámetros:

str='Hello, World!'
echo "${str/Hello/Goodbye}"

La expansión de parámetros simplemente significa convertir una variable en un valor. Por ejemplo, $foo se expande hasta su valor asignado. Otro ejemplo: si var="Foo"entonces "${var/Foo/Bar}" se expande a «Bar». Es un proceso realizado por Bash antes de ejecutar el script.

Una introducción a los conceptos básicos de combinación de patrones

La forma más básica de coincidencia de patrones utiliza comodines y probablemente los haya usado antes:

ls foo*
  • *: Coincide con cualquier cadena, por ejemplo, Hello* coincide con «¡Hola, mundo!»
  • ?: Coincide con cualquier carácter individual, por ejemplo, H?llo coincide con Hola, Hola, etc.

Esto cubre los comodines POSIX estándar, pero Bash ofrece tres extensiones adicionales. Las expresiones entre corchetes le permiten especificar caracteres individuales para hacer coincidir:

[a-z]

Eso coincidirá con cualquier carácter de la «a» a la «z». Algunos ejemplos más:

  • [A-Z]: Coincide con cualquier carácter en mayúscula.
  • [a-zA-Z]: Coincide con cualquier carácter en minúscula o mayúscula.
  • [0-9]: Coincide con cualquier dígito.
  • ["£$%^&*()]: Coincide con cualquiera de estos caracteres especiales una vez.
  • [^a-z]: niega una coincidencia: coincide con cualquier carácter que no sea una letra minúscula de la «a» a la «z».

Puedes crear tu propio patrón y también mezclarlo con comodines: [a-z][-_0-9]*.

A continuación, una clase de caracteres nos permite hacer coincidir texto independiente de la configuración regional, lo que esencialmente significa que funcionará con caracteres que no sean ASCII.

[:alnum:]

Esto coincidirá con cualquier carácter alfanumérico, por ejemplo. Hay 12 clases de caracteres POSIX estándar.

Para utilizar una clase de carácter con coincidencia de patrones, debe incrustarla dentro de una expresión entre corchetes: []—es decir, envuelva la clase de carácter con otro par de corchetes: [[:alnum:]]. Puedes expandir aún más la expresión entre corchetes usando lo que has aprendido:

[a-z[:digit:]]

Coincidencia de patrones en acción

Ya he cubierto la coincidencia de patrones en declaraciones condicionales, declaraciones de casos y expansiones de parámetros, así que profundizaré en un ejemplo más complejo.

[[ 'Hello, World!' == [Hh]ello?[[:space:]]W* ]] && echo 'matched'

La cadena coincidente debe comenzar con «Hola» (en mayúscula o no) y debe ir seguida de…

  1. Cualquier carácter (?).

  2. Un espacio ([[:space:]]).

  3. Una «W» literal.

  4. Cualquier cadena (*), sin incluir nada.

Algunos ejemplos de coincidencias son:

  • Hola; WIP

  • Hola_ Woops

  • Hola y W

Lo único que no incluí en ese ejemplo es [a-z]pero estoy seguro de que puedes imaginar dónde podría encajar. Pruébalo y verás.

3 técnicas de scripting Bash que todo usuario de Linux debería conocer

Desbloquea el poder de Bash con estas sencillas técnicas.


Para un uso único, usar grep y sed en línea está bien pero es ineficiente, porque su sistema debe bifurcar y reemplazar el proceso bifurcado, lo que requiere un trabajo considerable. El programa necesita inicializar y destruir todas sus estructuras de datos, lo cual es un gran esfuerzo desperdiciado. Por el contrario, una característica nativa de Bash es simplemente una construcción dentro de un programa que ya se está ejecutando. Una analogía adecuada es alquilar un apartamento cada vez que necesitas ducharte. Incluso si es asequible, todavía tiene poco sentido. Estas herramientas sólo tienen sentido cuando se procesan grandes volúmenes de texto.

Otro error común es usar estos programas por su potente motor de expresiones regulares, porque Bash ya lo admite con el =~ operador.

Para terminar, la sintaxis de coincidencia de patrones es más limpia y más idiomática (convencional y una mejor práctica). A menos que esté procesando grandes volúmenes de datos, prefiera las funciones nativas de Bash.

3 buenas razones para reemplazar Bash con Zsh

Zsh es más avanzado que Bash y tiene mucho más potencial para aumentar el rendimiento de su terminal.

We use cookies in order to give you the best possible experience on our website. By continuing to use this site, you agree to our use of cookies.
Accept