web-dev-qa-db-ja.com

文字列に少なくとも1つの大文字、1つの小文字、1つの数字、および1つの句読文字が含まれていることを確認するにはどうすればよいですか?

これは私が仕事をするために今使っているものです:

#!/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

これは非常に非効率的で冗長です。これを行うより良い方法はありますか?

5
Harold Fischer

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準拠のawksでは必要ありませんが、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で指定されたものだけに一致します。

7

柔軟な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

次のスクリプトはコードよりも長くなっていますが、パターンのリストに対して文字列をテストする方法を示しています。コードは、文字列がすべてのパターンに一致するかどうかを検出し、結果を出力します。

#!/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は引用符なしでテストで使用されているため、文字列の比較として一致は行われません。文字列が指定されたパターンに一致しない場合、*に一致し、failedtrueに設定するとループが終了します。

これを実行すると

$ 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の回答(バリアント) を取得します。

3
Kusalananda

RomanPerekhrestに触発されましたが、パイプラインとコマンドの置き換えをなくすためのいくつかのマイナーな改良が加えられています。

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

これは、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の回答を借用します。

3
Harold Fischer

純粋な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"
1
bu5hman

文字が文字範囲(下限、上限など)に該当するかどうかをテストする基本的な方法は、シェルパターン(グロブと同様)を使用することです。

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

完全を期すため、PCREについて言及している回答は他にないため。制限 BRE/ERE の場合、論理的なandalternationwith _|_。

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:]]
_

間違いなく「非能率で冗長」であることを支援するつもりはありません。

0
mr.spuratic

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
0
iruvar