如何确保字符串至少包含 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,但请注意,mawk尚不支持 POSIX 字符类。--与 POSIX 兼容的 s 不需要,awk但在旧版本的 busybox 中需要(这会 因以 开头awk的值而阻塞)。$string-

使用 shell 构造的该函数的变体case

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

但请注意,在脚本中间更改 shell 的语言环境并不适用于所有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
)

请注意,函数体现在位于子 shell 中。因此,设置LC_ALL=C不会影响调用环境中该变量的值。

让 shell 函数也将模式作为参数,你基本上得到Stéphane Chazelas 的回答(变体)

答案4

这是 RomanPerekhrest 的答案,重写为与 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

它还借用了 bxm 的答案,使用 awk 的退出代码而不是检查 awk 的输出是否为空。

相关内容