O.2. 编写脚本

编写脚本来执行下列各项任务。

简单

自我复制脚本

编写一个可以自我备份的脚本,即,将自身复制到一个名为backup.sh.

的文件。提示:使用 cat 命令和适当的位置参数

主目录列表

对用户的主目录执行递归目录列表,并将信息保存到文件中。压缩该文件,让脚本提示用户插入 USB 闪存驱动器,然后按 ENTER。最后,通过解析 df 的输出来确保闪存驱动器已正确挂载后,将文件保存到闪存驱动器。请注意,闪存驱动器在移除之前必须卸载

for 循环转换为 whileuntil 循环

示例 11-1 中的 for 循环 转换为 while 循环。提示:将数据存储在 数组 中,并逐步遍历数组元素。

已经完成了 "繁重的工作",现在将示例中的循环转换为 until 循环

更改文本文件的行距

编写一个脚本,读取目标文件的每一行,然后将该行写回stdout,但在其后添加一个额外的空行。这将产生双倍行距的效果。

包含所有必要的代码来检查脚本是否获得了必要的命令行参数(文件名),以及指定的文件是否存在。

当脚本正确运行时,修改它以三倍行距显示目标文件。

最后,编写一个脚本来删除目标文件中的所有空行,使其变为单倍行距

反向列表

编写一个脚本,将其自身回显到stdout,但以反向显示。

自动解压缩文件

给定一个文件名列表作为输入,此脚本查询每个目标文件(解析 file 命令的输出)以了解其使用的压缩类型。然后,脚本自动调用适当的解压缩命令(gunzip, bunzip2, unzip, uncompress,或其他任何命令)。如果目标文件未压缩,则脚本会发出警告消息,但不会对该特定文件执行其他操作。

唯一系统ID

为您的计算机生成一个 "唯一" 的 6 位十六进制标识符。不要使用有缺陷的 hostid 命令。提示: md5sum /etc/passwd,然后选择输出的前 6 位数字。

备份

将您主目录树(*.tar.gz文件)中所有在过去 24 小时内修改过的文件存档为一个 "tarball"/home/your-name)。提示:使用 find

可选:您可以将其用作 备份 脚本的基础。

检查进程是否仍在运行

给定一个 进程 IDPID)作为参数,此脚本将以用户指定的时间间隔检查给定的进程是否仍在运行。您可以使用 pssleep 命令。

素数

打印(到stdout)60000 到 63000 之间的所有素数。输出应以列的形式进行良好格式化(提示:使用 printf)。

彩票号码

一种彩票涉及选择五个不同的数字,范围为 1 - 50。编写一个脚本,生成此范围内的五个伪随机数,没有重复。该脚本将提供以下选项:将数字回显到stdout或将它们保存到文件中,以及生成特定数字集时的日期和时间。(如果您的脚本始终生成中奖彩票号码,那么您可以靠收益退休,并将 shell 脚本留给我们这些不得不工作才能谋生的人。)

中级

整数或字符串

编写一个脚本 函数,该函数确定传递给它的参数是整数还是字符串。如果传递的是整数,该函数将返回 TRUE (0),如果传递的是字符串,则返回 FALSE (1)。

提示:以下表达式在$1不是整数时会返回什么?

expr $1 + 0

ASCII 到整数

C 中的 atoi 函数将字符串字符转换为整数。编写一个执行相同操作的 shell 脚本函数。同样,编写一个执行相反操作的 shell 脚本函数,镜像 C itoa 函数,该函数将整数转换为 ASCII 字符。

管理磁盘空间

一次列出

/home/用户名目录树中所有大于 100K 的文件。让用户可以选择删除或压缩文件,然后继续显示下一个文件。将所有已删除文件的名称和删除时间写入日志文件。

横幅

在脚本中模拟已弃用的 banner 命令的功能。

删除非活动帐户

网络服务器上的非活动帐户会浪费磁盘空间,并可能成为安全风险。编写一个管理脚本(由 rootcron 守护程序 调用),该脚本检查并删除在过去 90 天内未被访问过的用户帐户。

强制执行磁盘配额

为多用户系统编写一个脚本,该脚本检查用户的磁盘使用情况。如果用户超过了其/home/用户名目录中的预设限制(例如,500 MB),则脚本会自动向她发送 "pigout" 警告电子邮件。

