网站搜索

通过编写“猜数字”游戏来学习 awk


编程语言往往有许多共同的特征。学习一门新语言的一个好方法是创建一个熟悉的程序。在本文中,我将使用 awk 创建一个“猜数字”游戏来演示熟悉的概念。

当您学习一门新的编程语言时,最好关注大多数编程语言的共同点:

  • 变量——存储信息的地方
  • 表达式——计算事物的方法
  • 语句——在程序中表达状态变化的方式

这些概念是大多数编程语言的基础。

一旦你理解了这些概念,你就可以开始弄清楚剩下的事情了。例如,大多数语言都有一种由其设计支持的“做事方式”,并且这些方式在一个程序与另一个程序之间可能有很大不同。这些方式包括模块化(将相关功能分组在一起)、声明式与命令式、面向对象、低级与高级语法特征等等。许多程序员都熟悉的一个例子是“仪式”,即在解决问题之前设置场景所需的工作量。据说 Java 编程语言有一个重要的仪式要求,这源于它的设计,它要求所有代码都在类中定义。

但回到基础知识。编程语言通常有相似之处。一旦了解了一种编程语言,就开始学习另一种编程语言的基础知识,以了解该新语言的差异。

一个好的方法是创建一组基本测试程序。有了这些,学习就从这些相似之处开始。

您可以使用的一个测试程序是“猜数字”程序。计算机会选择一个 1 到 100 之间的数字,并要求您猜测该数字。程序会一直循环,直到您做出正确的猜测。

“猜数字”程序运用了编程语言中的几个概念:

  • 变量
  • 输入
  • 输出
  • 有条件评价
  • 循环

这是学习新编程语言的一个很好的实践实验。

注意:本文改编自 Moshe Zadka 关于在 Julia 中使用此方法的文章和 Jim Hall 关于在 Bash 中使用此方法的文章。

猜猜awk中的数字

让我们编写一个“猜数字”游戏作为 Awk 程序。

awk 是动态类型的,是一种面向数据转换的脚本语言,并且对交互式使用有着令人惊讶的良好支持。 Awk 自 20 世纪 70 年代以来就已存在,最初是作为 Unix 操作系统的一部分。如果您不了解 Awk 但喜欢电子表格,这是一个标志……去学习 Awk!

您可以通过编写“猜数字”游戏的版本来开始探索 Awk。

这是我的实现(带有行号,以便我们可以查看一些特定功能):

     1    BEGIN {
     2        srand(42)
     3        randomNumber = int(rand() * 100) + 1
     4        print "random number is",randomNumber
     5        printf "guess a number between 1 and 100\n"
     6    }
     7    {
     8        guess = int($0)
     9        if (guess < randomNumber) {
    10            printf "too low, try again:"
    11        } else if (guess > randomNumber) {
    12            printf "too high, try again:"
    13        } else {
    14            printf "that's right\n"
    15            exit
    16        }
    17    }

我们可以立即看到 Awk 控制结构与 C 或 Java 的控制结构之间的相似之处,但与 Python 不同。在 if-then-else 或 while 等语句中,then、else 和 while 部分采用 { 和 } 内的一条语句或一组语句。然而,AWk 有一个很大的区别需要从一开始就理解:

按照设计,Awk 是围绕数据管道构建的。

这意味着什么?大多数 Awk 程序都是接收一行输入、对数据执行某些操作并将其写入输出的代码片段。认识到对这种转换管道的需求,Awk 默认提供所有转换管道。让我们通过上面的程序来探索这个问题,提出一个基本问题:“从控制台读取数据”结构在哪里?

答案是——它是内置的。特别是,第 7 – 17 行告诉 Awk 如何处理读取的每一行。考虑到该上下文,很容易看出第 1 – 6 行是在读取任何内容之前执行的。

更具体地说,第 1 行的 BEGIN 关键字是一种“模式”,在本例中向 Awk 指示,在读取任何数据之前,它应该执行 BEGIN 之后的内容> 在 { ... } 中。一个类似的 END 关键字(未在此程序中使用)指示 Awk 在读取所有内容后要执行的操作。

