Dans Bash, comment puis-je vérifier si une chaîne commence par une valeur?

746

Je voudrais vérifier si une chaîne commence par "node", par exemple "node001". Quelque chose comme

if [ $HOST == user* ]
  then
  echo yes
fi

Comment puis-je le faire correctement?


J'ai en outre besoin de combiner des expressions pour vérifier si HOST est "user1" ou commence par "node"

if [ [[ $HOST == user1 ]] -o [[ $HOST == node* ]] ];
then
echo yes
fi

> > > -bash: [: too many arguments

Comment puis-je le faire correctement?

Tim
la source
7
Ne soyez pas trop tenté de combiner des expressions. Il peut sembler plus laid d'avoir deux conditions distinctes, bien que vous puissiez donner de meilleurs messages d'erreur et rendre votre script plus facile à déboguer. Aussi j'éviterais les fonctionnalités bash. L'interrupteur est la voie à suivre.
hendry

Réponses:

1074

Cet extrait du guide de script avancé de Bash dit:

# The == comparison operator behaves differently within a double-brackets
# test than within single brackets.

[[ $a == z* ]]   # True if $a starts with a "z" (wildcard matching).
[[ $a == "z*" ]] # True if $a is equal to z* (literal matching).

Vous l'aviez donc presque correct; vous aviez besoin de crochets doubles , pas de crochets simples.


En ce qui concerne votre deuxième question, vous pouvez l'écrire de cette façon:

HOST=user1
if  [[ $HOST == user1 ]] || [[ $HOST == node* ]] ;
then
    echo yes1
fi

HOST=node001
if [[ $HOST == user1 ]] || [[ $HOST == node* ]] ;
then
    echo yes2
fi

Qui fera écho

yes1
yes2

Il ifest difficile de s'habituer à la syntaxe de Bash (IMO).

Mark Rushakoff
la source
12
Par expression régulière, voulez-vous dire [[$ a = ~ ^ z. *]]?
JStrahl
3
Y a-t-il donc une différence fonctionnelle entre [[ $a == z* ]]et [[ $a == "z*" ]]? En d'autres termes: fonctionnent-ils différemment? Et que voulez-vous dire spécifiquement lorsque vous dites "$ a est égal à z *"?
Niels Bom
5
Vous n'avez pas besoin du séparateur d'instructions ";" si vous mettez "alors" sur sa propre ligne
Yaza
6
Juste pour être complet: Pour vérifier si une chaîne se termine avec ...:[[ $a == *com ]]
lepe
4
L'ABS est un choix malheureux de références - c'est bien le W3Schools de bash, plein de contenu obsolète et d'exemples de mauvaises pratiques; la chaîne freenode #bash essaie de décourager son utilisation au moins depuis 2008 . Y a- t-il une chance de rejointoyer au BashFAQ # 31 ? (J'aurais aussi suggéré le wiki des Bash-Hackers, mais il est en panne depuis un petit moment maintenant).
Charles Duffy
207

Si vous utilisez une version récente de Bash (v3 +), je suggère l'opérateur de comparaison d'expressions régulières Bash =~, par exemple,

if [[ "$HOST" =~ ^user.* ]]; then
    echo "yes"
fi

Pour faire correspondre this or thatune expression régulière, utilisez |, par exemple,

if [[ "$HOST" =~ ^user.*|^host1 ]]; then
    echo "yes"
fi

Remarque - il s'agit d'une syntaxe d'expression régulière «correcte».

  • user*signifie useet zéro ou plusieurs occurrences de r, donc useet userrrrcorrespondra.
  • user.*signifie useret zéro ou plusieurs occurrences de n'importe quel caractère, donc user1, userXcorrespondra.
  • ^user.*signifie correspondre au modèle user.*au début de $ HOST.

Si vous n'êtes pas familier avec la syntaxe des expressions régulières, essayez de vous référer à cette ressource .

brabster
la source
Merci, Brabster! J'ai ajouté au message d'origine une nouvelle question sur la façon de combiner des expressions dans if cluase.
Tim
2
C'est dommage que la réponse acceptée ne dit rien sur la syntaxe de l'expression régulière.
CarlosRos
20
Pour info l' =~opérateur Bash ne fait correspondre les expressions régulières que lorsque le côté droit est UNQUOTED. Si vous citez le côté droit "Toute partie du motif peut être citée pour le forcer à correspondre en tant que chaîne." (1.) assurez-vous de toujours mettre les expressions régulières à droite sans guillemets et (2.) si vous stockez votre expression régulière dans une variable, assurez-vous de NE PAS citer le côté droit lorsque vous faites l'expansion des paramètres.
Trevor Boyd Smith
145

