Cómo comprobar si una cadena contiene una subcadena en Bash
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 echo
y grep
?
if echo "$string" | grep 'foo'; then
echo "It's there!"
fi
Eso parece un poco torpe.
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 =~
.
Si prefiere el enfoque de expresiones regulares:
string='My string';
if [[ $string =~ "My" ]]; then
echo "It's there!"
fi
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
¡¡Reescritura completa 2023-07-03!!
La cadena contiene: compatibilidad con POSIX, bash
mayú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:bash
ksh
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
case
El método es al menos 2 veces más rápido queparameter expansion
el 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 case
mé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 -i
una 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
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 if
con 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 grep
y no el [
comando.
Solo haz:
if grep -q foo <<<"$string"; then
echo "It's there"
fi
Ahora que está pensando en if
probar 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 -q
opció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).
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 $var
con la primera instancia de search
reemplazada por replace
, si se encuentra (no cambia $var
). Si intenta reemplazar foo
por nada y la cadena ha cambiado, entonces obviamente foo
se encontró.