Cómo comprobar si una cadena contiene una subcadena en Bash

Resuelto davidsheldon asked hace 15 años • 30 respuestas

Tengo una cadena en Bash:

string="My string"

¿Cómo puedo probar si contiene otra cadena?

if [ $string ?? 'foo' ]; then
  echo "It's there!"
fi

¿ Dónde ??está mi operador desconocido? ¿Utilizo echoy grep?

if echo "$string" | grep 'foo'; then
  echo "It's there!"
fi

Eso parece un poco torpe.

davidsheldon avatar Oct 23 '08 19:10 davidsheldon
Aceptado

También puede usar la respuesta de Marcus (* comodines) fuera de una declaración de caso, si usa corchetes dobles:

string='My long string'
if [[ $string == *"My long"* ]]; then
  echo "It's there!"
fi

Tenga en cuenta que los espacios en la cadena de agujas deben colocarse entre comillas dobles y los *comodines deben estar afuera. También tenga en cuenta que se utiliza un operador de comparación simple (es decir ==), no el operador de expresiones regulares =~.

Adam Bellaire avatar Oct 23 '2008 12:10 Adam Bellaire

Si prefiere el enfoque de expresiones regulares:

string='My string';

if [[ $string =~ "My" ]]; then
   echo "It's there!"
fi
Matt Tardiff avatar Oct 23 '2008 20:10 Matt Tardiff

No estoy seguro de usar una declaración if, pero puedes obtener un efecto similar con una declaración case:

case "$string" in 
  *foo*)
    # Do stuff
    ;;
esac
Marcus Griep avatar Oct 23 '2008 12:10 Marcus Griep

¡¡Reescritura completa 2023-07-03!!

La cadena contiene: compatibilidad con POSIX, bashmayúsculas y minúsculas independientes, sugerencias y comentarios.

Introducción

La respuesta anterior se basó en la expansión de parámetros , pero después de hacer una comparación con la solución basada en casos , como lo propone la respuesta de Marcus Griep , debo confesar: ¡ el método de casos es mucho más eficiente!

Breve esencial

case $string in
    *$substring* )
         do something with "$substring"
         ;;
esac 

Como una función:

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

Muestra de uso

for string in 'echo "My string"'    "Don't miss quotes"    ''; do # 3 strings
    for substr in "'t mis"   'o "My'   "s"   "Y"   ""; do # 5 substrings
        if stringContain "$substr" "$string"; then
            printf 'Match: %-12s %s\n' "'$substr'" "'$string'"
        else
            printf 'No match: %s\n' "'$substr'"
        fi
    done
done
No match: ''t mis'
Match: 'o "My'      'echo "My string"'
Match: 's'          'echo "My string"'
No match: 'Y'
Match: ''           'echo "My string"'
Match: ''t mis'     'Don't miss quotes'
No match: 'o "My'
Match: 's'          'Don't miss quotes'
No match: 'Y'
Match: ''           'Don't miss quotes'
No match: ''t mis'
No match: 'o "My'
No match: 's'
No match: 'Y'
Match: ''           ''

Alternativa mediante expansión de parámetros

En la respuesta anterior propuse:

stringContain() { [ -z "$1" ] || { [ -z "${2##*$1*}" ] && [ -n "$2" ];};}

Pero después de hacer algunas comparaciones, usando dash, y busybox shell, aquí está mi resultado promedio:bashksh

Comparing time PExp vs Case method under bash    :    634.71%
Comparing time PExp vs Case method under dash    :    878.87%
Comparing time PExp vs Case method under ksh     :    217.95%
Comparing time PExp vs Case method under busybox :    752.42%

Script de prueba completo: stringContain-test.sh

caseEl método es al menos 2 veces más rápido que parameter expansionel método, independientemente de la implementación de Shell utilizada.

Semánticamente:

  • Método de caso: en caso de que la cadena coincida con algo (podría ser nada), seguida de una subcadena , seguida de cualquier cosa. es una sola prueba.
  • Expansión de parámetros : si la subcadena está vacía o una cadena donde algo seguido de una subcadena seguida de cualquier cosa se reemplaza por nada, no es nada y la cadena contiene algo, es una prueba múltiple compleja después de la transformación de la cadena.

Desde este punto de vista, parece fácil entender que el casemétodo es más eficiente.

Caso independiente

Bajointentoy algunos otroscaparazón, puede usar la expansión de parámetros para transformar rápidamente su cadena a minúsculas o mayúsculas , usando respectivamente: ${var,,}y ${var^^}:

Por lo tanto, agregar -iuna opción a la función, para casos independientes , se podría hacer mediante:

stringContain() {
    if [[ $1 == -i ]] ; then
        case ${3,,} in
            *${2,,}*) return 0;;
            *) return 1;;
        esac
    else
        case $2 in
            *$1*) return 0;;
            *) return 1;;
        esac
    fi
}
stringContain hello 'Hello world!' && echo yes || echo no
no
stringContain -i hello 'Hello world!' && echo yes || echo no
yes
F. Hauri - Give Up GitHub avatar Dec 08 '2013 23:12 F. Hauri - Give Up GitHub

Debe recordar que los scripts de shell son menos un lenguaje y más una colección de comandos. Instintivamente piensas que este "lenguaje" requiere que sigas un ifcon un [o un [[. Ambos son solo comandos que devuelven un estado de salida que indica éxito o fracaso (como cualquier otro comando). Por esa razón usaría grepy no el [comando.

Solo haz:

if grep -q foo <<<"$string"; then
    echo "It's there"
fi

Ahora que está pensando en ifprobar el estado de salida del comando que le sigue (completo con punto y coma), ¿por qué no reconsiderar el origen de la cadena que está probando?

## Instead of this
filetype="$(file -b "$1")"
if grep -q "tar archive" <<<"$filetype"; then
#...

## Simply do this
if file -b "$1" | grep -q "tar archive"; then
#...

La -qopción hace que grep no genere nada, ya que solo queremos el código de retorno. <<<hace que el shell expanda la siguiente palabra y la use como entrada para el comando, una versión de una línea del <<documento aquí (no estoy seguro de si esto es estándar o un bashismo).

Mark Baker avatar Oct 27 '2008 14:10 Mark Baker

La respuesta aceptada es la mejor, pero como hay más de una forma de hacerlo, aquí tienes otra solución:

if [ "$string" != "${string/foo/}" ]; then
    echo "It's there!"
fi

${var/search/replace}es $varcon la primera instancia de searchreemplazada por replace, si se encuentra (no cambia $var). Si intenta reemplazar foopor nada y la cadena ha cambiado, entonces obviamente foose encontró.

ephemient avatar Oct 23 '2008 14:10 ephemient