Linux 系统管理:用户指南
明年,Addison Wesley Longman 将出版我的新书,Linux 系统管理:用户指南(版权 2001 年,ISBN 0-201-71934-7)。由于本期重点是系统管理,《Linux Journal》的好心人给了我一个机会,让大家先睹为快。
那么,让我来设置场景。这是一个黑暗而暴风雨的夜晚(我一直想写这句话),一位孤独的系统管理员工作到很晚,寻找回家并完成工作的方法。这是第 15 章的片段,我称之为“创造性的懒惰”的一章。多年来,我和一大群其他人一直在宣扬,我的意思是推测,创造性的懒惰可能是一种很棒的工具。我钦佩的那种懒人努力寻找更轻松的方法来做事,并且总是为任何问题寻找更简单、更优雅的解决方案。如果我可以引用科幻小说大师罗伯特·A·海因莱因的一句话,“进步是由懒人寻找更轻松的做事方式而实现的。”
本章的引言涵盖了各种自动化工具,然后才讲到这一点。现在是晚上 11:00。披萨早已消失,自助餐厅也卖完了咖啡。灯光。摄像机。开拍....
乍一看,如果您想要自动化的内容需要人为干预,您似乎运气不佳。有些事情需要人来做:从菜单选项中选择、输入密码或根据呈现的信息做出决定。交互式应用程序需要用户的反应,不是吗?对于聪明的懒惰系统管理员来说,答案并非总是如此——这要归功于一个名为 expect 的小程序。
虽然我之前听说过 expect,但我几年前才发现这种语言的用处。我的合作伙伴和我正在开发一个基于 Web 的系统,该系统需要从主计算机的数据库定期更新,但该数据库不允许命令行脚本。我们需要的数据需要执行一个 SQL 语句,该语句只能通过供应商的菜单界面输入。然后,该 SQL 语句将生成我们 Web 界面所需的数据文件。整个过程的关键是编写一些东西来模拟用户坐在终端前,并在出现各种提示时输入信息。expect,一个基于 Tcl 的软件套件/语言,是解决这个困境的答案。后来,expect 使扩展这个 Web 工具成为可能,远远超出了我们,咳咳,当时的预期。
还在想您是否需要它?还记得本章开头的懒惰讨论吗?好吧,假设您工作到很晚,离开前要做的最后一件事是登录到您的远程站点,确保特定的应用程序已完成(总是在凌晨 3:00 完成),然后将该应用程序生成的文件下载回您的本地站点。现在是晚上 10:00,您宁愿回家,也不愿在那里等待文件准备就绪的神奇时刻。您可以启动一个 at 作业来启动 ncftp 进行下载,但您不知道文件名,因为输出名称在每次运行时都会更改。您通过登录菜单系统并检查完成日志来找到名称。我故意让事情变得复杂,以证明有些情况很难用简单的 shell 脚本自动化。
expect 脚本的基本格式如下
#!/usr/local/bin/expect # Comments on this script (name, what it does, # optional) spawn some_command set response myanswer expect "Some prompt . . . ." send $response\r close
以下是发生的情况。“spawn”关键字告诉 expect 启动某个程序。这可以是 shell (spawn /bin/bash) 或任何会话将通过其进行的命令。使用“set”关键字,我将变量 response 设置为某个预定的响应。该语言的同名“expect”关键字的功能正如其名称所示。它扫描我们使用 spawn 调用的任何命令的输出,搜索匹配的文本。然后,“send”使用第一个变量 $response 响应预期的文本。现在让我们做一些实际的事情。
我在我的系统上运行了一个带有 OpenSSL 扩展的 Apache Web 服务器,用于安全交易。启动带有 OpenSSL 扩展的 Apache 需要我输入安全密码,以便它可以启动,因为服务器上的私钥文件是加密的(参见第 26 章“构建安全 Web 服务器”)。如果我在这里输入密码,这一切都很好,但是如果服务器在我不在时宕机怎么办?它已经几个月没有发生过了,但是这些事情会发生,我们有时会去度假。这可能是一些疯狂的事情,比如我为我的新磁带驱动器添加了一张 SCSI 卡。我可能忘记了(这发生过)重新启动运行 OpenSSL 的 Web 服务器。那怎么办?为了解决这个问题,我编写了您在下面看到的简单 expect 脚本
#!/usr/bin/expect # Routine: startapachessl # Purpose: Start web server with OpenSSL active # log_file -a /tmp/expectlog #log_user 0 spawn /bin/bash sleep .2 send "usr/local/apache/bin/apachectl startssl\r" expect "Enter pass phrase*" sleep .2 send "mysecretphrasegoeshere\r" sleep .2 close
当系统重新启动时,无论我在不在,这个脚本都会重新启动我的运行 OpenSSL 的 Apache Web 服务器。查看脚本,您会注意到一些有趣的事情。例如,log_file 参数是新的。它的作用是为该脚本的执行定义一个日志文件。是否写入文件由 log_user 参数定义。如果设置为“1”,则将进行日志记录。当我仍在测试脚本时,我倾向于使用 log_user,但您可能会决定始终捕获输出。还要注意,我正在生成一个 bash shell 来执行启动我的服务器的脚本。然后,是 sleep 语句。在所有情况下,我都让 shell 等待五分之一秒然后继续。最后,close 语句告诉生成的进程没有更多内容了。此时,expect 终止并返回到生成它的进程。
毫无疑问,您可以使用其他语言编写这些功能,但 expect 使其变得容易。您在前进的过程中会发现,并非每种工具都适合每项工作。对于交互式应用程序的快速而粗略的自动化,没有什么比 expect 更好。完全探索 expect 需要一本书(事实上,已经有一本了)。我试图做的是让您了解您可以使用它做什么,而不是解释该语言的每个方面。在我让您开始自己的探索之前,让我将这些示例更进一步。
我们都知道,定期更改密码与选择好的密码一样重要(参见第 6 章),并且让用户在登录时这样做是相当容易的,但如果他们没有登录帐户,则会更加困难。我指的是电子邮件——仅限用户,即您允许 POP3 邮件接收(或基于 Web 的电子邮件)但不允许实际命令行访问的用户。许多办公室都为他们的 Linux 系统设置了这种设置——它充当电子邮件或 Internet 网关,并且不允许登录。那么,当用户不允许登录时,您如何允许他们更改密码呢?毕竟,更改密码是一种交互式活动,正如以下对话框将证明的那样
[root@myhost] # passwd New UNIX password:
更复杂的是,为了让非 root 用户更改密码,他们必须首先输入旧密码,因此对话框更加复杂。现在怎么办?
您可以创建一个基于 Web 的表单,用户可以在其中预先输入所有这些信息(参见图 1)。

