web-dev-qa-db-fra.com

Comment vérifier si une chaîne contient une sous-chaîne dans Bash

J'ai une chaîne dans Bash:

string="My string"

Comment puis-je tester s'il contient une autre chaîne?

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

?? est mon opérateur inconnu. Est-ce que j'utilise echo et grep?

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

Cela semble un peu maladroit.

1969
davidsheldon

Vous pouvez utiliser la réponse de Marcus (* caractères génériques) en dehors d'une instruction de casse, aussi, si vous utilisez des doubles crochets:

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

Notez que les espaces dans la chaîne d'aiguille doivent être placés entre guillemets et que les caractères génériques * doivent être à l'extérieur.

2842
Adam Bellaire

Si vous préférez l'approche regex:

string='My string';

if [[ $string =~ .*My.* ]]
then
   echo "It's there!"
fi
429
Matt Tardiff

Je ne suis pas sûr d'utiliser une instruction if, mais vous pouvez obtenir un effet similaire avec une instruction case:

case "$string" in 
  *foo*)
    # Do stuff
    ;;
esac
271
Marcus Griep

Réponse compatible

Comme il existe déjà de nombreuses réponses utilisant les fonctionnalités spécifiques à Bash, il existe un moyen de travailler sous des shells moins riches, comme busybox :

[ -z "${string##*$reqsubstr*}" ]

En pratique, cela pourrait donner:

string='echo "My string"'
for reqsubstr in 'o "M' 'alt' 'str';do
  if [ -z "${string##*$reqsubstr*}" ] ;then
      echo "String '$string' contain substring: '$reqsubstr'."
    else
      echo "String '$string' don't contain substring: '$reqsubstr'."
    fi
  done

Cela a été testé sous bash , tiret , ksh et ash (busybox), et le résultat est toujours:

String 'echo "My string"' contain substring: 'o "M'.
String 'echo "My string"' don't contain substring: 'alt'.
String 'echo "My string"' contain substring: 'str'.

Dans une fonction

Comme demandé par @EeroAaltonen, voici une version de la même démo, testée sous les mêmes shells:

myfunc() {
    reqsubstr="$1"
    shift
    string="$@"
    if [ -z "${string##*$reqsubstr*}" ] ;then
        echo "String '$string' contain substring: '$reqsubstr'.";
      else
        echo "String '$string' don't contain substring: '$reqsubstr'." 
    fi
}

Ensuite:

$ myfunc 'o "M' 'echo "My String"'
String 'echo "My String"' contain substring 'o "M'.

$ myfunc 'alt' 'echo "My String"'
String 'echo "My String"' don't contain substring 'alt'.

Remarque: vous devez échapper ou doubler des guillemets et/ou des guillemets:

$ myfunc 'o "M' echo "My String"
String 'echo My String' don't contain substring: 'o "M'.

$ myfunc 'o "M' echo \"My String\"
String 'echo "My String"' contain substring: 'o "M'.

Fonction simple

Cela a été testé sous busybox , tiret et, bien sûr bash :

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

C'est tout les gens!

Alors maintenant:

$ if stringContain 'o "M3' 'echo "My String"';then echo yes;else echo no;fi
no
$ if stringContain 'o "M' 'echo "My String"';then echo yes;else echo no;fi
yes

... Ou si la chaîne soumise pouvait être vide, comme l'a souligné @Sjlver, la fonction deviendrait:

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

ou comme suggéré par le commentaire d'Adrian Günter , en évitant le -o switche:

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

Avec des chaînes vides: 

$ if stringContain '' ''; then echo yes; else echo no; fi
yes
$ if stringContain 'o "M' ''; then echo yes; else echo no; fi
no
172
F. Hauri