J'essaie toujours de m'en tenir à POSIX shau lieu d'utiliser des extensions Bash, car l'un des points majeurs du script est la portabilité (en plus de connecter des programmes, pas de les remplacer).

Dans sh, il existe un moyen simple de rechercher une condition "is-prefix".

case $HOST in node*)
    # Your code here
esac

Compte tenu de l'âge, des arcanes et de la cruauté de sh (et Bash n'est pas le remède: c'est plus compliqué, moins cohérent et moins portable), je voudrais souligner un très bel aspect fonctionnel: alors que certains éléments de syntaxe comme casesont intégrés , les constructions résultantes ne sont pas différentes de tout autre travail. Ils peuvent être composés de la même manière:

if case $HOST in node*) true;; *) false;; esac; then
    # Your code here
fi

Ou encore plus court

if case $HOST in node*) ;; *) false;; esac; then
    # Your code here
fi

Ou encore plus court (juste pour présenter !comme un élément de langage - mais c'est un mauvais style maintenant)

if ! case $HOST in node*) false;; esac; then
    # Your code here
fi

Si vous aimez être explicite, créez votre propre élément de langage:

beginswith() { case $2 in "$1"*) true;; *) false;; esac; }

N'est-ce pas vraiment sympa?

if beginswith node "$HOST"; then
    # Your code here
fi

Et comme il ne shs'agit essentiellement que de travaux et de listes de chaînes (et de processus internes, à partir desquels les travaux sont composés), nous pouvons même maintenant faire une programmation fonctionnelle légère:

beginswith() { case $2 in "$1"*) true;; *) false;; esac; }
checkresult() { if [ $? = 0 ]; then echo TRUE; else echo FALSE; fi; }

all() {
    test=$1; shift
    for i in "$@"; do
        $test "$i" || return
    done
}

all "beginswith x" x xy xyz ; checkresult  # Prints TRUE
all "beginswith x" x xy abc ; checkresult  # Prints FALSE

C'est élégant. Ce n'est pas que je recommanderais d'utiliser shquoi que ce soit de sérieux - cela casse trop rapidement les exigences du monde réel (pas de lambdas, nous devons donc utiliser des chaînes. Mais l'imbrication des appels de fonction avec des chaînes n'est pas possible, les tuyaux ne sont pas possibles, etc.)

Jo So
la source
12
+1 Non seulement il est portable, il est également lisible, idiomatique et élégant (pour le script shell). Il s'étend également naturellement à plusieurs modèles; case $HOST in user01 | node* ) ...
tripleee
Existe-t-il un nom pour ce type de formatage de code? if case $HOST in node*) true;; *) false;; esac; then Je l'ai vu ici et là, à mes yeux, il semble un peu froissé.
Niels Bom
@NielsBom Je ne sais pas exactement ce que vous entendez par formatage, mais mon argument était que le code shell est très composable . Les casecommandes becaues sont des commandes, elles peuvent aller à l'intérieur if ... then.
Jo So
Je ne vois même pas pourquoi il est composable, je ne comprends pas assez de script shell pour cela :-) Ma question est de savoir comment ce code utilise des parenthèses non appariées et des doubles points-virgules. Il ne ressemble en rien au script shell que j'ai vu auparavant, mais je peux être habitué à voir le script bash plus que le script sh, donc ça pourrait être ça.
Niels Bom
Remarque: Il devrait en être beginswith() { case "$2" in "$1"*) true;; *) false;; esac; }autrement s'il $1a un littéral *ou ?il pourrait donner une mauvaise réponse.
LLFourn
80

Vous pouvez sélectionner uniquement la partie de la chaîne que vous souhaitez vérifier:

if [ "${HOST:0:4}" = user ]

Pour votre question de suivi, vous pouvez utiliser un OU :

if [[ "$HOST" == user1 || "$HOST" == node* ]]
Martin Clayton
la source
8
Vous devriez citer deux fois${HOST:0:4}
Jo So
@Jo So: Quelle est la raison?
Peter Mortensen
@PeterMortensen, essayezHOST='a b'; if [ ${HOST:0:4} = user ] ; then echo YES ; fi
Jo So
60

