web-dev-qa-db-fra.com

Comment puis-je m'assurer qu'une chaîne contient au moins une lettre majuscule, une lettre minuscule, un chiffre et un caractère de ponctuation?

Voici ce que j'utilise maintenant pour faire le travail:

#!/bin/sh --

string='Aa1!z'

if ! printf '%s\n' "$string" | LC_ALL=C grep -q '[[:upper:]]' || \
   ! printf '%s\n' "$string" | LC_ALL=C grep -q '[[:lower:]]' || \
   ! printf '%s\n' "$string" | LC_ALL=C grep -q '[[:digit:]]' || \
   ! printf '%s\n' "$string" | LC_ALL=C grep -q '[[:punct:]]'; then
  printf '%s\n' 'String does not meet your requirements'
else
  printf '%s\n' 'String meets your requirements'
fi

C'est extrêmement inefficace et verbeux. Y a-t-il une meilleure manière de faire cela?

5
Harold Fischer

Avec un seul appel à awk et sans pipe:

#! /bin/sh -
string='whatever'

has_char_of_each_class() {
  LC_ALL=C awk -- '
    BEGIN {
      for (i = 2; i < ARGC; i++)
        if (ARGV[1] !~ "[[:" ARGV[i] ":]]") exit 1
    }' "$@"
}

if has_char_of_each_class "$string" lower upper digit punct; then
  echo OK
else
  echo not OK
fi

C'est POSIX mais notez que mawk ne prend pas encore en charge les classes de caractères POSIX. Le -- n'est pas nécessaire avec la compatibilité POSIX awks mais le serait dans les anciennes versions de busybox awk (ce qui étoufferait les valeurs de $string commençant par -).

Une variante de cette fonction utilisant une construction Shell case:

has_char_of_each_class() {
  input=$1; shift
  for class do
    case $input in
      (*[[:$class:]]*) ;;
      (*) return 1;;
    esac
  done
}

Notez cependant que la modification des paramètres régionaux pour le shell au milieu d'un script ne fonctionne pas avec toutes les implémentations sh (vous aurez donc besoin que le script soit déjà appelé dans les paramètres régionaux C si vous souhaitez que l'entrée être considéré comme étant codé dans le jeu de caractères C local et les classes de caractères pour correspondre uniquement à celles spécifiées par POSIX).

7
Stéphane Chazelas

Avec une correspondance flexible de motifs awk:

if [[ $(echo "$string" | awk '/[a-z]/ && /[A-Z]/ && /[0-9]/ && /[[:punct:]]/') ]]; then  
    echo "String meets your requirements"
else 
    echo "String does not meet your requirements"
fi
6
RomanPerekhrest

Le script suivant est plus long que votre code, mais montre comment vous pouvez tester une chaîne par rapport à une liste de modèles. Le code détecte si la chaîne correspond à tous les modèles ou non et imprime un résultat.

#!/bin/sh

string=TestString1

failed=false

for pattern in '*[[:upper:]]*' '*[[:lower:]]*' '*[[:digit:]]*' '*[[:punct:]]*'
do
    case $string in
        $pattern) ;;
        *)
            failed=true
            break
    esac
done

if "$failed"; then
    printf '"%s" does not meet the requirements\n' "$string"
else
    printf '"%s" is ok\n' "$string"
fi

Le case ... esac La commande composée est la manière POSIX de tester une chaîne par rapport à un ensemble de modèles globbing. La variable $pattern est utilisé sans guillemets dans le test, de sorte que la correspondance ne se fait pas comme comparaison de chaînes. Si la chaîne ne correspond pas au modèle donné, elle correspondra à *, et la boucle est fermée après avoir défini failed sur true.

L'exécution de cela donnerait

$ sh script.sh
"TestString1" does not meet the requirements

Vous pouvez ranger le test dans une fonction comme celle-ci (le code teste un certain nombre de chaînes dans une boucle, appelant la fonction):

#!/bin/sh

test_string () {
    for pattern in '*[[:upper:]]*' '*[[:lower:]]*' '*[[:digit:]]*' '*[[:punct:]]*'
    do
        case $1 in ($pattern) ;; (*) return 1; esac
    done
}

for string in TestString1 Test.String2 TestString-3; do
    if ! test_string "$string"; then
        printf '"%s" does not meet the requirements\n' "$string"
    else
        printf '"%s" is ok\n' "$string"
    fi
done

Si vous souhaitez définir LC_ALL=C localement dans la fonction, écrivez-le comme

test_string () (
    LC_ALL=C

    for pattern in '*[[:upper:]]*' '*[[:lower:]]*' '*[[:digit:]]*' '*[[:punct:]]*'
    do
        case $1 in ($pattern) ;; (*) return 1; esac
    done
)

Notez que le corps de la fonction est maintenant dans un sous-shell. Réglage LC_ALL=C n'affectera donc pas la valeur de cette variable dans l'environnement appelant.

Obtenez la fonction Shell pour prendre les motifs comme arguments aussi, et vous obtenez essentiellement réponse de Stéphane Chazelas (la variante) .

3
Kusalananda

Inspiré par RomanPerekhrest, mais avec quelques améliorations mineures pour supprimer le pipeline et la substitution de commandes:

