これは私が仕事をするために今使っているものです:
#!/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
これは非常に非効率的で冗長です。これを行うより良い方法はありますか?
awk
への1回の呼び出しでパイプなし:
#! /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
これはPOSIXですが、mawk
はまだPOSIX文字クラスをサポートしていないことに注意してください。 --
はPOSIX準拠のawk
sでは必要ありませんが、busybox awk
の古いバージョンでは必要です($string
で始まる-
の値が詰まる)。
case
Shell構文を使用したその関数のバリアント:
has_char_of_each_class() {
input=$1; shift
for class do
case $input in
(*[[:$class:]]*) ;;
(*) return 1;;
esac
done
}
ただし、スクリプトの途中でシェルのロケールを変更しても、すべてのsh
実装では機能しないことに注意してください(したがって、入力したい場合は、Cロケールでスクリプトを呼び出す必要があります。 Cロケールの文字セットと文字クラスでエンコードされていると見なされ、POSIXで指定されたものだけに一致します。
柔軟な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
次のスクリプトはコードよりも長くなっていますが、パターンのリストに対して文字列をテストする方法を示しています。コードは、文字列がすべてのパターンに一致するかどうかを検出し、結果を出力します。
#!/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
case ... esac
複合コマンドは、グロビングパターンのセットに対して文字列をテストするPOSIXの方法です。変数$pattern
は引用符なしでテストで使用されているため、文字列の比較として一致は行われません。文字列が指定されたパターンに一致しない場合、*
に一致し、failed
をtrue
に設定するとループが終了します。
これを実行すると
$ sh script.sh
"TestString1" does not meet the requirements
次のように、テストを関数に組み込むことができます(コードは、ループ内で多数の文字列をテストし、関数を呼び出します)。
#!/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
関数でローカルにLC_ALL=C
を設定する場合は、次のように記述します
test_string () (
LC_ALL=C
for pattern in '*[[:upper:]]*' '*[[:lower:]]*' '*[[:digit:]]*' '*[[:punct:]]*'
do
case $1 in ($pattern) ;; (*) return 1; esac
done
)
関数の本体がサブシェルにあることに注意してください。したがって、LC_ALL=C
を設定しても、呼び出し環境のこの変数の値には影響しません。
引数としてパターンを取るシェル関数も取得します。基本的に、 StéphaneChazelasの回答(バリアント) を取得します。
RomanPerekhrestに触発されましたが、パイプラインとコマンドの置き換えをなくすためのいくつかのマイナーな改良が加えられています。
if awk '/[[:lower:]]/ && /[[:upper:]]/ && /[[:digit:]]/ && /[[:punct:]]/ {exit 1}' <<< "$string" ; then
echo "did not match all requirements"
else
echo "looks good to me"
fi
これは、mawkで動作するように書き換えられたRomanPerekhrestの回答です。
#!/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
また、awkの出力が空かどうかをチェックする代わりに、awkの終了コードを使用してbxmの回答を借用します。
純粋なawk
ソリューションのために@HaroldFischer @bxmおよび@RomanPerekhrestから恥知らずに盗む
awk -v test="does not meet" '/[a-z]/ && /[A-Z]/ && /[0-9]/ && /[[:punct:]]/ {test="meets"}
END {print "String "test" your requirements"}' <<<"Aa&0"
文字が文字範囲(下限、上限など)に該当するかどうかをテストする基本的な方法は、シェルパターン(グロブと同様)を使用することです。
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
完全を期すため、PCREについて言及している回答は他にないため。制限 BRE/ERE の場合、論理的なandをalternationwith _|
_。
PCREパターン 幅ゼロのアサーション(先読みまたは後読み)を使用して「および」条件を作成できます。これらは文字を「消費」しませんが、パターンの前後のマッチングを制限します。これらを使用するには多くの方法がありますが、ここでは前もって先読みすることが理にかなっています。
_LC_ALL=C pcregrep -q '(?=.*[[:upper:]])(?=.*[[:lower:]])(?=.*[[:digit:]])(?=.*[[:punct:]]).{4,}'
_
PCREは、一致を適用する前に、4つの「前提条件」を入力に適用します_.{4,}
_(4文字以上、自由に大きくしてください;-)。注意すべき点は、「_(?=[[:upper:]])
_」は単一の文字のみを検査するため、各条件の前に「_.*
_」が付いているため、入力全体がチェックされるということです。 pcregrep
は、_--locale=C
_によるロケールもサポートします。
PCREの「P」はPerl
を表すので、
_Perl -wln -e \
'/(?=.*[[:upper:]])(?=.*[[:lower:]])(?=.*[[:digit:]])(?=.*[[:punct:]]).{4,}/ && exit 0; exit 1;'
_
単一行の入力に対して同じことを行います( "_pcregrep -q
_"の一般的な置き換えではありません)。
このタイプの問題の頭をひねるスーパーセットはここにあります: https://stackoverflow.com/questions/469913/regular-expressions-is-there-an-and-operator
¹あなたcouldEREを展開して、順列で「and」をエミュレートします。
_[[:lower:]].*[[:upper:]].*[[:digit:]].*[[:punct:]]|
[[:lower:]].*[[:upper:]].*[[:punct:]].*[[:digit:]]|
[[:lower:]].*[[:digit:]].*[[:upper:]].*[[:punct:]]| ... 20 more lines ...
[[:punct:]].*[[:digit:]].*[[:upper:]].*[[:lower:]]
_
間違いなく「非能率で冗長」であることを支援するつもりはありません。
bash
がオプションの場合: extended globbing を有効にし、@(
および!(
サブパターンを組み合わせてグロブ@(!(*[[:upper:]]*)|!(*[[:lower:]]*)|!(*[[:punct:]]*)|!(*[[:digit:]]*))
と比較する
$ 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