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

编程语言往往有很多共同的特性。学习一门新语言的一个好方法是创建一个熟悉的程序。在本文中,我将使用 awk 创建一个“猜数字”游戏来演示熟悉的概念。
72 位读者喜欢这篇文章。
question mark in chalk

TeroVesalainen via Pixabay CC0

当您学习一门新的编程语言时,最好专注于大多数编程语言共有的东西

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

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

一旦你理解了这些概念,你就可以开始弄清楚其余的部分。例如,大多数语言都有其设计所支持的“做事方式”,而这些方式可能因程序而异。这些方式包括模块化(将相关功能组合在一起)、声明式与命令式、面向对象、低级与高级语法特性等等。许多程序员熟悉的一个例子是“仪式感”,即在解决问题之前设置场景所需的工作量。据说 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-elsewhile 之类的语句中,thenelsewhile 部分采用语句或用 {} 括起来的语句组。但是,关于 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,引用了已经定义的变量 guess,而不是每次都转换输入行。

让我们回到我们想要学习的事物列表

  • 变量 – 是的,Awk 有这些;我们可以推断输入数据以字符串形式出现,但在需要时可以转换为数值
  • 输入 – Awk 只是通过其“数据转换管道”方法来读取内容
  • 输出 – 我们已经使用 Awk 的 printprintf 过程将内容写入输出
  • 条件评估 – 我们已经了解了 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,它可能就是你计算机上安装的版本,或者 阅读有关其最初开发人员的原始版本的所有信息

你还可以 下载我们的速查表,以帮助你跟踪你学习的所有内容。

接下来阅读什么
标签
Chris Hermansen portrait Temuco Chile
自从 1978 年毕业于不列颠哥伦比亚大学以来,我就一直与某种计算机形影不离。自 2005 年以来,我一直是全职 Linux 用户,从 1986 年到 2005 年是全职 Solaris 和 SunOS 用户,在此之前是 UNIX System V 用户。

4 条评论

当我研究某种编程语言时,我不是通过创建一些游戏来学习的。知道如何输入信息并打印出一些结果是好的,但在实践层面,我通常需要能够打开一些文件,从中获取信息,然后从中找到一些东西,也许编辑它,然后保存原始文件或一些新文件。
通过搜索你在某处可以找到的小程序来节省大量时间,无论是在书中还是在网上,然后修改它们以了解是什么破坏了它们,然后如何修复它们。失败是你的朋友,只要你坚持到某个积极的结局。(嗯,这几乎像一首诗的片段)

没错,失败是你学习新工具/编程语言/技术的最好朋友。了解为什么事情不起作用以及如何修复它们是最有价值的一课。

回复 作者:Greg P

随着世界的发展,
很多事情变得复杂。
而那些能够简化它们的人,往往会成功。
准备好经历数千次失败,才能获得一次成功。

无论如何,感谢 Chris Hermansen 如此完美的解释。

Creative Commons License本作品根据 Creative Commons Attribution-Share Alike 4.0 International License 许可。
© . All rights reserved.