当前位置: 首页>>技术教程>>正文


bash – 如何编写一个 shell 脚本来为数字范围分配字母等级?

,

问题描述

我想制作一个脚本,提示您输入 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

最佳回答

你已经有了基本的想法。如果你想在 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

次佳回答

简洁与可读性:中间立场

正如您所看到的,这个问题承认解决方案的长度适中,有些重复但可读性强( terdon’sA.B.’s bash 答案),以及那些非常短但不直观的解决方案和 much less self-documenting(蒂姆的 pythonbash 答案和格伦杰克曼的 perl answer )。所有这些方法都是有价值的。

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

#!/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 解决方案中,我包含了一些空白行以增强可读性,但如果您希望它更短,可以删除它们。

包括空行,这实际上只比 A.B.’s bash solutiona compactified, still pretty readable variant 略短。与该方法相比,它的主要优点是:

  • 它更直观。

  • 更改成绩之间的界限(或添加其他成绩)更容易。

  • 它自动接受带有前导和尾随空格的输入(有关 (( )) 工作原理的说明,请参见下文)。

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

这个怎么运作

  1. Read input from the user. 让他们使用箭头键在他们输入的文本中移动 ( -e ),不要将 \ 解释为转义字符 ( -r )。此脚本不是 feature-rich 解决方案——请参阅下面的改进——但这些有用的功能只会使它变长两个字符。我建议始终将 -rread 一起使用,除非您知道需要让用户提供 \ 转义。

  2. 如果用户写了 qQ ,退出。

  3. 创建 associative array ( declare -A )。用与每个字母等级相关的最高数字等级填充它。

  4. Loop through 字母等级从最低到最高,检查 user-provided 数字是否低到足以落入每个字母等级的数字范围内。使用 (( )) 算术评估,变量名不需要用 $ 扩展。 (在大多数其他情况下,如果您想使用变量的值代替其名称 you must do this 。)

  5. 如果它在范围内,则打印等级和 exit 。为简洁起见,我使用短路和运算符 ( && ) 而不是 ifthen

  6. 如果循环结束并且没有匹配范围,则假设输入的数字太高(超过 100)并告诉用户它超出了范围。

这是如何表现的,带有奇怪的输入

