虽然您的脚本在技术上(几乎)是正确的,但它并不是很漂亮(无论是在视觉上还是在代码美感方面)。
您的版本没有按预期工作的原因@devnull 已经指出,但还有另一个错误,我将在下面进一步解释。
由于您正在使用bash
,您可以以更惯用、更易读和更短的方式重写整个内容。
这是您的脚本的重写版本(评论和解释如下):
#!/bin/bash
clear
IFS= read -p "enter a string: " string
for ((i = 0; i < ${#string}; i++)); do
case "${string:$i:1}" in
[AaEeIiOoUu]) ((vowels++)) ;;
[[:alpha:]]) ((consonants++)) ;;
[[:digit:]]) ((digits++)) ;;
[[:space:]]) ((whitespace++)) ;;
*) ((others++)) ;;
esac
done
echo "vowels = ${vowels-0}"
echo "consonants = ${consonants-0}"
echo "digits = ${digits-0}"
echo "whitespace = ${whitespace-0}"
echo "other characters = ${others-0}"
缩进
首先,您应该始终缩进您的代码块(if
构造、循环、switch ( case
) 语句,...)以提高可读性,例如
while [ $len -gt 0 ]; do
do_stuff
done
读取、空格和提示
由于您正在使用bash
,read
能够为您显示提示 -echo
不需要额外的。此外,除非您设置为空字符串,read
否则会去除导致计算错误的前导和尾随空格:IFS
IFS= read -p "this is my prompt: " string
遍历字符串中的字符
您可以使用参数扩展来获取字符串的长度以及使用 for 循环一次切出一个字符,摆脱不必要的cut
并避免子外壳。
# ${#string} = length of $string
for ((i = 0; i < ${#string}; i++)); do
c=${string:$i:1} # c is set to the character at position $i in string $string
done
字符类
首先,您的辅音声明仍然包括Ii
匹配。从技术上讲,这并不重要,因为您不能从元音匹配中掉线,但如果这是一个作业,您可能想要删除它。
话虽如此,您可以只使用短字符类来提高可读性:
[AaEeIiOoUu]) vowel_stuff ;;
[a-zA-Z]) consonant_stuff ;; # vowels already matched above, so superfluous characters don't matter here
为了让您的生活更轻松,您可以使用所谓的字符类,例如
[:digit:] = [0-9]
[:space:] = tabs, newline, form feed, carriage return, space
等等。请注意,您当前的语言环境会影响某些字符类别。
特殊字符大小写
只需使用 switch 语句中的默认情况来计算它们,然后您可以跳过之后的计算:
case ... in
vowels) handle_vowel ;;
[...]
*) handle_other_character ;;
esac
默认值
使用参数扩展,您还可以摆脱使用 初始化变量0
,如果变量未设置,您可以即时将变量扩展为0
,即它们在循环期间没有递增。
反引号
除非您编写的代码必须是超级可移植的并且必须在各种旧 shell 中工作,否则请使用$()
语法而不是``
.
算术表达式
和上面一样,除非你真的需要它,否则你可以(( ))
用于算术表达式,例如
a=5
((a = a + 10)) # or even ((a += 10))
# $a is now 15
Google 和Advanced Bash-Scripting Guide以及Greg 的 Wikibash
部分都是您的朋友。