Je préfère les autres méthodes déjà publiées, mais certaines personnes aiment utiliser:

case "$HOST" in 
    user1|node*) 
            echo "yes";;
        *)
            echo "no";;
esac

Éditer:

J'ai ajouté vos suppléants à la déclaration de cas ci-dessus

Dans votre version modifiée, vous avez trop de crochets. Ça devrait ressembler à ça:

if [[ $HOST == user1 || $HOST == node* ]];
En pause jusqu'à nouvel ordre.
la source
Merci Dennis! J'ai ajouté au message d'origine une nouvelle question sur la façon de combiner des expressions dans if cluase.
Tim
11
"certaines personnes aiment ...": celui-ci est plus portable entre les versions et les coques.
carlosayam
Avec les instructions case, vous pouvez laisser de côté les guillemets autour de la variable car aucun fractionnement de mot ne se produit. Je sais que c'est inutile et incohérent, mais j'aime laisser de côté les citations pour le rendre localement plus attrayant visuellement.
Jo So
Et dans mon cas, j'ai dû laisser les citations avant): "/ *") ne fonctionnait pas, / *). (Je recherche des chaînes commençant par /, c'est-à-dire des chemins absolus)
Josiah Yoder
36

Bien que je trouve la plupart des réponses ici tout à fait correctes, beaucoup d'entre elles contiennent des bashismes inutiles. L'extension des paramètres POSIX vous offre tout ce dont vous avez besoin:

[ "${host#user}" != "${host}" ]

et

[ "${host#node}" != "${host}" ]

${var#expr}bandes le plus petit préfixe correspondant exprde ${var}et des rentabilités. Par conséquent, si ${host}ne commence pas par user( node), ${host#user}( ${host#node}) est identique à ${host}.

exprpermet les fnmatch()caractères génériques, ainsi ${host#node??}et les amis travaillent également.

dhke
la source
2
Je dirais que le bashisme [[ $host == user* ]]pourrait être nécessaire, car il est beaucoup plus lisible que [ "${host#user}" != "${host}" ]. En tant que tel, à condition que vous contrôliez l'environnement dans lequel le script est exécuté (ciblez les dernières versions de bash), le premier est préférable.
x-yuri
2
@ x-yuri Franchement, je voudrais simplement ranger cela dans une has_prefix()fonction et ne plus jamais le regarder.
dhke
30

Depuis #a un sens dans Bash, je suis arrivé à la solution suivante.

De plus, j'aime mieux emballer les chaînes avec "" pour surmonter les espaces, etc.

A="#sdfs"
if [[ "$A" == "#"* ]];then
    echo "Skip comment line"
fi
ozma
la source
C'était exactement ce dont j'avais besoin. Merci!
Ionică Bizău du
merci, je me demandais aussi comment faire correspondre une chaîne commençant par blah:, on dirait que c'est la réponse!
Anentropic
12

Ajout d'un tout petit peu plus de détails de syntaxe à la réponse la plus élevée de Mark Rushakoff.

L'expression

$HOST == node*

Peut également s'écrire

$HOST == "node"*

L'effet est le même. Assurez-vous simplement que le caractère générique est en dehors du texte cité. Si le caractère générique est à l' intérieur des guillemets, il sera interprété littéralement (c'est-à-dire pas comme un caractère générique).

MrPotatoHead
la source
8

@OP, pour vos deux questions, vous pouvez utiliser case / esac:

string="node001"
case "$string" in
  node*) echo "found";;
  * ) echo "no node";;
esac

Deuxième question

case "$HOST" in
 node*) echo "ok";;
 user) echo "ok";;
esac

case "$HOST" in
 node*|user) echo "ok";;
esac

Ou Bash 4.0

case "$HOST" in
 user) ;&
 node*) echo "ok";;
esac
ghostdog74
la source
Notez que ;&n'est disponible que dans Bash> = 4.
pause jusqu'à nouvel ordre.
6
if [ [[ $HOST == user1 ]] -o [[ $HOST == node* ]] ];
then
echo yes
fi

