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?
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 awk
s 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).
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
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) .
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
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.
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"
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
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".
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