简洁与易读:中间立场

简洁与易读:中间立场

我想编写一个脚本,提示您输入一个 0-100 之间的数字,然后根据该数字为您提供等级。

我希望在 bash 中使用它。

PS3='Please enter your choice: '
(Something here)

do
case $
    "0-59")
        echo "F"
        ;;
    "60-69")
        echo "D"
        ;;
    "70-79")
        echo "C"
        ;;
    "Quit")
        break
        ;;
    *) echo invalid option;;
esac
done

答案1

您已经有了基本的想法。如果您想编写代码bash(这是一个合理的选择,因为它是 Ubuntu 和大多数其他 Linux 上的默认 shell),您不能使用它,case因为它不理解范围。相反,您可以使用if/ else

#!/usr/bin/env bash

read -p "Please enter your choice: " response

## If the response given did not consist entirely of digits
if [[ ! $response =~ ^[0-9]*$ ]]
then
    ## If it was Quit or quit, exit
    [[ $response =~ [Qq]uit ]] && exit
    ## If it wasn't quit or Quit but wasn't a number either,
    ## print an error message and quit.
    echo "Please enter a number between 0 and 100 or \"quit\" to exit" && exit
fi
## Process the other choices
if [ $response -le 59 ]
then
    echo "F"
elif [ $response -le 69 ]
then
    echo "D"
elif  [ $response -le 79 ]
then
    echo "C"
elif  [ $response -le 89 ]
then
    echo "B"
elif [ $response -le 100 ]
then
    echo "A"
elif [ $response -gt 100 ]
then
    echo "Please enter a number between 0 and 100"
     exit
fi

答案2

简洁与易读:中间立场

正如你所见,这个问题的解决方案比较长,有些重复,但可读性很高(特登AB 的bash 答案),以及那些非常简短但不直观的答案,更少的自我记录(蒂姆的Python狂欢答案和格伦·杰克曼的perl 答案)这些方法都是很有价值的。

您还可以使用介于紧凑性和可读性之间的代码来解决这个问题。这种方法的可读性几乎与较长的解决方案一样好,但长度更接近小型、深奥的解决方案。

#!/usr/bin/env bash

read -erp 'Enter numeric grade (q to quit): '
case $REPLY in [qQ]) exit;; esac

declare -A cutoffs
cutoffs[F]=59 cutoffs[D]=69 cutoffs[C]=79 cutoffs[B]=89 cutoffs[A]=100

for letter in F D C B A; do
    ((REPLY <= cutoffs[$letter])) && { echo $letter; exit; }
done
echo "Grade out of range."

在这个 bash 解决方案中,我添加了一些空行来增强可读性,但是如果您希望它更短,可以删除它们。

包括空白行,这实际上只比紧凑但可读性较好的版本AB 的 bash 解决方案. 与该方法相比,它的主要优点是:

  • 它更加直观。
  • 更改等级之间的界限(或添加额外等级)更加容易。
  • 它自动接受带有前导和尾随空格的输入(请参阅下文了解其(( ))工作原理)。

所有这三个优点的出现是因为该方法使用用户的输入作为数字数据而不是手动检查其组成数字。

