문자열에 대문자 1개, 소문자 1개, 숫자 1개, 구두점 1개 이상이 포함되어 있는지 확인하는 방법은 무엇입니까?

문자열에 대문자 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

이는 매우 비효율적이고 장황합니다. 더 좋은 방법이 있나요?

답변1

한 번의 호출로 awk파이프 없이:

#! /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이지만 mawkPOSIX 문자 클래스는 아직 지원되지 않습니다. --POSIX 호환 s에는 필요하지 않지만 awk이전 버전의 busybox에는 필요합니다( awks로 시작하는 값을 차단함).$string-

쉘 구성을 사용하는 이 함수의 변형 case:

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

그러나 스크립트 도중에 쉘의 로케일을 변경하는 것이 모든 sh구현에서 작동하는 것은 아닙니다. 따라서 입력을 C 로케일로 인코딩된 것으로 처리하려면 C 로케일에서 스크립트를 호출해야 합니다. C 로캘 문자 집합 및 문자 클래스는 POSIX 지정 문자 집합 및 문자 클래스에만 일치합니다.

답변2

유연한 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

답변3

다음 스크립트는 코드보다 길지만 패턴 목록에 대해 문자열을 테스트하는 방법을 보여줍니다. 이 코드는 문자열이 모든 패턴과 일치하는지 확인하고 결과를 인쇄합니다.

#!/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éphane Chazelas의 답변 (변형).

답변4

다음은 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

또한 bxm의 답변을 빌려 awk의 출력이 비어 있는지 확인하는 대신 awk의 종료 코드를 사용합니다.

관련 정보