如何编写Shell脚本以将字母等级分配给数字范围?


19

我想制作一个脚本,提示您输入0-100之间的数字,然后根据该数字给您评分。

我想要它。

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

Answers:


20

简洁与可读性:中间立场

如您所见,此问题允许使用长度适中,有些重复但可读性强的解决方案(TerdonAB的 bash答案),以及解决方案很短但不直观且自我记录少的解决方案(Tim的python)bash答案以及glenn jackman的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)。
    该脚本不是功能丰富的解决方案-请参见下文进行细化-但这些有用的功能只会使其长度增加两个字符。我建议始终-r与一起使用read,除非您知道需要让用户供应\转义。
  2. 如果用户写了qQ,请退出。
  3. 创建一个关联 数组declare -A)。用与每个字母等级相关的最高数字等级填充它。
  4. 从最低到最高循环遍历字母等级,检查用户提供的数字是否足够低以落入每个字母等级的数字范围。
    通过(( ))算术评估,不需要使用扩展变量名$。(在大多数其他情况下,如果要使用变量的值代替其名称,则必须执行此操作。)
  5. 如果它落在范围内,则打印等级并退出
    为简便起见,我使用短路运算符(&&)而不是if- then
  6. 如果循环结束且没有范围匹配,则假定输入的数字太大(超过100),并告诉用户它超出范围。