if awk '/[[:lower:]]/ && /[[:upper:]]/ && /[[:digit:]]/ && /[[:punct:]]/ {exit 1}' <<< "$string" ; then
  echo "did not match all requirements"
else
  echo "looks good to me"
fi
3
bxm

Voici la réponse de RomanPerekhrest réécrite pour travailler avec mawk:

#!/bin/sh --

string='Aa1!z'

if printf '%s\n' "$string" | LC_ALL=C awk '/[a-z]/ && /[A-Z]/ && /[0-9]/ && /[!-\/:-@[-`{-~]/ {exit 1}'; then
  printf '%s\n' 'String does not meet your requirements'
else
  printf '%s\n' 'String meets your requirements'
fi

Il emprunte également à la réponse de bxm en utilisant le code de sortie de awk au lieu de vérifier si la sortie de awk est vide.

3
Harold Fischer

Voler sans vergogne à @HaroldFischer @bxm et @RomanPerekhrest pour une solution awk pure

awk -v test="does not meet" '/[a-z]/ && /[A-Z]/ && /[0-9]/ && /[[:punct:]]/ {test="meets"}
    END {print "String "test" your requirements"}' <<<"Aa&0"
1
bu5hman

Un moyen de base pour tester que les caractères tombent dans des plages de caractères (inférieur, supérieur, etc.) consiste à utiliser un modèle Shell (similaire à un glob).

password='aA23.sd'

res=true
for    p in lower upper digit punct
do     if     [ "${str}" = "${password#*[[:$p:]]}" ]
       then   res=false; break
       fi
done

if     "$res"
then
       echo "The password match all requirements"
else
       echo "The password doesn't match all requirements"
fi
0
Isaac

Pour être complet, car aucune autre réponse ne mentionne PCRE. Une limitation de BRE/ERE est que vous ne pouvez pas trivialement implémenter un logique et pour le "ou" logique équivalent dans alternance avec |.

modèles PCRE vous permet de créer des conditions "et" en utilisant des assertions de largeur nulle: regard vers l'avant ou vers l'arrière. Ces caractères "ne consomment" aucun caractère, mais restreignent la correspondance avant ou après les modèles. Il existe de nombreuses façons de les utiliser, il est judicieux de placer les perspectives à l'avant:

LC_ALL=C pcregrep -q '(?=.*[[:upper:]])(?=.*[[:lower:]])(?=.*[[:digit:]])(?=.*[[:punct:]]).{4,}'

Le PCRE applique 4 "conditions préalables" à l'entrée avant d'appliquer la correspondance .{4,} (4 caractères ou plus, n'hésitez pas à l'agrandir ;-). Un point à noter est que "(?=[[:upper:]]) "inspectera uniquement un seul caractère, donc chaque condition est précédée de" .* "afin que toute l'entrée soit vérifiée. pcregrep prend également en charge les paramètres régionaux via --locale=C.

Puisque le "P" dans PCRE signifie Perl:

Perl -wln -e \
  '/(?=.*[[:upper:]])(?=.*[[:lower:]])(?=.*[[:digit:]])(?=.*[[:punct:]]).{4,}/ && exit 0; exit 1;'

fait la même chose pour une seule ligne d'entrée (ce n'est pas un remplacement général pour "pcregrep -q ").

Un sur-ensemble qui fait tourner la tête de ce type de problème peut être trouvé ici: https://stackoverflow.com/questions/469913/regular-expressions-is-there-an-and-operator


¹ Vous pourriez développer un ERE pour émuler "et" par permutations:

[[:lower:]].*[[:upper:]].*[[:digit:]].*[[:punct:]]|
[[:lower:]].*[[:upper:]].*[[:punct:]].*[[:digit:]]|
[[:lower:]].*[[:digit:]].*[[:upper:]].*[[:punct:]]| ... 20 more lines ...
[[:punct:]].*[[:digit:]].*[[:upper:]].*[[:lower:]]

Ne va certainement pas aider à être "inefficace et verbeux".

0
mr.spuratic

Maintenant, si bash était une option: vous pouvez activer globbing étend et combiner les sous-modèles @( Et !( Pour construire la fonction glob @(!(*[[:upper:]]*)|!(*[[:lower:]]*)|!(*[[:punct:]]*)|!(*[[:digit:]]*)) pour comparer

$ shopt -s extglob
$ arr=( '!(*'{'[[:upper:]]','[[:lower:]]','[[:punct:]]','[[:digit:]]'}'*)' )
$ pattern=$(IFS='|'; printf '@(%s)' "${arr[*]}")
$ printf "$pattern\n"
@(!(*[[:upper:]]*)|!(*[[:lower:]]*)|!(*[[:punct:]]*)|!(*[[:digit:]]*))
$ [[ 'Aa3,' = $pattern ]] && echo yes
$ [[ 'Aa3' = $pattern ]] && echo yes
yes
$ [[ 'Aa,' = $pattern ]] && echo yes
yes
$ [[ 'A3,' = $pattern ]] && echo yes
yes
$ [[ 'a3,' = $pattern ]] && echo yes
yes
0
iruvar