怎么运行的

  1. 读取用户的输入。让他们使用箭头键在输入的文本中移动(-e)并且不将其解释\为转义字符(-r)。
    此脚本不是功能丰富的解决方案——请参阅下面的改进——但这些有用的功能只会使其长度增加两个字符。我建议始终使用-rread除非您知道需要让用户提供\转义符。
  2. 如果用户写了qQ,则退出。
  3. 创建一个联想 大批declare -A)。用与每个字母等级相关的最高数字等级填充它。
  4. 依次通过字母等级从低到高,检查用户提供的数字是否足够低,以落入每个字母等级的数字范围。
    使用(( ))算术评估,变量名称不需要用 扩展$。(在大多数其他情况下,如果您想使用变量的值代替其名称,你必须这样做
  5. 如果在范围内,则打印成绩并出口
    为了简洁起见,我使用短路运算符 ( &&) 而不是if- then
  6. 如果循环结束并且没有匹配的范围,则假定输入的数字太高(超过 100)并告诉用户超出范围。

如何处理奇怪的输入

就像其他短的解决方案发布后,该脚本在假设输入为数字之前不会检查输入。算术评估 ( (( ))) 会自动删除前导和尾随空格,因此那是没问题,但是:

  • 根本不像数字的输入将被解释为 0。
  • 如果输入看起来像数字(即,如果它以数字开头)但包含无效字符,则脚本会发出错误。
  • 0输入以is开头的多位数字解释为八进制。例如,脚本会告诉您 77 是 C,而 077 是 D。虽然有些用户可能想要这样,但大多数用户可能不想要,这可能会造成混淆。
  • 好的一面是,当给出算术表达式时,此脚本会自动简化它并确定相关的字母等级。例如,它会告诉你 320/4 是 B。

功能齐全的扩展版本

出于这些原因,您可能需要使用类似这个扩展脚本的东西,它会检查以确保输入良好,并包含一些其他增强功能。

#!/usr/bin/env bash
shopt -s extglob

declare -A cutoffs
cutoffs[F]=59 cutoffs[D]=69 cutoffs[C]=79 cutoffs[B]=89 cutoffs[A]=100

while read -erp 'Enter numeric grade (q to quit): '; do
    case $REPLY in  # allow leading/trailing spaces, but not octal (e.g. "03") 
        *( )@([1-9]*([0-9])|+(0))*( )) ;;
        *( )[qQ]?([uU][iI][tT])*( )) exit;;
        *) echo "I don't understand that number."; continue;;
    esac

    for letter in F D C B A; do
        ((REPLY <= cutoffs[$letter])) && { echo $letter; continue 2; }
    done
    echo "Grade out of range."
done

这仍然是一个非常紧凑的解决方案。

这增加了什么功能?