该脚本将使用 dumail 命令。作为一种选择,它将允许使用 quotasetquota 命令设置和强制执行配额。

登录用户信息

对于所有已登录的用户,显示他们的真实姓名以及上次登录的时间和日期。

提示:使用 wholastlog 和解析 /etc/passwd

安全删除

"安全" 删除命令sdel.sh实现为一个脚本。传递给此脚本作为命令行参数的文件名不会被删除,而是 gzipped(如果尚未压缩)(使用 file 检查),然后移动到~/TRASH目录。调用时,脚本检查~/TRASH目录中是否存在超过 48 小时的文件,并永久删除它们。(更好的替代方法可能是让第二个脚本处理此操作,并由 cron 守护程序 定期调用。)

附加题:编写脚本使其可以递归地处理文件和目录。这将使其具有 "安全删除" 整个目录结构的能力。

兑换零钱

使用普通流通中的硬币(最多 25 美分),兑换 1.68 美元最有效的方法是什么?它是 6 个 25 美分硬币、1 个 10 美分硬币、1 个 5 美分硬币和 3 个 1 美分硬币。

给定任意的命令行输入,以美元和美分 ($*.??) 为单位,使用最少数量的硬币计算零钱。如果您的国家/地区不是美国,则可以使用您当地的货币单位代替。该脚本需要解析命令行输入,然后将其更改为最小货币单位(美分或其他)的倍数。提示:查看 示例 24-8

二次方程

解形式为Ax^2 + Bx + C = 0二次方程。让脚本将系数A, BC作为参数,并返回小数点后五位的解。

提示:使用众所周知的公式,将系数管道传输到 bcx = ( -B +/- sqrt( B^2 - 4AC ) ) / 2A.

对数表

使用 bcprintf 命令,打印出一个格式精美的八位自然对数表,范围在 0.00 到 100.00 之间,步长为 .01。

提示:bc 需要-l选项来加载数学库。

Unicode 表

使用 示例 T-1 作为模板,编写一个脚本,将完整的 Unicode 表打印到文件中。

提示:使用-e选项到 echoecho -e '\uXXXX',其中XXXX是 Unicode 数字字符的指定。这需要 Bash 的 4.2 版本或更高版本。

匹配数字之和

找到所有五位数(范围为 10000 - 99999)的数字之和,这些数字恰好包含两个以下数字集合:{ 4, 5, 6 }。这些数字可以在同一数字中重复,如果是这样,则每次出现都计数一次。

匹配数字的一些示例是 42057、74638 和 89515。

幸运数字

幸运数字是指其各个数字加起来为 7 的数字,即连续相加。例如,62431 是一个 幸运数字 (6 + 2 + 4 + 3 + 1 = 16, 1 + 6 = 7)。找到 1000 到 10000 之间的所有 幸运数字

掷骰子

示例 A-40 借用 ASCII 图形,编写一个脚本来玩著名的赌博游戏 掷骰子。该脚本将接受一个或多个玩家的赌注,掷骰子,并跟踪输赢以及每个玩家的资金。

井字游戏

编写一个脚本,与人类玩家玩儿童游戏 井字游戏。该脚本将让人类选择是否先走一步。该脚本将遵循最佳策略,因此永远不会输。为了简化问题,您可以使用 ASCII 图形。

   o | x |
   ----------
     | x |
   ----------
     | o |
     
   Your move, human (row, column)?

按字母顺序排列字符串

按字母顺序(ASCII 顺序)排列从命令行读取的任意字符串。

解析

解析 /etc/passwd,并以美观、易于阅读的表格形式输出其内容。

记录登录