N'oubliez pas que les scripts Shell sont moins un langage que des ensembles de commandes. Instinctivement, vous pensez que cette "langue" vous oblige à suivre une if avec un [ ou un [[. Ce ne sont que des commandes qui renvoient un état de sortie indiquant le succès ou l’échec (comme toutes les autres commandes). Pour cette raison, j'utiliserais grep et non la commande [

Il suffit de faire:

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

Maintenant que vous pensez à if, vous testez l'état de sortie de la commande qui la suit (avec le point-virgule). Pourquoi ne pas reconsidérer la source de la chaîne que vous testez?

## 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
#...

L'option -q fait que grep ne génère rien, car nous ne voulons que le code de retour. <<< oblige le shell à développer le prochain mot et à l'utiliser comme entrée de la commande, une version d'une ligne du document << here (je ne suis pas sûr qu'il s'agisse d'un standard ou d'un bashisme).

131
Mark Baker

La réponse acceptée est la meilleure, mais comme il existe plus d'une façon de le faire, voici une autre solution:

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

${var/search/replace} est $var avec la première instance de search remplacée par replace, si elle est trouvée (elle ne change pas $var). Si vous essayez de remplacer foo par rien, et que la chaîne a changé, alors évidemment foo a été trouvé.

81
ephemient

Il y a donc beaucoup de solutions utiles à la question - mais laquelle est la plus rapide/utilise le moins de ressources?

Tests répétés en utilisant ce cadre:

/usr/bin/time bash -c 'a=two;b=onetwothree; x=100000; while [ $x -gt 0 ]; do TEST ; x=$(($x-1)); done'

Remplacer TEST à chaque fois:

[[ $b =~ $a ]]           2.92user 0.06system 0:02.99elapsed 99%CPU

[ "${b/$a//}" = "$b" ]   3.16user 0.07system 0:03.25elapsed 99%CPU

[[ $b == *$a* ]]         1.85user 0.04system 0:01.90elapsed 99%CPU

case $b in *$a):;;esac   1.80user 0.02system 0:01.83elapsed 99%CPU

doContain $a $b          4.27user 0.11system 0:04.41elapsed 99%CPU

(doContain était dans la réponse de F. Houri)

Et pour rire

echo $b|grep -q $a       12.68user 30.86system 3:42.40elapsed 19%CPU !ouch!

Donc, l'option de substitution simple l'emporte de manière préditable, que ce soit dans un test étendu ou dans un cas. L'affaire est portable.

Piper à 100 000 greps est prévisible douloureux! L'ancienne règle sur l'utilisation d'utilitaires externes sans nécessité est vraie.

41
Paul Hedderly

Cela fonctionne aussi:

if printf -- '%s' "$haystack" | egrep -q -- "$needle"
then
  printf "Found needle in haystack"
fi

Et le test négatif est:

if ! printf -- '%s' "$haystack" | egrep -q -- "$needle"
then
  echo "Did not find needle in haystack"
fi

Je suppose que ce style est un peu plus classique - moins dépendant des fonctionnalités de Bash Shell.

L'argument -- est une pure paranoïa POSIX, utilisé pour se protéger contre les chaînes d'entrée similaires aux options, telles que --abc ou -a.

Remarque: dans une boucle étroite, ce code sera beaucoup plus lent que l'utilisation des fonctions internes de Bash Shell, puisqu'un ou deux processus distincts seront créés et connectés via des tuyaux.

26
kevinarpe

Que dis-tu de ça:

text="   <tag>bmnmn</tag>  "
if [[ "$text" =~ "<tag>" ]]; then
   echo "matched"
else
   echo "not matched"
fi
15
Stefan

Comme Paul l'a mentionné dans sa comparaison de performances:

if echo "abcdefg" | grep -q "bcdef"; then
    echo "String contains is true."
else
    echo "String contains is not true."
fi

Ceci est conforme à POSIX comme la "case" $ string "dans la réponse fournie par Marcus, mais est légèrement plus facile à lire que la réponse à la déclaration case. Notez également que cela sera beaucoup plus lent que d'utiliser une déclaration de casse, comme Paul l'a souligné, ne l'utilisez pas en boucle.

