¿Cómo saber si una cadena contiene otra cadena en POSIX sh?

136

Quiero escribir un script de shell de Unix que haga varias lógicas si hay una cadena dentro de otra cadena. Por ejemplo, si estoy en una carpeta determinada, bifurca. ¿Podría alguien decirme cómo lograr esto? Si es posible, me gustaría hacer que esto no sea específico de shell (es decir, no solo bash), pero si no hay otra forma de hacerlo.

#!/usr/bin/env sh

if [ "$PWD" contains "String1" ]
then
    echo "String1 present"
elif [ "$PWD" contains "String2" ]
then
    echo "String2 present"
else
    echo "Else"
fi
Mate
fuente
2
Me doy cuenta de que esto es antiguo, pero aquí hay algunas cosas a tener en cuenta para futuros visitantes: (1) Por lo general, es una buena práctica reservar los nombres de las variables SNAKE_CASE para las variables internas del entorno y del shell. (2) La configuración CURRENT_DIRes redundante; solo puedes usar $PWD.
nyuszika7h

Respuestas:

162

Aquí hay otra solución. Utiliza la expansión de parámetros de la subcadena POSIX , por lo que funciona en Bash, Dash, KornShell (ksh), Z shell (zsh), etc.

test "${string#*$word}" != "$string" && echo "$word found in $string"

Una versión funcionalizada con algunos ejemplos:

# contains(string, substring)
#
# Returns 0 if the specified string contains the specified substring,
# otherwise returns 1.
contains() {
    string="$1"
    substring="$2"
    if test "${string#*$substring}" != "$string"
    then
        return 0    # $substring is in $string
    else
        return 1    # $substring is not in $string
    fi
}

contains "abcd" "e" || echo "abcd does not contain e"
contains "abcd" "ab" && echo "abcd contains ab"
contains "abcd" "bc" && echo "abcd contains bc"
contains "abcd" "cd" && echo "abcd contains cd"
contains "abcd" "abcd" && echo "abcd contains abcd"
contains "" "" && echo "empty string contains empty string"
contains "a" "" && echo "a contains empty string"
contains "" "a" || echo "empty string does not contain a"
contains "abcd efgh" "cd ef" && echo "abcd efgh contains cd ef"
contains "abcd efgh" " " && echo "abcd efgh contains a space"
fjarlq
fuente
3
Esto no funciona para mí si la subcadena contiene barras invertidas. Como de costumbre, substring="$( printf '%q' "$2" )"salva el día.
Egor Tensin
esto también coincide con la subcadena incorrecta. string = "aplha beta betaone" substring = "beta". coincide con betaone y dbeta, lo que creo que está mal.
rajeev
1
¿Qué pasa con el uso de comodines ? [[ $haystack == *"My needle"* ]]
Pablo A
44
Lo que está mal es que los corchetes dobles no son POSIX, que era la premisa de la pregunta, @Pablo.
Rob Kennedy el
1
Esto no funciona con caracteres especiales como []. Vea mi respuesta stackoverflow.com/a/54490453/712666 .
Alex Skrypnyk
85

Shell POSIX puro:

#!/bin/sh
CURRENT_DIR=`pwd`

case "$CURRENT_DIR" in
  *String1*) echo "String1 present" ;;
  *String2*) echo "String2 present" ;;
  *)         echo "else" ;;
esac

Los shells extendidos como ksh o bash tienen mecanismos de combinación sofisticados, pero el estilo antiguo casees sorprendentemente poderoso.

Norman Ramsey
fuente
40

Lamentablemente, no conozco una manera de hacer esto en sh. Sin embargo, usando bash (a partir de la versión 3.0.0, que es probablemente lo que tiene), puede usar el operador = ~ de esta manera:

#!/bin/bash
CURRENT_DIR=`pwd`

if [[ "$CURRENT_DIR" =~ "String1" ]]
then
 echo "String1 present"
elif [[ "$CURRENT_DIR" =~ "String2" ]]
then
 echo "String2 present"
else
 echo "Else"
fi

Como un bono adicional (y / o una advertencia, si sus cadenas tienen caracteres divertidos), = ~ acepta expresiones regulares como el operando correcto si omite las comillas.

John Hyland
fuente
3
No cites la expresión regular, o no funcionará en general. Por ejemplo, trate de [[ test =~ "test.*" ]]frente [[ test =~ test.* ]].
l0b0
1
Bueno, funcionará bien si está probando una subcadena, como en la pregunta original, pero no tratará el operando correcto como una expresión regular. Actualizaré mi respuesta para que quede más claro.
John Hyland
3
Esto es Bash, no POSIX sh como se hace la pregunta.
Reid
28
#!/usr/bin/env sh

# Searches a subset string in a string:
# 1st arg:reference string
# 2nd arg:subset string to be matched

if echo "$1" | grep -q "$2"
then
    echo "$2 is in $1"
else 
    echo "$2 is not in $1"
fi
a.saurabh
fuente
2
Cambie grep -q "$2"a grep -q "$2" > /dev/nullpara evitar salidas no deseadas.
Victor Sergienko
15

Aquí hay un enlace a varias soluciones de su problema.

Este es mi favorito, ya que tiene el sentido más legible para los humanos:

El método comodín estrella

if [[ "$string" == *"$substring"* ]]; then
    return 1
fi
return 0
ma77c
fuente
El shrecibí "operando desconocido" con esto. Sin embargo, funciona con Bash.
halfer
13
[[no es POSIX
Ian
14
case $(pwd) in
  *path) echo "ends with path";;
  path*) echo "starts with path";;
  *path*) echo "contains path";;
  *) echo "this is the default";;
esac
método de ayuda
fuente
2
test $(echo "stringcontain" "ingcon" |awk '{ print index($1, $2) }') -gt 0 && echo "String 1 contain string 2"

-> salida: la cadena 1 contiene la cadena 2


fuente
2

Consulte la página de manual del programa 'prueba'. Si solo está probando la existencia de un directorio, normalmente haría algo así:

if test -d "String1"; then
  echo "String1 present"
end

Si realmente está tratando de hacer coincidir una cadena, también puede usar reglas de expansión bash y comodines:

if test -d "String*"; then
  echo "A directory starting with 'String' is present"
end

Si necesita hacer algo más complejo, deberá usar otro programa como expr.

jdeseno
fuente
1
Parece haber una doble cita falsa (") en su segundo ejemplo.
Alexis Wilke
2

En casos especiales en los que desea saber si una palabra está contenida en un texto largo, puede recorrer el texto largo con un bucle.

found=F
query_word=this
long_string="many many words in this text"
for w in $long_string; do
    if [ "$w" = "$query_word" ]; then
          found=T
          break
    fi
done

Esto es pura cáscara de Bourne.

Kemin Zhou
fuente
1

Si desea un método solo para ksh que sea tan rápido como "prueba", puede hacer algo como:

contains() # haystack needle
{
    haystack=${1/$2/}
    if [ ${#haystack} -ne ${#1} ] ; then
        return 1
    fi
    return 0
}

Funciona eliminando la aguja en el pajar y luego comparando la longitud de la cuerda de los pajar viejos y nuevos.

JoeOfTex
fuente
1
¿No sería posible devolver el resultado de test? Al igual que en return [ ${#haystack} -eq ${#1} ]?
Alexis Wilke
Si, eso es correcto. Lo dejaré así, ya que es más fácil de entender para los laicos. Si usa su código, use el método @AlexisWilke.
JoeOfTex