解析/var/log/messages生成一个格式良好的用户登录名和登录时间文件。该脚本可能需要以 root 身份运行。(提示:搜索字符串 "LOGIN."

美化数据文件

某些数据库和电子表格软件包使用逗号分隔字段的保存文件,通常称为逗号分隔值或 CSV。其他应用程序通常需要解析这些文件。

给定一个包含逗号分隔的字段的数据文件,格式如下:

Jones,Bill,235 S. Williams St.,Denver,CO,80221,(303) 244-7989
Smith,Tom,404 Polk Ave.,Los Angeles,CA,90003,(213) 879-5612
...
重新格式化数据并将其输出为stdout带有标签的,间距均匀的列。

两端对齐

给定来自stdin或文件的 ASCII 文本输入,调整单词间距以将每行右对齐到用户指定的行宽,然后将输出发送到stdout.

邮件列表

使用 mail 命令,编写一个管理简单邮件列表的脚本。该脚本自动通过电子邮件发送每月公司新闻通讯,从指定的文本文件中读取,并将其发送到邮件列表中的所有地址,该脚本从另一个指定的文件中读取该邮件列表。

生成密码

生成伪随机 8 字符密码,使用 [0-9]、[A-Z]、[a-z] 范围内的字符。每个密码必须至少包含两个数字。

监控用户

您怀疑网络上的某个特定用户滥用其权限,并可能试图入侵系统。编写一个脚本来自动监控和记录她在登录时的活动。日志文件将保存前一周的条目,并删除超过七天的条目。

您可以使用 lastlastloglastcomm 来帮助您监视可疑的恶棍。

检查断开的链接

使用带有以下选项的 lynx-traversal编写一个脚本来检查网站上是否有断开的链接。

困难

测试密码

编写一个脚本来检查和验证密码。目的是标记 "弱" 或容易猜到的密码候选项。

要被认为是可接受的,密码必须满足以下最低条件:

  • 最小长度为 8 个字符

  • 必须包含至少一个数字字符

  • 必须包含以下非字母字符之一:@#$%&*+-=

可选

  • 对测试密码中每段至少四个连续字母字符的序列进行字典检查。这将消除包含标准字典中找到的嵌入式 "单词" 的密码。

  • 使脚本能够检查系统上的所有密码。这些密码不位于 /etc/passwd 中。

本练习测试对 正则表达式 的掌握程度。

交叉引用

编写一个在目标文件上生成交叉引用索引)的脚本。 输出将是目标文件中所有单词出现的列表,以及每个单词出现的行号。 传统上,链表结构将用于此类应用程序。 因此,您应该在本练习中调查数组示例 16-12 可能不是一个好的开始。

平方根

编写一个使用 牛顿法 计算数字平方根的脚本。

其算法,表示为 Bash 伪代码 片段如下:

#  (Isaac) Newton's Method for speedy extraction
#+ of square roots.

guess = $argument
#  $argument is the number to find the square root of.
#  $guess is each successive calculated "guess" -- or trial solution --
#+ of the square root.
#  Our first "guess" at a square root is the argument itself.

oldguess = 0
# $oldguess is the previous $guess.

tolerance = .000001
# To how close a tolerance we wish to calculate.

loopcnt = 0
# Let's keep track of how many times through the loop.
# Some arguments will require more loop iterations than others.


while [ ABS( $guess $oldguess ) -gt $tolerance ]
#       ^^^^^^^^^^^^^^^^^^^^^^^ Fix up syntax, of course.

#      "ABS" is a (floating point) function to find the absolute value
#+      of the difference between the two terms.
#             So, as long as difference between current and previous
#+            trial solution (guess) exceeds the tolerance, keep looping.

do
   oldguess = $guess  # Update $oldguess to previous $guess.

#  =======================================================
   guess = ( $oldguess + ( $argument / $oldguess ) ) / 2.0
#        = 1/2 ( ($oldguess **2 + $argument) / $oldguess )
#  equivalent to:
#        = 1/2 ( $oldguess + $argument / $oldguess )
#  that is, "averaging out" the trial solution and
#+ the proportion of argument deviation
#+ (in effect, splitting the error in half).
#  This converges on an accurate solution
#+ with surprisingly few loop iterations . . .
#+ for arguments > $tolerance, of course.
#  =======================================================

   (( loopcnt++ ))     # Update loop counter.
done

这是一个足够简单的配方,乍一看,似乎很容易转换为有效的 Bash 脚本。 但是,问题是 Bash 不支持浮点数。 因此,脚本编写者需要使用 bc 甚至 awk 来转换数字并进行计算。 它可能会变得相当混乱...

记录文件访问

记录对/etc中文件的所有访问,为期一天。 此信息应包括文件名、用户名和访问时间。 如果对文件进行任何更改,将对其进行标记。 将此数据以表格(制表符分隔)格式记录在日志文件中。