图 1. 密码更改 Web 表单
表单背后的 Perl 脚本将提取变量并将它们传递给 expect 脚本,expect 脚本完成其余的工作。如果您对这个小程序感到好奇,或者想使用它,请随时从我的网站下载它。同时,请看一看应用程序的这一段
#!/usr/bin/expect # Routine: psdcmd # Purpose: to change a user's password with expect log_user 1 set uservar [lindex $argv 0] set currpassword [lindex $argv 1] set newpassword [lindex $argv 2] set renewpassword [lindex $argv 3] # # log_file -a /tmp/expectlog # send_user "Spawning passwd command with uservar.\n" spawn su -l -c "passwd" $uservar expect "Password:" sleep .1 send "$currpassword\r" sleep .1 # expect { "(current) UNIX password:" {send "$currpassword\r"} "su: incorrect password" {exit 0} } sleep .1 expect { "su: incorrect password" {exit 0} "New UNIX password:" {send "$newpassword\r"} } sleep .1 expect { "BAD PASSWORD:" {exit 0} "Retype new UNIX password:" {send "$renewpassword\r"} } sleep .1 expect { "su: incorrect password" {exit 0} "New UNIX password:" {exit 0} } #End of password change routine
set varname [lindex $argv num] 构造表示传递给 expect 例程的参数。请注意,我们的 “spawn” 参数调用会对用户名执行 su 以更改密码。默认情况下,Web 服务器上的 CGI 脚本以一些非特权用户(如 “nobody” 或 “www”)身份执行,因此我们需要更改我们的有效用户才能更改密码。顺便说一下,您希望将 Apache 服务器的默认用户保持为非 root 用户。否则可能会构成可怕的安全漏洞。
脚本中还有另一个新项目。查看 send_user 参数。这本质上是一个打印语句。我将其保留在示例脚本中,因为我想向您展示一种巧妙的调试 expect 脚本的方法。每个程序员都在他或她的代码中插入了调试语句,以监视事情的进展情况。在这种情况下,这是相同的想法。您可以使用 send_user 作为在脚本执行过程中与外部世界通信的一种手段。由于我通过我的 Perl 脚本捕获 expect 脚本的输出,我也会看到这些消息。顺便说一下,Perl 脚本以这种方式调用例程(整个命令在一行中)
$return_code =3D `./psdcmd "$username" "$currpassword" "$newpassword" "$renewpassword" `;
如您所见,调用 expect 脚本时,会传递用户名、当前密码、新密码和重复的新密码。我们可以简单地传递两次新密码,但我们希望密码更改例程的验证方面尽可能接近用户在命令行中体验到的情况。更重要的是,强制用户在更改密码之前确认密码也是一个好主意。
现在您已经了解了 expect 脚本,我将使这个过程变得几乎不可能地容易。与其手动创建 expect 脚本,不如让程序也为您做这件事?当您安装 expect 时,您还将安装一个很酷的小程序,名为 autoexpect。简而言之,autoexpect 将监视您在交互式会话中所做的一切,并为您创建 expect 脚本。以下是命令的格式
autoexpect -f script_outputfile command_string
例如,假设我们想登录到防火墙后面的远程系统,这本质上是一个两步登录过程。在登录到防火墙后,我们然后执行登录(telnet、ssh 等)到内部网络上的另一个系统,然后执行标准菜单程序。我们希望将整个两次登录并启动此菜单的过程自动化。从命令提示符下,我们将键入以下命令
autoexpect -f superlogin.script telnet firewall.mycompany.com完成登录后,您可以退出菜单并注销。autoexpect 将为您捕获整个会话。在运行新脚本之前,您可能需要进行一些编辑以清理一些内容。autoexpect 的输出可能比您想要的更冗长一些。此外,您将需要删除退出菜单和注销的行,但是脚本的基础知识和所有提示都已在那里为您捕获。使脚本可执行,您就快完成了。
您仍然需要添加另一件事。在新 expect 脚本的末尾,添加以下命令:interact
这告诉 expect 在完成其工作后将控制权返回给您。如果没有它,expect 会关闭生成的进程,而您所做的只是非常快速地登录和注销。
我绝不打算将其作为 expect 的权威参考。但是,我确实希望这个小小的介绍(实际上是整章)能够激发您的胃口,并激发您的想象力,去探索其他发展建设性懒惰的方法。毕竟,我们都有其他工作要做。
您屏幕上关于魔法斗篷的所有这些是什么?