11
Samuel

Bash4 + exemples. Remarque: ne pas utiliser de guillemets causera des problèmes lorsque les mots contiennent des espaces, etc. Toujours citer entre guillemets dans IMO.

Voici quelques exemples BASH4 +:

Exemple 1, recherchez «oui» dans la chaîne (insensible à la casse):

    if [[ "${str,,}" == *"yes"* ]] ;then

Exemple 2, recherchez «oui» dans la chaîne (insensible à la casse):

    if [[ "$(echo "$str" | tr '[:upper:]' '[:lower:]')" == *"yes"* ]] ;then

Exemple 3, recherchez «oui» dans la chaîne (sensible à la casse):

     if [[ "${str}" == *"yes"* ]] ;then

Exemple 4, recherchez «oui» dans la chaîne (sensible à la casse):

     if [[ "${str}" =~ "yes" ]] ;then

Exemple 5, correspondance exacte (sensible à la casse):

     if [[ "${str}" == "yes" ]] ;then

Exemple 6, correspondance exacte (insensible à la casse):

     if [[ "${str,,}" == "yes" ]] ;then

Exemple 7, correspondance exacte:

     if [ "$a" = "$b" ] ;then

Exemple 8, correspondance avec caractère générique .ext (insensible à la casse):

     if echo "$a" | egrep -iq "\.(mp[3-4]|txt|css|jpg|png)" ; then

prendre plaisir.

10
Mike Q

Cette réponse au débordement de pile était la seule à intercepter l'espace et les caractères tiret:

# For null cmd arguments checking   
to_check=' -t'
space_n_dash_chars=' -'
[[ $to_check == *"$space_n_dash_chars"* ]] && echo found
10
Yordan Georgiev

L'un est:

[ $(expr $mystring : ".*${search}.*") -ne 0 ] && echo 'yes' ||  echo 'no'
9
chemila
[[ $string == *foo* ]] && echo "It's there" || echo "Couldn't find"
8
Jahid

grep -q est utile à cet effet.

La même chose avec awk:

string="unix-bash 2389"
character="@"
printf '%s' "$string" | awk -vc="$character" '{ if (gsub(c, "")) { print "Found" } else { print "Not Found" } }'

Sortie:

Pas trouvé

string="unix-bash 2389"
character="-"
printf '%s' "$string" | awk -vc="$character" '{ if (gsub(c, "")) { print "Found" } else { print "Not Found" } }'

Sortie:

A trouvé

Source originale: http://unstableme.blogspot.com/2008/06/bash-search-letter-in-string-awk.html

5
Jadu Saikia

Mon .bash_profile et comment j'ai utilisé grep Si le PATH incluait mes 2 répertoires 

# .bash_profile
# Get the aliases and functions
if [ -f ~/.bashrc ]; then
    . ~/.bashrc
fi

U=~/.local.bin:~/bin

if ! echo "$PATH" | grep -q "home"; then
    export PATH=$PATH:${U}   
fi
5
Leslie Satenstein

J'aime sed.

substr="foo"
nonsub="$(echo "$string" | sed "s/$substr//")"
hassub=0 ; [ "$string" != "$nonsub" ] && hassub=1

Edit, Logic:

  • Utilisez sed pour supprimer une instance de sous-chaîne d'une chaîne

  • Si la nouvelle chaîne diffère de l'ancienne chaîne, la sous-chaîne existe

5
ride

J'ai eu besoin de cette fonctionnalité assez fréquemment, alors j'utilise une fonction Shell faite maison dans mon .bashrc comme ceci, ce qui me permet de la réutiliser aussi souvent que nécessaire, avec un nom facile à retenir:

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

Pour tester si $string1 (par exemple, abc ) est contenu dans $string2 (par exemple, 123abcABC ), je dois juste exécuter stringinstring "$string1" "$string2" et vérifier la valeur de retour, par exemple