ne fonctionne pas, parce que tous [, [[et testreconnaître la même grammaire nonrecursive. Voir la section EXPRESSIONS CONDITIONNELLES sur votre page de manuel Bash.

En passant, le SUSv3 dit

La commande conditionnelle dérivée de KornShell (double crochet [[]] ) a été supprimée de la description du langage de commande shell dans une première proposition. Des objections ont été soulevées selon lesquelles le vrai problème est une mauvaise utilisation de la commande de test ( [ ), et la placer dans le shell n'est pas la bonne façon de résoudre le problème. À la place, une documentation appropriée et un nouveau mot réservé au shell ( ! ) Sont suffisants.

Les tests qui nécessitent plusieurs opérations de test peuvent être effectués au niveau du shell à l'aide d' appels individuels de la commande de test et des logiques du shell, plutôt que d'utiliser l' indicateur -o sujet aux erreurs de test .

Vous devez l'écrire de cette façon, mais le test ne le prend pas en charge:

if [ $HOST == user1 -o $HOST == node* ];
then
echo yes
fi

test utilise = pour l'égalité des chaînes, et plus important encore, il ne prend pas en charge la correspondance de modèles.

case/ esaca un bon support pour la correspondance de motifs:

case $HOST in
user1|node*) echo yes ;;
esac

Il a l'avantage supplémentaire de ne pas dépendre de Bash, et la syntaxe est portable. De la spécification Unix unique , le langage de commande Shell :

case word in
    [(]pattern1) compound-list;;
    [[(]pattern[ | pattern] ... ) compound-list;;] ...
    [[(]pattern[ | pattern] ... ) compound-list]
esac
juste quelqu'un
la source
1
[et testsont intégrés Bash ainsi que des programmes externes. Essayez type -a [.
pause jusqu'à nouvel ordre.
Merci beaucoup d'avoir expliqué les problèmes avec le "composé ou", @just quelqu'un - cherchait précisément quelque chose comme ça! À votre santé! PS Note (sans rapport avec OP): if [ -z $aa -or -z $bb ]; ... donne " bash: [: -ou: opérateur binaire attendu "; if [ -z "$aa" -o -z "$bb" ] ; ...passe cependant .
sdaau
2

grep

Oubliant les performances, c'est POSIX et plus joli que les casesolutions:

mystr="abcd"
if printf '%s' "$mystr" | grep -Eq '^ab'; then
  echo matches
fi

Explication:

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
la source
2

J'ai modifié la réponse de @ markrushakoff pour en faire une fonction appelable:

function yesNo {
  # Prompts user with $1, returns true if response starts with y or Y or is empty string
  read -e -p "
$1 [Y/n] " YN

  [[ "$YN" == y* || "$YN" == Y* || "$YN" == "" ]]
}

Utilisez-le comme ceci:

$ if yesNo "asfd"; then echo "true"; else echo "false"; fi

asfd [Y/n] y
true

$ if yesNo "asfd"; then echo "true"; else echo "false"; fi

asfd [Y/n] Y
true

$ if yesNo "asfd"; then echo "true"; else echo "false"; fi

asfd [Y/n] yes
true

$ if yesNo "asfd"; then echo "true"; else echo "false"; fi

asfd [Y/n]
true

$ if yesNo "asfd"; then echo "true"; else echo "false"; fi

asfd [Y/n] n
false

$ if yesNo "asfd"; then echo "true"; else echo "false"; fi

asfd [Y/n] ddddd
false

Voici une version plus complexe qui prévoit une valeur par défaut spécifiée:

function toLowerCase {
  echo "$1" | tr '[:upper:]' '[:lower:]'
}

function yesNo {
  # $1: user prompt
  # $2: default value (assumed to be Y if not specified)
  # Prompts user with $1, using default value of $2, returns true if response starts with y or Y or is empty string

  local DEFAULT=yes
  if [ "$2" ]; then local DEFAULT="$( toLowerCase "$2" )"; fi
  if [[ "$DEFAULT" == y* ]]; then
    local PROMPT="[Y/n]"
  else
    local PROMPT="[y/N]"
  fi
  read -e -p "
$1 $PROMPT " YN

  YN="$( toLowerCase "$YN" )"
  { [ "$YN" == "" ] && [[ "$PROMPT" = *Y* ]]; } || [[ "$YN" = y* ]]
}

Utilisez-le comme ceci:

$ if yesNo "asfd" n; then echo "true"; else echo "false"; fi

asfd [y/N]
false

$ if yesNo "asfd" n; then echo "true"; else echo "false"; fi

asfd [y/N] y
true

$ if yesNo "asfd" y; then echo "true"; else echo "false"; fi

asfd [Y/n] n
false
Mike Slinn
la source
-5

Une autre chose que vous pouvez faire est de catsavoir avec quoi vous faites écho etinline cut -c 1-1

Richard Tang
la source