与发布的其他简短解决方案一样,该脚本在假设输入是数字之前不会检查输入。算术评估( (( )) )会自动去除前导和尾随空格,所以这没问题,但是:

  • 看起来根本不像数字的输入被解释为 0。

  • 如果输入看起来像一个数字(即,如果它以数字开头)但包含无效字符,则脚本会发出错误。

  • 0 开头的 Multi-digit 输入是 octal 中的 interpreted as being 。例如,脚本会告诉您 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’s script checks inputif [[ ! $response =~ ^[0-9]*$ ]] ... ,所以我展示了另一种方式,它牺牲了一些简洁性但更健壮,允许用户输入前导和尾随空格并拒绝允许可能或可能不是八进制的表达式(除非它是零)。

  • 我使用 caseextended globbing 而不是 [[=~ regular expression matching 运算符(如 terdon’s answer )。我这样做是为了表明(以及如何)它也可以这样做。 Glob 和正则表达式是指定匹配文本的模式的两种方法,任何一种方法都适用于该应用程序。

  • A.B.’s bash script 一样,我已经将整个东西封闭在一个外部循环中(除了 cutoffs 数组的初始创建)。只要终端输入可用并且用户没有告诉它退出,它就会请求数字并给出相应的字母等级。从您问题中代码周围的 dodone 来看,看起来您想要这样。

  • 为了让退出变得容易,我接受 qquit 的任何不区分大小写的变体。

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

说明: continue 的使用

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

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

        *) 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 来确定数字属于哪个字母等级 bin (如 A.B.’s bash answer )。但我确实使用 case 来决定是否应考虑用户的输入:

  • 一个有效的数字,*( )@([1-9]*([0-9])|+(0))*( )

  • 退出命令,*( )[qQ]?([uU][iI][tT])*( )

  • 其他任何东西(因此输入无效),*

这些是 shell globs

  • 每个后跟一个 ) ,它与任何开头的 ( 都不匹配,这是 case 用于将模式与匹配时运行的命令分开的语法。

  • ;;case 的语法,用于指示针对特定案例匹配运行的命令结束(并且在运行它们之后不应测试后续案例)。

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

说明:扩展通配符

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

  • 零个或多个空格 ( *( ) )。 *( ) 构造匹配括号中的零个或多个模式,这里只是一个空格。实际上有两种水平空白,空格和制表符,通常也需要匹配制表符。但我在这里并不担心,因为这个脚本是为手动、交互式输入而编写的,并且 read-e 标志启用了 GNU readline。这样用户可以使用左右箭头键在文本中来回移动,但它的副作用是通常会阻止按字面意思输入选项卡。

  • 一次出现(

    @(

    )

    ) 或 (

    |

    ):

    • 一个非零数字 ( [1-9] ) 后跟零个或多个 ( *( ) ) 的任何数字 ( [0-9] )。

    • 0 中的一个或多个 (+( ))。

  • 零个或多个空格( *( ) ),再次。

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

  • 零个或多个空格 ( *( ) )。

  • qQ ( [qQ] )。

  • 可选 – 即,零次或一次出现 (

    ?(

    )

    ) – 的:

    • uU ( [uU] ) 后跟 iI ( [iI] ) 后跟 tT ( [tT] )。

  • 零个或多个空格( *( ) ),再次。

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

如果您更喜欢根据正则表达式而不是 shell glob 测试用户的输入,您可能更喜欢使用这个版本,它的工作原理相同,但使用 [[=~(如 terdon’s answer 中)而不是 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 命令。这是因为我能够设置 nocasematch shell 选项,然后自动覆盖 qquit 的所有案例变体。这就是 shopt -s nocasematch 命令的作用。 shopt -s extglob 命令被省略,因为此版本中不使用通配符。

  • 正则表达式技能比 bash 的 extglobs 的熟练程度更常见。

说明:正则表达式

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

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

  • 线的开始 – 即左边( ^ )。

  • 零个或多个( * ,应用后缀)空格。空格通常不需要在正则表达式中进行 \ 转义,但 [[ 需要这样做以防止语法错误。

  • 一个子串 (

    (

    )

    ) 那是一个

    或者

    另一个 (

    |

    ) 的:

    • [1-9][0-9]* :一个非零数字 ( [1-9] ) 后跟零个或多个 ( * ,应用后缀) 的任何数字 ( [0-9] )。

    • 0+0 的一个或多个( + ,应用后缀)。

  • 和以前一样,零个或多个空格 ( \ * )。

  • 线的末端(即右边)( $ )。

与匹配被测试的整个表达式的 case 标签不同,如果其 left-hand 表达式的任何部分与作为其 right-hand 表达式给出的模式匹配,则 =~ 返回 true。这就是为什么这里需要指定行的开头和结尾的 ^$ 锚点,并且在语法上不对应于带有 case 和 extglob 的方法中出现的任何内容。

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

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

  • 行的开头 ( ^ )。

  • 零个或多个空格( \ * ,见上面的解释)。

  • 字母 q 。或 Q ,因为 shopt nocasematch 已启用。

  • 可选 – 即,零次或一次出现(后缀

    ?

    )–子串 (

    (

    )

    ):

    • u ,然后是 i ,然后是 t 。或者,由于启用了 shopt nocasematch,因此 u 可能是 U ;独立地, i 可能是 I ;并且独立地, t 可能是 T 。 (也就是说,可能性不限于 uitUIT 。)

  • 零个或多个空格( \ * )。

  • 行尾 ( $ )。

第三种回答

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

第四种回答

所有 Ubuntu 安装都有 Python,所以这里有一个 Python 脚本。如果你需要它在 bash 中,我也有 written the equivalent as a shell script

print (chr(75-max(5,int('0'+raw_input('Enter the number: ')[:-1]))))

要运行,请将其保存在文件中(例如 grade.py ),然后在终端中运行它:

python grade.py

这是您将看到的:

Enter the number: 65
E

这是如何运作的?

  1. 输入 – 65

  2. 在开头添加 0 – 065

  3. 删除最后一个字符 – 06

  4. 75 减去那个数字 – 70

  5. 转换为字母(A 为 65,B 为 66)- E

  6. 打印出来 – E

我的代词是他/他

第五种回答

这是我的 semi-esoteric bash 解决方案,它填充一个包含 101 个条目的数组,然后根据它们检查用户输入。即使对于 real-world 使用也是合理的——如果您需要出色的性能,您就不会使用 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

好处:

  • 这并不是那么深奥。虽然它比最短的解决方案更长,而且不像更长的解决方案那样 self-documenting……它是合理的 self-documenting,但仍然明显偏小。

  • 它允许轻松修改以更改成绩范围或添加/删除成绩。

  • 它在循环中运行并退出 qquit 或以 q /Q 开头的任何内容。

  • 首先列出较高的成绩,以帮助您积极思考。 🙂

  • 嗯,这可以完成工作,即使在您查看之后仍然有意义,并且具有基本功能。你真的可以用这个!

缺点:

  • 当您输入非数字输入时,它会给您一个 F ……但这并不是那么糟糕,是吗?如果你在需要数字的地方给出一个非数字,也许你应该得到一个 F!

  • 模棱两可的,可能是八进制输入被视为八进制(因为 gone-dimensional indexed array )。正如那句老话所说,“这不是一个错误,这是一个功能!”也许。

  • 超出范围或不是数字的输入会导致打印空行。不过,这并没有什么真正的问题:它会告诉您输入对应的字母等级,而对于错误的输入,则没有。

  • 输入一个负数,它……好吧,称之为 easter egg

  • 仍然比 Tim’s python solution 长得多。是的,我真的不能把它变成一个优势。

有点酷,对吧? (嗯,我想是的。)

这个怎么运作

  1. p 函数填充一个数字索引数组 g,索引范围从其第一个参数到第二个参数,(字母)值在其第三个参数中给出。

  2. 为每个字母等级调用 p,以定义其数值范围。

  3. 只要用户输入可用且不以 q (或 Q )开头,请继续阅读用户输入,检查 g 数组中哪个字母等级对应于输入的数字,然后打印该字母。

第六种回答

making it in Python 2 之后,我决定用 bash 制作它。

#! /bin/bash

read -p "Enter the number: " i
i=0$i
x=$((10#${i::-1}))
printf "\x$(printf %x $((11-($x>5?$x:5)+64)))\n"

要运行,请将其保存在文件中(例如grade.sh),使用 chmod +x grade.sh 使其可执行,然后使用 ./grade.sh 运行。

这是您将看到的:

Enter the number: 65
E

这是如何运作的?

  1. 输入 – 65

  2. 在开头添加一个 0 – 065 (并且 10# 保持它以 10 为底)。

  3. 删除最后一个字符 – 06

  4. 75 减去那个数字 – 70

  5. 转换为字母(A 为 65,B 为 66)- E

  6. 打印出来 – E

我的代词是他/他

第七种回答

这是我的 awk 版本:

awk '{
  if($_ <= 100 && $_ >= 0) {
      sub(/^([0-9]|[1-5][0-9])$/, "F", $_);
      sub(/^(6[0-9])$/, "D", $_);
      sub(/^(7[0-9])$/, "C", $_);
      sub(/^(8[0-9])$/, "B", $_);
      sub(/^(9[0-9]|100)$/, "A", $_);
      print
    }
    else {
      print "Only numbers between 0..100"
    }
}' -

或作为 one-liner:

awk '{if($_ <= 100 && $_ >= 0) { sub(/^([0-9]|[1-5][0-9])$/, "F", $_); sub(/^(6[0-9])$/, "D", $_); sub(/^(7[0-9])$/, "C", $_); sub(/^(8[0-9])$/, "B", $_);sub(/^(9[0-9]|100)$/, "A", $_);   print} else { print "Only numbers between 0..100"}}' -

第八种回答

这是另一个 “esoteric” 答案

perl -E '
    print "number: "; 
    $n = <>; 
    say qw/A A B C D E F F F F F/[11-($n+1)/10]
       if $n=~/^\s*\d/ and 0<=$n and $n<=100
'

Explanation

  • perl -E-E-e 一样,允许将脚本作为命令行参数传递。这是一种运行 perl one-liners 的方法。与 -e 不同,-E 还启用所有可选功能(例如 say ,它基本上是一个带有尾随换行符的 print。)。

  • print "number: "; :提示用户输入数字。

  • $n = <>; :将该数字保存为 $n

下一点需要分解一下。 qw/string/ 评估为通过在空白处断开 string 生成的列表。所以,qw/A A B C D E F F F F F/ 实际上就是这个列表:

0 : A
1 : A
2 : B
3 : C
4 : D
5 : E
6 : F
7 : F
8 : F
9 : F
10 : F

因此,say qw/A A B C D E F F F F F/[11-($n+1)/10] 等价于

my @F=("A","A","B","C","D","E","F","F","F","F","F");
print "$F[11-($n+1)/10]\n"

现在,Perl 允许使用负索引来检索从数组末尾开始计数的元素。例如,$arrray[-1] 将打印数组的最后一个元素。此外,浮点数组索引(例如 10.7)会自动截断为下一个较小的整数(10.7 或 10.3 或任何都变为 10 的整数。)

所有这一切的结果是索引 11-($n+1)/10 始终评估为数组的适当元素(等级)。

参考资料

本文由Ubuntu问答整理, 博文地址: https://ubuntuqa.com/article/12712.html,未经允许,请勿转载。