监控进程

编写一个脚本,持续监控所有正在运行的进程,并跟踪每个父进程生成多少个子进程。 如果某个进程产生超过五个子进程,则该脚本会将包含所有相关信息的电子邮件发送给系统管理员(或 root),包括时间、父进程的 PID、子进程的 PID 等。 该脚本每十分钟将一份报告附加到日志文件中。

删除注释

从命令行上指定名称的 shell 脚本中删除所有注释。 请注意,初始的 #! 行 不能删除。

删除 HTML 标签

从指定的 HTML 文件中删除所有 HTML 标签,然后将其重新格式化为长度介于 60 到 75 个字符之间的行。 根据需要重置段落和块间距,并将 HTML 表格转换为近似的文本等效项。

XML 转换

将 XML 文件转换为 HTML 和文本格式。

可选:将 Docbook/SGML 转换为 XML 的脚本。

追捕垃圾邮件发送者

编写一个脚本,通过对标头中的 IP 地址进行 DNS 查找来分析垃圾邮件,以识别中继主机以及始发 ISP。 该脚本会将未修改的垃圾邮件消息转发给负责的 ISP。 当然,有必要过滤掉您自己的 ISP 的 IP 地址,这样您就不会最终抱怨自己了。

根据需要,使用适当的网络分析命令

有关一些想法,请参阅示例 16-41示例 A-28

可选:编写一个脚本,搜索电子邮件列表并根据指定的过滤器删除垃圾邮件。

创建 man pages

编写一个自动化创建 man pages 的过程的脚本。

给定一个包含要格式化为 man page 的信息的文本文件,该脚本将读取该文件,然后调用适当的 groff 命令以将相应的 man page 输出到stdout。 该文本文件包含标准 man page 标题下的信息块,即 NAME、SYNOPSIS、DESCRIPTION 等。

示例 A-39 是一个具有指导意义的第一步。

十六进制转储

对指定为脚本参数的二进制文件执行十六进制转储。 输出应采用整齐的表格 字段,第一个字段显示地址,接下来的 8 个字段各显示一个 4 字节十六进制数,最后一个字段显示前 8 个字段的 ASCII 等效项。

显而易见的后续操作是将十六进制转储脚本扩展为反汇编程序。 使用查找表或其他巧妙的技巧,将十六进制值转换为 80x86 操作码。

模拟移位寄存器

示例 27-15 为灵感,编写一个脚本,将 64 位移位寄存器模拟为一个 数组。 实现 加载 寄存器,左移右移旋转 的函数。 最后,编写一个将寄存器内容解释为八个 8 位 ASCII 字符的函数。

计算行列式

编写一个通过递归扩展余子式来计算行列式的脚本[1]。 使用 4 x 4 行列式作为测试用例。

隐藏单词

编写一个"找词"谜题生成器,一个脚本,在 10 x 10 的随机字母数组中隐藏 10 个输入单词。 这些单词可以横向、纵向或对角线隐藏。

可选:编写一个脚本来解决找词谜题。 为了避免变得太难,解决方案脚本将仅查找水平和垂直单词。(提示:将每一行和每一列视为一个字符串,并搜索子字符串。)

字谜

生成 4 个字母输入的字谜。 例如,word 的字谜是:do or rod row word。 您可以使用/usr/share/dict/linux.words作为参考列表。

词梯

"词梯"是一系列单词,序列中每个连续的单词与前一个单词仅相差一个字母。

例如,要从 markvase 建立"词梯"

mark --> park --> part --> past --> vast --> vase
         ^           ^       ^      ^           ^

编写一个脚本来解决词梯谜题。 给定一个起始单词和一个结束单词,该脚本将列出 "词梯" 中的所有中间步骤。 请注意,序列中的所有单词都必须是合法的字典单词。

迷雾指数

一段文本的"迷雾指数"估计了其阅读难度,该数字大致对应于学校年级。 例如,迷雾指数为 12 的段落对于受过 12 年教育的任何人来说都应该是可以理解的。