这个扩展脚本的重点是:

  • 输入验证。terdon 的脚本检查输入,所以我展示了另一种方法,它牺牲了一些简洁性但更为强大,允许用户输入前导和尾随空格,并且拒绝允许可能或可能不是八进制的表达式(除非它是零)。if [[ ! $response =~ ^[0-9]*$ ]] ...
  • 我用过case扩展通配符代替[[=~ 正则表达式匹配运算符(如terdon 的回答)。我这样做是为了展示(以及如何)也可以通过这种方式完成。通配符和正则表达式是指定与文本匹配的模式的两种方式,这两种方法都适用于此应用程序。
  • 喜欢AB 的 bash 脚本,我将整个过程放在一个外循环中(除了数组的初始创建cutoffs)。只要终端输入可用并且用户没有告诉它退出,它就会请求数字并给出相应的字母等级。从问题中代码周围的do...来看done,看起来你想要这个。
  • 为了方便退出,我接受任何不区分大小写的q或变体quit

该脚本使用了一些新手可能不熟悉的结构;下面详细介绍。

解释:使用continue

当我想跳过外循环的其余部分时while,我会使用continue命令。这会将其带回到循环顶部,以读取更多输入并运行另一次迭代。

我第一次这样做时,我所处的唯一循环是外while循环,因此我可以continue不带参数地调用。 (我在case构造中,但这不会影响break或的操作continue。)

        *) echo "I don't understand that number."; continue;;

然而,第二次,我处于一个for嵌套在外while循环中的内循环中。如果我continue不使用参数,这将相当于continue 1继续内for循环而不是外while循环。

        ((REPLY <= cutoffs[$letter])) && { echo $letter; continue 2; }

因此在这种情况下,我使用continue 2让 bash 查找并继续第二个循环。

解释:case带有 Glob 的标签

我并不习惯case弄清楚哪个字母等级垃圾桶一个数字落入(如AB 的 bash 回答)。但我确实使用它case来决定是否应该考虑用户的输入:

  • 有效数字,*( )@([1-9]*([0-9])|+(0))*( )
  • 退出命令,*( )[qQ]?([uU][iI][tT])*( )
  • 其他内容(因此输入无效),*

这些都是外壳全局变量

  • 每个后面都跟有一个)不与任何开头匹配的(,这是case将模式与匹配时运行的命令分开的语法。
  • ;;case用于指示针对特定案例匹配运行的命令结束的语法(并且在运行它们之后不应测试任何后续案例)。

普通的 shell 通配符*可以匹配零个或多个字符,?也可以匹配一个字符,还可以匹配[ ]括号中的字符类/范围。但我使用的是扩展通配符,这超出了这一点。在交互使用时,扩展通配默认启用bash,但在运行脚本时默认禁用。shopt -s extglob脚本顶部的命令将其打开。

解释:扩展通配符

*( )@([1-9]*([0-9])|+(0))*( ),检查数字输入,匹配以下序列:

  • 零个或多个空格 ( *( ))。该*( )构造匹配括号中的零个或多个模式,这里只是一个空格。
    实际上有两种水平空白,空格和制表符,通常也希望匹配制表符。但我在这里并不担心这一点,因为这个脚本是为手动、交互式输入而编写的,并且标志-e启用read了 GNU readline。这样用户就可以使用左右箭头键在文本中来回移动,但它的副作用是通常会阻止按字面输入制表符。
  • 下列任一 ( @( )) 出现一次 ( |):
    • 一个非零数字 ( [1-9]) 后跟零个或多个 ( *( )) 任意数字 ( [0-9])。
    • 一个或多个(+( )0
  • 再次,零个或多个空格(*( ))。

*( )[qQ]?([uU][iI][tT])*( ),检查退出命令,匹配以下序列:

  • 零个或多个空格 ( *( ))。
  • q或者Q[qQ])。
  • 可选地——即,零次或一次出现(?( ))——:
    • uU( [uU]) 后跟iI( [iI]) 后跟tT( [tT])。
  • 再次,零个或多个空格(*( ))。

变体:使用扩展正则表达式验证输入

如果你更喜欢用正则表达式而不是 shell glob 来测试用户的输入,那么你可能更喜欢使用这个版本,它的工作原理相同,但使用了[[=~(就像在terdon 的回答) 来代替case并扩展通配符。

#!/usr/bin/env bash
shopt -s nocasematch

declare -A cutoffs
cutoffs[F]=59 cutoffs[D]=69 cutoffs[C]=79 cutoffs[B]=89 cutoffs[A]=100

while read -erp 'Enter numeric grade (q to quit): '; do
    # allow leading/trailing spaces, but not octal (e.g., "03")
    if [[ ! $REPLY =~ ^\ *([1-9][0-9]*|0+)\ *$ ]]; then
        [[ $REPLY =~ ^\ *q(uit)?\ *$ ]] && exit
        echo "I don't understand that number."; continue
    fi

    for letter in F D C B A; do
        ((REPLY <= cutoffs[$letter])) && { echo $letter; continue 2; }
    done
    echo "Grade out of range."
done

这种方法可能具有以下优点:

  • 在这个特定情况下,语法稍微简单一些,至少在第二个模式中,我检查 quit 命令。这是因为我能够设置nocasematchshell 选项,然后自动覆盖q和的所有大小写变体。quit

    这就是命令的作用。由于此版本不使用通配符,因此省略了shopt -s nocasematch该命令。shopt -s extglob

  • 正则表达式技能比熟练掌握 bash 的 extglobs 更为常见。

解释:正则表达式

至于运算符右侧指定的模式=~,这些正则表达式的工作方式如下。

^\ *([1-9][0-9]*|0+)\ *$,检查数字输入,匹配以下序列:

  • 线的开始(即左边缘) ( ^)。
  • 零个或多个( ,应用后缀)空格。正则表达式中*通常不需要对空格进行转义,但需要这样做以防止出现语法错误。\[[
  • 子字符串 ( ( )) 为或者以下项中的另一项 ( |):
    • [1-9][0-9]*:一个非零数字 ( [1-9]) 后跟零个或多个(*,应用后缀)任意数字 ( [0-9])。
    • 0+:一个或多个(+,应用后缀)的0
  • 零个或多个空格 ( \ *),与前面相同。
  • 线的末端(即右边缘) ( $)。

与标签不同case,标签会匹配正在测试的整个表达式,=~如果其左侧表达式的任何部分与其右侧表达式给出的模式匹配,则返回 true。这就是为什么这里需要^$锚点(指定行的开始和结束),并且与方法中出现的带有和 extglobs 的任何内容在语法上不对应case

需要使用括号来创建^和绑定到和$的析取。否则它将是和的析取,并匹配以非零数字开头的任何输入[1-9][0-9]*0+^[1-9][0-9]*0+$或者以 a 结尾0(或两者,其间可能仍包含非数字)。

^\ *q(uit)?\ *$,检查退出命令,匹配以下序列:

  • 行的开头 ( ^)。
  • 零个或多个空格(\ *,参见上面的解释)。
  • 字母q. 或Q,因为shopt nocasematch已启用。
  • ?可选地,即子字符串 ( )出现零次或一次(后缀) ( )
    • u,然后是i,然后是t。或者,由于shopt nocasematch被启用u可能是U;独立地,i可能是I;并且独立地,t可能是T。(也就是说,可能性是不是限于uitUIT。)
  • 零个或多个空格再次 ( \ *)。
  • 行的结尾 ( $)。

答案3

#!/bin/bash

while true
do
  read -p "Please enter your choice: " choice

  case "$choice"
   in
      [0-9]|[1-5][0-9])
          echo "F"
          ;;
      6[0-9])
          echo "D"
          ;;
      7[0-9])
          echo "C"
          ;;
      8[0-9])
          echo "B"
          ;;
      9[0-9]|100)
          echo "A"
          ;;
      [Qq])
          exit 0
          ;;
      *) echo "Only numbers between 0..100, q for quit"
          ;;
  esac
done

以及更紧凑的版本(Thx@EliahKagan):

#!/usr/bin/env bash

while read -erp 'Enter numeric grade (q to quit): '; do
    case $REPLY in
        [0-9]|[1-5][0-9])   echo F ;;
        6[0-9])             echo D ;;
        7[0-9])             echo C ;;
        8[0-9])             echo B ;;
        9[0-9]|100)         echo A ;;

        [Qq])               exit ;;
        *)                  echo 'Only numbers between 0..100, q for quit' ;;
    esac
done

答案4

这是我的-esoteric bash 解决方案,它用 101 个条目填充一个数组,然后根据这些条目检查用户输入。即使对于实际使用来说,这也是合理的——如果您需要出色的性能,您就不会使用 bash,一百(左右)个分配仍然很快。但如果扩展到更大的范围(比如一百万),它就不再合理了。

#!/usr/bin/env bash
p(){ for i in `seq $2 $3`; do g[$i]=$1; done; }
p A 90 100; p B 80 89; p C 70 79; p D 60 69; p F 0 59
while read -r n && [[ ! $n =~ ^[qQ] ]]; do echo ${g[$n]}; done

优点:

  • 其实这并不是那么深奥。虽然它比最短的解决方案要长,并且不像较长的解决方案那样具有自我文档化......它相当具有自我记录性,但仍然明显偏小。
  • 它允许轻松修改以更改等级范围或添加/删除等级。
  • 它循环运行并退出qquit或任何以q/开头的内容Q
  • 首先列出较高的等级,以帮助您积极思考。:)
  • 嗯,它确实能完成工作,看了之后仍然有意义,并且具有基本功能。您确实可以使用它!

缺点:

  • 当你输入非数字时,它会给你一个 F... 但这不是很糟糕,不是吗?如果你在需要数字的地方输入了非数字,也许你应该得到一个 F!
  • 含糊不清,可能是八进制的输入被视为八进制(因为g一维索引数组)正如那句老话所说,“这不是一个缺陷,而是一个特性!”好吧,也许吧。
  • 输入超出范围或不是数字会导致打印空行。但这并没有什么错:它会告诉您输入对应的字母等级,而对于错误输入则没有等级。
  • 输入一个负数,它...好吧,称之为复活节彩蛋
  • 仍然明显长于Tim 的 Python 解决方案。是的,我真的不能认为这看起来像是一个优势。

很酷吧?(嗯,我是这么认为。)

怎么运行的

  1. 功能p填充一个数字索引g数组Grades,索引范围从其第一个参数到其第二个参数,其第三个参数中给出(字母)值。
  2. p对每个字母等级进行调用,以定义其数字范围。
  3. 只要用户输入可用并且不以q(或Q)开头,就继续读取用户输入,检查g哪个字母等级与输入的数字相对应的数组,并打印该字母。

相关内容