奇怪的输入方式

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

  • 看起来完全不像数字的输入被解释为0。
  • 输入看起来像数字(即,如果以数字开头)但包含无效字符,则脚本会发出错误。
  • 多位数输入开始0解释为八进制。例如,脚本将告诉您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扩展的globing,而不是[[使用了=~ 正则表达式匹配运算符(如terdon的answer)。我这样做是为了表明(以及如何)也可以这样做。globs和regexps是指定与文本匹配的模式的两种方法,对于该应用程序,这两种方法都适用。
  • AB的bash脚本一样,我将整个内容封装在一个外部循环中(除了cutoffs数组的初始创建)。只要终端输入可用并且用户没有告诉它退出,它就会请求数字并给出相应的字母等级。根据问题代码中的do... 判断done,看起来就像您想要的那样。
  • 为了使戒烟容易,我接受任何不区分大小写的变种qquit

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

说明:的使用 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 2bash查找并继续执行第二个循环。

说明:case带有标签的标签

我不使用case找出哪些字母等级一批落入(如AB的bash的答案)。但我确实case决定是否应考虑用户的输入:

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

这些是贝壳球

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

普通shell *匹配提供了零个或多个字符的?匹配,一个字符的精确匹配以及[ ]括号中的字符类/范围。但是我正在使用扩展的globlob,这还不止于此bash交互式使用时,默认情况下会启用扩展的glob ,但是运行脚本时,默认情况下会禁用扩展的glob 。shopt -s extglob脚本顶部的命令将其打开。

说明:扩展globbing

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

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

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

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

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

如果您希望针对正则表达式而不是Shell Glob测试用户的输入,则您可能更喜欢使用此版本,该版本的工作原理相同,但是使用[[=~(例如,在Terdon的答案中)代替case和扩展的Glob。

#!/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的extglob中,正则表达技能比熟练程度更普遍。

说明:正则表达式

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

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

  • 行(^)的起点,即左边缘。
  • 零个或多个(*,应用后缀)空格。通常不需要\在正则表达式中转义空格,但这是[[防止语法错误所必需的。
  • 一个子串(( )),它是以下其中一个另一个(|):
    • [1-9][0-9]*:一个非零数字([1-9]),后跟*任意一个数字()的零个或多个(,应用后缀[0-9])。
    • 0+:的一个或多个(+应用后缀)0
  • \ *与以前一样,零个或多个空格()。
  • 行($)的末端,即右边缘。

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

需要括号,使^$结合的脱节[1-9][0-9]*0+。否则会使的析取^[1-9][0-9]*0+$,并匹配任何输入开始以非零的数字与结束0(或两者,这可能仍然包括之间的非数字)。

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

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

3
告诉我真相..花了多少时间?;)
heemayl 2015年

4
@heemayl我不太确定,因为我一整天都写了很多小篇幅(随后在发布之前进行了完整的通读和编辑)。我很确定,如果我一直在考虑要花多长时间,最终花费的时间会比我刚开始时想的要长。:)
Eliah Kagan 2015年

6
写的越来越多,我需要一本书来回答你。
Grijesh Chauhan

TL; DR但还是让我发笑!
Fabby

从标题到解释的一切都很好。我一开始就了解它,但是我再次读了它,只是因为我想
Sumeet Deshmukh

23

您已经有了基本的想法。如果要使用此代码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

4
这些一束-ge测试可以消除,据推测,由于您使用elif。没有爱(( $response < X ))吗?
muru,2015年

2
@muru是的,谢谢。我一直在思考数字范围,但没有理由。至于(( $response < X )),当然,但是我发现它更清晰了,OP显然是bash脚本的新成员。
terdon'5

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

1
这些是字符范围吗?即[0-59]表示0、1、2、3、4、5或9中的任何字符,依此类推。我看不到它如何适用于数值
steeldriver

3
您不必一直都是FGITW。花些时间,写出好的答案。看看terdon或Eliah Kagan的工作方式。
muru,2015年

@AB 我注意到该解决方案可以缩短,主要是通过样式更改,同时仍然保持可读性。简洁很少是最重要的考虑因素,因此我认为您应该以这种方式改变自己拥有的东西。但是,由于更紧凑的形式不会占用太多空间,因此如果有些读者想要一个较短的脚本以相同的方式工作,则可以考虑将其显示出来。(如果您愿意,请随时使用我的简化版本或对其进行任何更改。)
Eliah Kagan 2015年

9

Ubuntu的所有安装都有Python,因此这里是一个python 脚本。如果您需要将它放在bash中,我也将等效代码编写为shell脚本

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

我的代词是他/他


我喜欢你的主意 +1
AB

不要使用input(),它会调用eval(),而是使用.. raw_input()90+Bchr(74 - max(4, num))
而且

well..your的解决方案是好的,在python2将工作also..just变化input()raw_input()进行python2..thats它..
heemayl

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

然后,您也需要更改原始代码。.您目前所修改的代码是错误的,尽管python3没有raw_input()。.我建议raw_input()您使用第一个初始代码,因为您告诉过要使用python2..
heemayl,2015年

6

这是我的深奥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函数p opulates一个数字索引阵列g拉迪斯,在索引范围从它的第一个参数的第二,在其第三个参数给出的(字母)值。
  2. p 每个字母等级都会调用,以定义其数字范围。
  3. 一直在阅读用户输入,只要它可用且不以q(或Q)开头,请检查g字母等级与输入数字相对应的数组,然后打印该字母。

这个条件呢?[[ $n =~ ^(0|[1-9]+[0-9]*)$ ]]
Helio 2015年

6

在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

我的代词是他/他


非常聪明,做得很好
kos,2015年

@kos谢谢:)我怀疑它将对OP有效,因为他的范围可能不是他发布的范围。我希望这些只是为了简单。
蒂姆(Tim)2015年

5

这是我的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"
    }
}' -

或单线:

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"}}' -

4

这是另一个“神秘的”答案

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
'

说明

  • perl -E:和-E一样-e,允许将脚本作为命令行参数传递。这是运行perl单行代码的一种方法。与不同-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总是求值到数组的适当元素(等级)。


4
神秘的答案都不错,但请附上解释。
muru 2015年

1

尽管您要求使用bash解决方案,但我认为在python中,这可以通过简洁的方式完成。涵盖了在输入错误的情况下处理错误以及将0到100之间的数字“转换”为A到F(或其他任何字母)的情况:

#!/usr/bin/env python3
try:
    n = int(input("number: ")); n = n if n>0 else ""
    print("FEDCBA"[[n>=f for f in [50,60,70,80,90,101]].count(True)])
except:
    print("invalid input")

说明

  1. 首先,我们需要从用户那里获取电话号码:

    n = int(input("number: "))
  2. 我们测试此号码在多种情况下是否有效:

    n>=50, n>=60, n>=70, n>=80, n>=90

    对于这些测试中的每一个,结果将为FalseTrue。因此(稍微压缩一下代码):

    [n>=f for f in [50,60,70,80,90]].count(True)]

    将产生一个从05

  3. 随后,我们可以将此图用作字符串的索引,以产生一个字符作为输出,例如

    "ABCDEF"[3] 

    将输出“ D”(因为第一个字符=“ A”)

  4. 101列表的附加功能是在数量超出时生成(Index-)错误100,因为该数量"ABCDEF"[6]不存在。同样适用于n = n if n>=0 else "",如果输入的数字小于0,则将产生(Value-)错误。
    在这些情况下,以及如果输入的不是数字,结果将是:

    invalid input

测试:

number: 10
F

number: 50
E

number: 60
D

number: 70
C

number: 80
B

number: 90
A

number: 110
invalid input

number: -10
invalid input

number: Monkey
invalid input
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.