Comment savoir si une chaîne contient une autre chaîne dans POSIX sh?

136

Je veux écrire un script shell Unix qui fera diverses logiques s'il y a une chaîne à l'intérieur d'une autre chaîne. Par exemple, si je suis dans un certain dossier, bifurquez. Quelqu'un pourrait-il me dire comment y parvenir? Si possible, je voudrais que ce ne soit pas spécifique au shell (c'est-à-dire pas seulement bash), mais s'il n'y a pas d'autre moyen, je peux me contenter de cela.

#!/usr/bin/env sh

if [ "$PWD" contains "String1" ]
then
    echo "String1 present"
elif [ "$PWD" contains "String2" ]
then
    echo "String2 present"
else
    echo "Else"
fi
Mat
la source
2
Je me rends compte que c'est vieux, mais voici quelques points à noter pour les futurs visiteurs: (1) Il est généralement recommandé de réserver les noms de variables SNAKE_CASE pour les variables internes d'environnement et de shell. (2) Le réglage CURRENT_DIRest redondant; vous pouvez simplement utiliser $PWD.
nyuszika7h

Réponses:

162

Voici encore une autre solution. Cela utilise l' expansion des paramètres de sous-chaîne POSIX , donc cela fonctionne dans Bash, Dash, KornShell (ksh), Z shell (zsh), etc.

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

Une version fonctionnalisée avec quelques exemples:

# 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
la source
3
Cela ne fonctionne pas pour moi si la sous-chaîne contient des barres obliques inverses. Comme d'habitude, substring="$( printf '%q' "$2" )"sauve la mise.
Egor Tensin
cela correspond également à une sous-insertion incorrecte. string = "aplha beta betaone" substring = "beta". il correspond à la fois à la bêtaone et à la bêta, ce qui me semble faux.
rajeev
1
Qu'en est-il des caractères génériques ? [[ $haystack == *"My needle"* ]]
Pablo A
4
Ce qui ne va pas, c'est que les doubles crochets ne sont pas POSIX, ce qui était la prémisse de la question, @Pablo.
Rob Kennedy le
1
Cela ne fonctionne pas avec des caractères spéciaux comme []. Voir ma réponse stackoverflow.com/a/54490453/712666 .
Alex Skrypnyk le
85

Shell POSIX pur:

#!/bin/sh
CURRENT_DIR=`pwd`

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

Les shells étendus comme ksh ou bash ont des mécanismes de correspondance sophistiqués, mais l'ancien style caseest étonnamment puissant.

Norman Ramsey
la source
40

Malheureusement, je ne connais pas un moyen de faire cela dans sh. Cependant, en utilisant bash (à partir de la version 3.0.0, ce que vous avez probablement), vous pouvez utiliser l'opérateur = ~ comme ceci:

#!/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

En prime (et / ou un avertissement, si vos chaînes contiennent des caractères amusants), = ~ accepte les expressions régulières comme opérande correct si vous omettez les guillemets.

John Hyland
la source
3
Ne citez pas l'expression régulière, sinon cela ne fonctionnera pas en général. Par exemple, essayez [[ test =~ "test.*" ]]vs [[ test =~ test.* ]]..
l0b0
1
Eh bien, cela fonctionnera très bien si vous testez une sous-chaîne, comme dans la question d'origine, mais cela ne traitera pas le bon opérande comme une expression régulière. Je mettrai à jour ma réponse pour que cela soit plus clair.
John Hyland
3
C'est Bash, pas POSIX sh comme le demande la question.
Reid le
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
la source
2
Remplacez grep -q "$2"par grep -q "$2" > /dev/nullpour éviter une sortie indésirable.
Victor Sergienko
15

Voici un lien vers diverses solutions à votre problème.

C'est mon préféré car il a le sens le plus lisible par l'homme:

La méthode Star Wildcard

if [[ "$string" == *"$substring"* ]]; then
    return 1
fi
return 0
ma77c
la source
Sur shj'ai reçu "opérande inconnu" avec ceci. Fonctionne avec Bash cependant.
halfer
13
[[n'est pas 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éthode d'aide
la source
2
test $(echo "stringcontain" "ingcon" |awk '{ print index($1, $2) }') -gt 0 && echo "String 1 contain string 2"

-> sortie: la chaîne 1 contient la chaîne 2


la source
2

Voir la page de manuel du programme 'test'. Si vous testez simplement l'existence d'un répertoire, vous feriez normalement quelque chose comme ceci:

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

Si vous essayez réellement de faire correspondre une chaîne, vous pouvez également utiliser des règles d'expansion bash et des caractères génériques:

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

Si vous avez besoin de faire quelque chose de plus complexe, vous devrez utiliser un autre programme comme expr.

jdeseno
la source
1
Il semble y avoir un faux guillemet double (") dans votre deuxième exemple.
Alexis Wilke
2

Dans les cas particuliers où vous voulez savoir si un mot est contenu dans un long texte, vous pouvez parcourir le long texte avec une boucle.

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

C'est un pur shell Bourne.

Kemin Zhou
la source
1

Si vous voulez une méthode ksh seulement aussi rapide que "test", vous pouvez faire quelque chose comme:

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

Cela fonctionne en supprimant l'aiguille de la botte de foin, puis en comparant la longueur de chaîne des anciennes et des nouvelles meules de foin.

JoeOfTex
la source
1
Ne serait-il pas possible de renvoyer le résultat de test? Comme dans return [ ${#haystack} -eq ${#1} ]?
Alexis Wilke
Oui c'est correct. Je vais le laisser ainsi car il est plus facile à comprendre pour les profanes. Si vous utilisez dans votre code, utilisez la méthode @AlexisWilke.
JoeOfTex