回到第 7 – 17 行,我们看到他们创建了一个类似的代码块 { … },但前面没有关键字。由于 { 之前没有任何内容可供 Awk 匹配,因此它会将这一行应用于收到的每一行输入。每行输入都将由用户猜测输入。

让我们看看正在执行的代码。首先,在读取任何输入之前发生的前导码。

在第 2 行中,我们用数字 42 初始化随机数生成器(如果我们不提供参数,则使用系统时钟)。 42?当然是 42。第 3 行计算 1 到 100 之间的随机数,第 4 行打印该数字以用于调试目的。第 5 行邀请用户猜测一个数字。请注意,此行使用 printf,而不是 print。与 C 一样,printf 的第一个参数是用于格式化输出的模板。

现在用户知道程序需要输入,她可以在控制台上输入猜测。如前所述,awk 将这个猜测提供给第 7 – 17 行的代码。第18行将输入记录转换为整数; $0 表示整个输入记录,而 $1 表示输入记录的第一个字段,$2 第二个,依此类推。是的,Awk 使用预定义的分隔符(默认为空格)将输入行拆分为组成字段。第 9 – 15 行将猜测与随机数进行比较,打印适当的响应。如果猜测正确,第 15 行会提前从输入行处理管道中退出。

简单的!

鉴于 Awk 程序作为代码片段的不寻常结构,对特定输入行配置做出反应并处理数据,让我们看一下替代结构,看看过滤部分是如何工作的:

     1    BEGIN {
     2        srand(42)
     3        randomNumber = int(rand() * 100) + 1
     4        print "random number is",randomNumber
     5        printf "guess a number between 1 and 100\n"
     6    }
     7    int($0) < randomNumber {
     8        printf "too low, try again: "
     9    }
    10    int($0) > randomNumber {
    11        printf "too high, try again: "
    12    }
    13    int($0) == randomNumber {
    14        printf "that's right\n"
    15        exit
    16    }

第 1 – 6 行没有改变。但是现在我们看到第7-9行是当该行的整数值小于随机数时执行的代码,第10-12行是当该行的整数值大于随机数时执行的代码,第 13 – 16 行是两者匹配时执行的代码。

这应该看起来“很酷但很奇怪”——例如,为什么我们要重复计算 int($0) ?可以肯定的是,这将是一种奇怪的解决问题的方法。但这些模式确实是分离条件处理的绝佳方式,因为它们可以使用正则表达式或 Awk 支持的任何其他结构。

为了完整起见,我们可以使用这些模式将常见计算与仅适用于特定情况的计算分开。这是第三个版本来说明:

     1    BEGIN {
     2        srand(42)
     3        randomNumber = int(rand() * 100) + 1
     4        print "random number is",randomNumber
     5        printf "guess a number between 1 and 100\n"
     6    }
     7    {
     8        guess = int($0)
     9    }
    10    guess < randomNumber {
    11        printf "too low, try again: "
    12    }
    13    guess > randomNumber {
    14        printf "too high, try again: "
    15    }
    16    guess == randomNumber {
    17        printf "that's right\n"
    18        exit
    19    }

认识到无论输入什么值,都需要将其转换为整数,因此我们创建了第 7 – 9 行来执行此操作。现在,10 – 12、13 – 15 和 16 – 19 这三组行引用已经定义的变量猜测,而不是每次都转换输入行。

让我们回到我们想要学习的内容列表:

  • 变量 – 是的,Awk 有那些;我们可以推断输入数据以字符串形式出现,但可以在需要时转换为数值
  • 输入 – Awk 只是通过其“数据转换管道”方法发送输入来读取内容
  • 输出 – 我们使用了 Awk 的 print 和 printf 过程来写入输出内容
  • 条件评估 – 我们了解了 Awk 的 if-then-else 和响应特定输入行配置的输入过滤器
  • 循环——哈,想象一下!再次感谢 Awk 采用的“数据转换管道”方法,我们在这里不再需要循环;循环“就这样发生了”。请注意,用户可以通过向 Awk 发送文件结束信号(使用 Linux 终端窗口时为 CTRL-D)来提前退出管道

不需要循环来处理输入的重要性是非常值得考虑的。 Awk 长久以来保持可行的原因之一是 Awk 程序很紧凑,而它们紧凑的原因之一是不需要从控制台或文件读取样板。

让我们运行一下程序:

$ awk -f guess.awk
random number is 25
guess a number between 1 and 100: 50
too high, try again: 30
too high, try again: 10
too low, try again: 25
that's right
$

我们没有涉及的一件事是评论。 Awk 注释以 # 开头,以行尾结束。

包起来

Awk 非常强大,这个“猜数字”游戏是一个很好的入门方式。但这不应该是您旅程的终点。您可以阅读 Awk 和 Gawk (GNU Awk) 的历史,Gawk 是 Awk 的扩展版本,如果您运行的是 Linux,则可能是您计算机上的版本,或者从其最初的开发人员那里阅读有关原始版本的所有内容。

您还可以下载我们的备忘单来帮助您跟踪您学到的所有内容。

相关文章: