¿Cómo determinar si una cadena es una subcadena de otra en bash?

49

Quiero ver si una cadena está dentro de una porción de otra cadena.
p.ej:

'ab' in 'abc' -> true
'ab' in 'bcd' -> false

¿Cómo puedo hacer esto en un condicional de un script bash?

Lucio
fuente

Respuestas:

27

Puede usar el formulario ${VAR/subs}donde VARcontiene la cadena más grande y subses la subcadena que está tratando de encontrar:

my_string=abc
substring=ab
if [ "${my_string/$substring}" = "$my_string" ] ; then
  echo "${substring} is not in ${my_string}"
else
  echo "${substring} was found in ${my_string}"
fi

Esto funciona porque ${VAR/subs}es igual $VARpero con la primera aparición de la cadena subseliminada, en particular si $VARno contiene la palabra subs, no se modificará.

edwin
fuente
Creo que deberías cambiar la secuencia de las echodeclaraciones. Porque consigoab is not in abc
Lucio
¡Tienes razón! : P
edwin
Mmm .. No, el guión está mal. Me gusta eso ab was found in abc, pero si lo uso substring=zme salez was found in abc
Lucio
1
Ahora lo entiendo ab is not in abc. Pero z was found in abc. Esto es divertido: D
Lucio
1
Duh! ¡Los ecos estaban justo al comienzo de esto! XD
edwin
47

[[ "bcd" =~ "ab" ]]
[[ "abc" =~ "ab" ]]

los corchetes son para la prueba, y como se trata de corchetes dobles, puede hacer algunas pruebas adicionales =~.

Entonces podrías usar este formulario algo así como

var1="ab"
var2="bcd"
if [[ "$var2" =~ "$var1" ]]; then
    echo "pass"
else
    echo "fail"
fi

Editar: corregido "= ~", había cambiado.

recatado
fuente
1
Me sale failcon estos parámetros:var2="abcd"
Lucio
3
@Lucio Lo correcto es [[ $string =~ $substring ]]. Actualicé la respuesta.
Eric Carvalho
12

Usar patrones de nombre de archivo bash (también conocidos como patrones "glob")

substr=ab
[[ abc == *"$substr"* ]] && echo yes || echo no    # yes
[[ bcd == *"$substr"* ]] && echo yes || echo no    # no
Glenn Jackman
fuente
if [["" JAVA_OPTS "! = " -XX: + UseCompressedOops " ]]; luego exporte JAVA_OPTS = "$ JAVA_OPTS -XX: + UseCompressedOops"; fi
Mike Slinn
10

Los siguientes dos enfoques funcionarán en cualquier entorno compatible con POSIX, no solo en bash:

substr=ab
for s in abc bcd; do
    if case ${s} in *"${substr}"*) true;; *) false;; esac; then
        printf %s\\n "'${s}' contains '${substr}'"
    else
        printf %s\\n "'${s}' does not contain '${substr}'"
    fi
done
substr=ab
for s in abc bcd; do
    if printf %s\\n "${s}" | grep -qF "${substr}"; then
        printf %s\\n "'${s}' contains '${substr}'"
    else
        printf %s\\n "'${s}' does not contain '${substr}'"
    fi
done

Ambos de los resultados anteriores:

'abc' contains 'ab'
'bcd' does not contain 'ab'

El primero tiene la ventaja de no generar un grepproceso separado .

Tenga en cuenta que lo uso en printf %s\\n "${foo}"lugar de echo "${foo}"porque echopodría estropearse ${foo}si contiene barras invertidas.

Richard Hansen
fuente
La primera versión funciona perfectamente para encontrar la subcadena del nombre del monitor en la lista de xrandrnombres de monitor almacenados en la variable. +1 y bienvenidos al 1K rep club :)
WinEunuuchs2Unix
6

declaración de caso de shell

Esta es la solución más portátil, funcionará incluso en los viejos shells Bourne y Korn

#!/bin/bash
case "abcd" in
    *$1*) echo "It's a substring" ;;
    *) echo "Not a substring" ;;
