¿Bash admite expresiones regulares de límite de palabras?

Estoy tratando de hacer coincidir la presencia de una palabra en una lista antes de agregar esa palabra nuevamente (para evitar duplicados). Estoy usando Bash 4.2.24 y estoy intentando lo siguiente:

[[ $foo =~ \bmyword\b ]] 

además

 [[ $foo =~ \ ]] 

Sin embargo, ninguno parece funcionar. Se mencionan en el ejemplo de bash docs: http://tldp.org/LDP/Bash-Beginners-Guide/html/sect_04_01.html .

Supongo que estoy haciendo algo mal, pero no estoy seguro de qué.

Sí, todas las extensiones de expresiones regulares enumeradas son compatibles, pero tendrá más suerte poniendo el patrón en una variable antes de usarlo. Prueba esto:

 re=\\bmyword\\b [[ $foo =~ $re ]] 

Explorando alrededor encontré esta pregunta , cuyas respuestas parecen explicar por qué el comportamiento cambia cuando la expresión regular se escribe en línea como en su ejemplo. Probablemente tenga que volver a escribir sus pruebas para usar una variable temporal para sus expresiones regulares, o use el modo de compatibilidad 3.1:

 shopt -s compat31 

tl; dr

  • Para estar seguro, no use un literal de expresión regular con =~ .
    En cambio, usa:

    • cualquiera : una variable auxiliar – ver la respuesta de @Eduardo Ivancec .
    • o : una sustitución de comando que emite un literal de cadena – vea el comentario de @ ruakh en la respuesta de Eduardo Ivancec
  • Si \b y \< / \> funcionan depende de la plataforma de host , no de Bash :

    • HACEN trabajo en Linux ,
    • pero NO en plataformas basadas en BSD como macOS .

Si quieres saber más, sigue leyendo.


En bash 3.2+ (a menos que se shopt opción compat31 shopt ), el operando derecho del operador =~ debe ser sin comillas para ser reconocido como una expresión regular ( si cita el operando correcto, =~ realiza una comparación de cadena regular en su lugar).

Más exactamente, al menos los caracteres especiales y las secuencias de expresiones regulares no se deben citar, por lo que es correcto y útil citar esas subcadenas que deben tomarse literalmente ; por ejemplo, [[ ' ab' =~ ^' ab' ]] coincide, porque ^ no está citado y, por lo tanto, se reconoce correctamente como el ancla de inicio de cadena.

Sin embargo, parece haber un error en (al menos) bash 4.x donde ciertos literales de expresiones regulares no se analizan correctamente , es decir, aquellos que contienen construcciones \ -prefixed como \< y \s ( si crees que esto no es un error) , házmelo saber ); comportamiento a partir de bash 4.2.46 en Linux :

  # BUG [[ ' word ' =~ \ ]] && echo MATCHES # !! DOES NOT MATCH [[ ' word ' =~ \\ ]] && echo MATCHES # !! BREAKS [[ ' word ' =~ \\\ ]] && echo MATCHES # !! DOES NOT MATCH # WORKAROUNDS re='\'; [[ ' word ' =~ $re ]] && echo MATCHES # OK - intermediate variable [[ ' word ' =~ $(printf %s '\') ]] && echo MATCHES # OK - command subst. 

Soporte multiplataforma :

=~ es el caso raro (¿el único caso?) de una característica de bash incorporada que depende de la plataforma : utiliza las bibliotecas de expresiones regulares de la plataforma en la que se ejecuta, lo que da como resultado diferentes sabores de expresiones regulares en diferentes plataformas .

Por ejemplo, en FreeBSD / OSX \< / \> y \b NO son compatibles, pero son [[:<:]] y [[:>:]] . En Linux, es al revés.

Por lo tanto, no es trivial y requiere un cuidado especial para escribir código portátil que use el operador =~ .

La respuesta aceptada se centra en el uso de variables auxiliares para tratar las rarezas de syntax de las expresiones regulares en las expresiones de Bash [[ ... ]] . Muy buena información.

Sin embargo, la verdadera respuesta es:

\b \< y \> no funcionan en OS X 10.11.5 (El Capitan) con la versión de bash 4.3.42 (1) - liberación (x86_64-apple-darwin15.0.0).

En su lugar, use [[:<:]] y [[:>:]] .

No exactamente “\ b”, pero para mí es más legible (y portátil) que las otras sugerencias:

 [[ $foo =~ (^| )myword($| ) ]] 

Esto funcionó para mí

 bar='\' [[ $foo =~ $bar ]] 

Tangencial a su pregunta, pero si puede usar egrep en su script:

 if [ `echo $foo | egrep -c "\b${myword}\b"` -gt 0 ]; then 

Terminé usando esto después de agitar con bash’s =~

Como mklement0’s señala astutamente, solo podemos confiar en el estado de salida de egrep y escribir:

 if egrep -q "\b${myword}\b" <<<$foo; then 

Puedes usar grep, que es más portable que la expresión regular de bash así:

 if echo $foo | grep -q '\'; then echo "MATCH"; else echo "NO MATCH"; fi 

He utilizado lo siguiente para unir límites de palabras en sistemas más antiguos. La clave es envolver $foo con espacios ya que [^[:alpha:]] no coincidirá con las palabras al principio o al final de la lista.

 [[ " $foo " =~ [^[:alpha:]]myword[^[:alpha:]] ]] 

Modifique la clase de caracteres según sea necesario en función de los contenidos esperados de myword , de lo contrario, puede que esta no sea una buena solución.