冈宁版本的迷雾指数使用以下算法。

  1. 选择长度至少为 100 个单词的文本部分。

  2. 计算句子数(被文本部分边界截断的句子的一部分算作一个)。

  3. 找到每个句子的平均字数。

    AVE_WDS_SEN = TOTAL_WORDS / SENTENCES

  4. 计算段落中"难"的单词数——那些包含至少 3 个音节的单词。 将此数量除以总字数以获得难词的比例。

    PRO_DIFF_WORDS = LONG_WORDS / TOTAL_WORDS

  5. 冈宁迷雾指数是上述两个量的总和,乘以 0.4,然后四舍五入到最接近的整数。

    G_FOG_INDEX = int ( 0.4 * ( AVE_WDS_SEN + PRO_DIFF_WORDS ) )

步骤 4 是该练习中最困难的部分。 存在各种用于估计单词音节数的算法。 经验法则公式可能会考虑单词中的字母数以及元音-辅音的组合。

对冈宁迷雾指数的严格解释不将复合词和专有名词算作"难"词,但这会使脚本极大地复杂化。

使用蒲丰投针法计算 PI

十八世纪的法国数学家蒲丰提出了一个新颖的实验。 反复丢掷长度为n的针到由长而窄的平行木板组成的木地板上。 分隔等宽地板的裂缝的固定距离为d。 跟踪总丢掷次数和针与地板上的裂缝相交的次数。 事实证明,这两个量的比率是 PI 的一个分数倍数。

本着 示例 16-50 的精神,编写一个运行 蒲丰投针 的蒙特卡罗模拟的脚本。 为了简化起见,将针长设置为等于裂缝之间的距离,n = d.

提示:实际上有两个关键变量:针中心到最近裂缝的距离,以及针与该裂缝的倾斜角。 您可以使用 bc 来处理计算。

普莱费尔密码

在脚本中实现普莱费尔(惠斯通)密码。

普莱费尔密码通过替换 二元组(2 个字母的分组)来加密文本。 传统上,使用 5 x 5 字母加扰字母 密钥方格 进行加密和解密。

   C O D E S
   A B F G H
   I K L M N
   P Q R T U
   V W X Y Z

Each letter of the alphabet appears once, except "I" also represents
"J". The arbitrarily chosen key word, "CODES" comes first, then all
the rest of the alphabet, in order from left to right, skipping letters
already used.

To encrypt, separate the plaintext message into digrams (2-letter
groups). If a group has two identical letters, delete the second, and
form a new group. If there is a single letter left over at the end,
insert a "null" character, typically an "X."

THIS IS A TOP SECRET MESSAGE

TH IS IS AT OP SE CR ET ME SA GE



For each digram, there are three possibilities.
-----------------------------------------------

1) Both letters will be on the same row of the key square:
   For each letter, substitute the one immediately to the right, in that
   row. If necessary, wrap around left to the beginning of the row.

or

2) Both letters will be in the same column of the key square:
   For each letter, substitute the one immediately below it, in that
   row. If necessary, wrap around to the top of the column.

or

3) Both letters will form the corners of a rectangle within the key square:
   For each letter, substitute the one on the other corner the rectangle
   which lies on the same row.


The "TH" digram falls under case #3.
G H
M N
T U           (Rectangle with "T" and "H" at corners)

T --> U
H --> G


The "SE" digram falls under case #1.
C O D E S     (Row containing "S" and "E")

S --> C  (wraps around left to beginning of row)
E --> S

=========================================================================

To decrypt encrypted text, reverse the above procedure under cases #1
and #2 (move in opposite direction for substitution). Under case #3,
just take the remaining two corners of the rectangle.


Helen Fouche Gaines' classic work, ELEMENTARY CRYPTANALYSIS (1939), gives a
fairly detailed description of the Playfair Cipher and its solution methods.

此脚本将有三个主要部分:

  1. 基于用户输入的关键字生成 密钥方格

  2. 加密 明文 消息。

  3. 解密加密的文本。

脚本将大量使用数组函数。您可以参考示例 A-56 作为灵感。

--

请不要将这些练习的解答发送给作者。有更合适的方式来用你的聪明才智给他留下深刻印象,比如提交错误修复和改进本书的建议。

注释

[1]

对于所有代数没学好的聪明人来说,行列式是一个与多维矩阵(数字数组)相关的数值。

For the simple case of a 2 x 2 determinant:

  |a  b|
  |b  a|

The solution is a*a - b*b, where "a" and "b" represent numbers.