esac

Ejecución de muestra:

$ ./case_substr.sh "ab"                                                                                           
It's a substring
$ ./case_substr.sh "whatever"                                                                                     
Not a substring

Tenga en cuenta que no tiene que usar específicamente echo, puede usarlo exit 1y exit 0significar éxito o fracaso.

Lo que podríamos hacer también es crear una función (que puede usarse en scripts grandes si es necesario) con valores de retorno específicos (0 en coincidencia, 1 en ausencia de coincidencia):

$ ./substring_function.sh                                  
ab is substring

$ cat substring_function.sh                                
#!/bin/sh

is_substring(){
    case "$2" in
        *$1*) return 0;;
        *) return 1;;
    esac
}

main(){
   if is_substring "ab" "abcdefg"
   then
       echo "ab is substring"
   fi
}

main $@

grep

$ grep -q 'ab' <<< "abcd" && echo "it's a substring" || echo "not a substring"                                    
it's a substring

Este enfoque particular es útil con las declaraciones if-else en bash. También en su mayoría portátil

AWK

$ awk '$0~/ab/{print "it is a substring"}' <<< "abcd"                                                             
it is a substring

Pitón

$ python -c 'import sys;sys.stdout.write("it is a substring") if "ab" in sys.stdin.read() else exit(1)' <<< "abcd"
it is a substring

Rubí

$ ruby -e ' puts "is substring" if  ARGV[1].include? ARGV[0]'  "ab" "abcdef"                                             
is substring
Sergiy Kolodyazhnyy
fuente
+1 por ir más allá de los demás. Noté que aquí y en otros sitios de intercambio de pila, ninguna respuesta devuelve el desplazamiento de la subcadena dentro de la cadena. Cuál es la misión de esta noche :)
WinEunuuchs2Unix
@ WinEunuuchs2Unix ¿Vas a hacer eso en bash?
Sergiy Kolodyazhnyy
Sí y no. Estoy haciendo un proyecto de Frankenstein donde Python obtiene todos los metadatos de mensajes de gmail.com y bash lo analiza y presenta una lista de GUI con desglose. Aunque encontré la respuesta aquí: stackoverflow.com/questions/5031764/…
WinEunuuchs2Unix
@ WinEunuuchs2Unix OK. Suena interesante. Personalmente prefiero analizar todo en Python. Tiene muchas más capacidades para el procesamiento de texto que bash solo.
Sergiy Kolodyazhnyy
Conozco tus preferencias durante unos dos años y las respeto. Pero solo estoy aprendiendo Python y hacer que Yad trabaje dentro de mí me parece engorroso. Sin mencionar todo el procesamiento de matriz con el que ya me siento cómodo en bash. Pero al menos escribí mi primer script de Python para absorber todo de gmail.com de Google en un archivo plano de Linux, ¿verdad? :)
WinEunuuchs2Unix
5

Cuidado con [[y ":

[[ $a == z* ]]   # True if $a starts with an "z" (pattern matching).
[[ $a == "z*" ]] # True if $a is equal to z* (literal matching).

[ $a == z* ]     # File globbing and word splitting take place.
[ "$a" == "z*" ] # True if $a is equal to z* (literal matching).

Como dijo @glenn_jackman, pero tenga en cuenta que si ajusta todo el segundo término entre comillas dobles, cambiará la prueba a coincidencia literal .

Fuente: http://tldp.org/LDP/abs/html/comparison-ops.html

Campa
fuente
4

Similar a la respuesta de edwin, pero con portabilidad mejorada para posix y ksh, y un toque menos ruidoso que el de Richard:

substring=ab

string=abc
if [ "$string" != "${string%$substring*}" ]; then
    echo "$substring IS in $string"
else
    echo "$substring is NOT in $string"
fi

string=bcd
if [ "$string" != "${string%$substring*}" ]; then
    echo "$string contains $substring"
else
    echo "$string does NOT contain $substring"
fi

Salida:

abc contains ab
bcd does NOT contain ab
laubster
fuente