stringinstring "$str1" "$str2"  &&  echo YES  ||  echo NO
4
Kurt Pfeifle

Essayez oobash car il s'agit d'une bibliothèque de chaînes de style OO pour bash 4. Il prend en charge les trémas allemands. C'est écrit en bash. Plusieurs fonctions sont disponibles: -base64Decode, -base64Encode, -capitalize, -center, -charAt, -concat, -contains, _ [SOMECODE] ] _, -count, -endsWith, -equals, -equalsIgnoreCase, -reverse, -hashCode, -indexOf, -isAlnum, , -isAlpha, -isAscii, -isDigit, -isEmpty, -isHexDigit, -isLowerCase, -isSpace, -isPrintable, -isUpperCase, -isVisible, -lastIndexOf, -length, -matches, -replaceAll, -replaceFirst, -startsWith, -substring, -swapCase, -toLowerCase, -toString, -toUpperCase, -trim et -zfill.

Regardez l'exemple contient:

[Desktop]$ String a testXccc                                                  
[Desktop]$ a.contains tX                   
true                                                           
[Desktop]$ a.contains XtX      
false      

oobash est disponible sur Sourceforge.net .

3
andreas

Correspondance exacte du mot:

string='My long string'
exactSearch='long'

if grep -E -q "\b${exactSearch}\b" <<<${string} >/dev/null 2>&1
  then
    echo "It's there"
  fi
3
Eduardo Cuomo

J'utilise cette fonction (une dépendance non incluse mais évidente). Il passe les tests ci-dessous. Si la fonction renvoie une valeur> 0, la chaîne a été trouvée. Vous pouvez tout aussi facilement renvoyer 1 ou 0 à la place.

function str_instr {
   # Return position of ```str``` within ```string```.
   # >>> str_instr "str" "string"
   # str: String to search for.
   # string: String to search.
   typeset str string x
   # Behavior here is not the same in bash vs ksh unless we escape special characters.
   str="$(str_escape_special_characters "${1}")"
   string="${2}"
   x="${string%%$str*}"
   if [[ "${x}" != "${string}" ]]; then
      echo "${#x} + 1" | bc -l
   else
      echo 0
   fi
}

function test_str_instr {
   str_instr "(" "'foo@Host (dev,web)'" | assert_eq 11
   str_instr ")" "'foo@Host (dev,web)'" | assert_eq 19
   str_instr "[" "'foo@Host [dev,web]'" | assert_eq 11
   str_instr "]" "'foo@Host [dev,web]'" | assert_eq 19
   str_instr "a" "abc" | assert_eq 1
   str_instr "z" "abc" | assert_eq 0
   str_instr "Eggs" "Green Eggs And Ham" | assert_eq 7
   str_instr "a" "" | assert_eq 0
   str_instr "" "" | assert_eq 0
   str_instr " " "Green Eggs" | assert_eq 6
   str_instr " " " Green "  | assert_eq 1
}
2
Ethan Post

Extension de la question à laquelle vous avez répondu ici https://stackoverflow.com/a/8811800/712666

Cette solution fonctionne avec des caractères spéciaux:

# contains(string, substring)
#
# Returns 0 if the specified string contains the specified substring,
# otherwise returns 1.
contains() {
    string="$1"
    substring="$2"

    if echo "$string" | $(type -p ggrep grep | head -1) -F -- "$substring" >/dev/null; 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"

contains "abcd [efg] hij" "[efg]" && echo "abcd [efg] hij contains [efg]"
contains "abcd [efg] hij" "[effg]" || echo "abcd [efg] hij does not contain [effg]"

contains "abcd *efg* hij" "*efg*" && echo "abcd *efg* hij contains *efg*"
contains "abcd *efg* hij" "d *efg* h" && echo "abcd *efg* hij contains d *efg* h"
contains "abcd *efg* hij" "*effg*" || echo "abcd *efg* hij does not contain *effg*"
1
Alex.Designworks