系统管理工具包: 构建无人参与的智能脚本

通过本文了解如何创建脚本来记录其输出、跟踪和标识错误及从错误和问题恢复,从而使其要么正确运行,要么失败并提供合适的错误消息和报告供分析。构建脚本并自动运行是每个好的管理员必须进行的工作,但如何处理错误输出并就脚本应该如何处理这些错误做出明智决策呢?本文将帮助您解决这些问题。

Martin C Brown, 自由撰稿人和顾问, MCslp

Martin Brown 成为一名职业作家已经有 8 年多的时间了。他是很多书籍和文章的作者,内容涉及很多主题。他的特长包括很多开发语言和平台 —— Perl、Python、Java、JavaScript、Basic、Pascal、Modula-2、C、C++、Rebol、Gawk、Shellscript、Windows、Solaris、Linux、BeOS、Mac OS/X 等等 —— 还包括 Web 编程、系统管理和集成。他会定期为 ServerWatch.com、LinuxToday.com 和 IBM developerWorks 撰写文章,在 Computerworld、Apple Blog 以及其他站点都会定期更新自己的 blogger,同时还为 Microsoft 撰写一些主题文章。您可以通过 questions@mcslp.com 与他联系。



2007 年 8 月 30 日

关于本系列

典型的 UNIX® 管理员拥有一套经常用于辅助管理过程的关键实用工具、诀窍和系统。存在各种用于简化不同过程的关键实用工具、命令行链和脚本。其中一些工具来自于操作系统,而大部分的诀窍则来源于长期的经验积累和减轻系统管理员工作压力的要求。本系列文章主要专注于最大限度地利用各种 UNIX 环境中可用的工具,包括简化异构环境中的管理任务的方法。

无人参与脚本的问题

有很多关于无人参与脚本的问题,所谓无人参与脚本是指通过 cron 之类的服务或 at 命令自动运行的脚本。

例如,cron 和 at 命令的默认模式是捕获脚本输出,然后将其通过电子邮件发送到运行脚本的用户。您可能会不希望用户总是收到 cron 缺省发送的电子邮件(特别在一切都正常的情况下)——有时候运行脚本的用户和实际负责监视输出的不是同一个人。

因此,您需要更好的方法来跟踪和标识脚本中的错误,需要更好的方法来沟通问题,此外,还需要更好的方法来将结果告知适当的人员。

正确设置脚本非常重要;您需要确保脚本得到恰当配置,能够方便地进行维护,而且能够有效地运行。还需要能够跟着错误和程序输出,并需要能够确保脚本的执行环境的安全性和有效性。继续往下阅读,以了解如何进行所有这些工作。

设置环境

在使用无人参与的脚本之前,需要确保恰当地设置了环境。作为脚本的一部分,需要对各种元素进行显式配置,花时间进行此工作不仅能够确保脚本正确运行,而且还能够让脚本更易维护。

您可能需要考虑的事项包括:

  • 应用程序的搜索路径
  • 库的搜索路径
  • 目录位置
  • 创建目录或路径
  • 公用文件

有些元素非常易于组织。例如,可以在大部分兼容 Bourne 的外壳程序(sh、Bash、ksh 和 zsh)中使用以下命令设置路径:

PATH=/usr/bin:/bin:/usr/sbin

对于命令和文件位置,请直接在脚本的起始处设置一个对应的变量。然后就可以在要使用文件名的地方使用此变量。例如,当写入到日志文件时,可以使用清单 1

清单 1. 写入日志文件
LOGFILE=/tmp/output.log

do_something >>$LOGFILE
do_another >>$LOGFILE

通过设置一次名称,然后使用相应的变量,可以确保不会用错文件名,如果需要更改文件名,也只需要更改一次即可。

使用单个文件名和变量还非常便于创建复杂的文件名。例如,通过使用 date 命令并提供格式规范可更方便地将日期添加到日志文件名中:

DATE='date +%Y%m%d.%H%M'

上面的命令将以 YYYYMMDD.HHMM 格式创建包含日期的字符串,例如,20070524.2359。可以将该日期变量插入文件名中,从而根据日志文件创建的日期对其进行标记。

如果未在日志文件名中使用日期/时间唯一标识符,则最好在两个脚本同时运行的情况下插入其他唯一标识符。如果您的脚本从两个不同的进程写入相同文件,最后可能导致信息会被破坏,或丢失信息。

所有外壳程序都支持唯一外壳程序 ID(基于外壳程序进程 ID),可以通过特殊的 $$ 变量名进行访问。通过使用全局日志变量,可以方便地创建用于进行日志记录的唯一文件:

LOGFILE=/tmp/$$.err

还可以将相同的全局变量原则应用于目录:

LOGDIR=/var/log/my_app

为了确保创建目录,请为 mkdir 使用 -p 选项,以创建希望使用的目录的完整路径:

mkdir -p $LOGDIR

幸运的是,如果目录已经存在,则此格式不会有任何问题,因而非常适合在无人参与的脚本中运行。

最后,通常最好在无人参与的脚本中使用完整路径名而不是相对路径,从而能将前面的原则与之结合使用。

清单 2. 在无人参与的脚本中使用完整路径名
DATE='date +%Y%m%d.%H%M'
LOGDIR=/usr/local/mcslp/logs/rsynclog
mkdir -p $LOGDIR
LOGNAME=$LOGDIR/$DATE.log

现在已经设置了环境,接下来让我们看看可以如何使用这些原则来帮助处理通用的无人参与脚本。

写入日志文件

可能对脚本最简单的改进就是将脚本的输出写入日志文件。您可能认为没有这个必要,但 cron 的缺省操作是保存所执行的脚本或命令的输出,然后将其通过电子邮件发送到拥有 crontab 或负责相关工作的人。

由于多方面的原因,这个方法并不完美。首先,所配置的可能运行脚本的用户可能和需要处理输出的实际人员不是同一个人。您可能会作为 root 运行脚本,不过运行时脚本或命令的输出要送到其他人那里。如果希望将不同命令的输出发送到不同的用户,设置通用筛选器或重定向的方法并不会起作用。

第二个原因是更具有根本意义。除非出现错误,否则就没有必要接收脚本的输出。cron 守护程序将发送 stdout 和 stderr 的输出,这意味着即使脚本成功执行,也会得到一个输出副本。

最后的原因是关于生成的信息和输出的管理和组织。电子邮件并非总能够有效地记录和跟踪自动运行的脚本的输出。您可能会只想保存成功的日志文件的存档,或在出现问题时将错误日志的副本以电子邮件方式发送出去。

可以采取多种方式进行写入日志文件的工作。最简单的方式是将每个命令的输出重定向到文件中(请参见清单 3)。

清单 3. 将输出重定向到文件
cd /shared
rsync --delete --recursive . /backups/shared >$LOGFILE

如果希望将错误或标准输出组合为单个文件,请使用编号重定向(请参见清单 4)。

清单 4. 将错误和标准输出组合到单个文件中
cd /shared
rsync --delete --recursive . /backups/shared >$LOGFILE 2>&1

清单 4 将信息写入到同一个日志文件中。

您还可能会希望将信息写入到不同的文件中(请参见清单 5)。

清单 5. 将信息写入不同的文件中
cd /shared
rsync --delete --recursive . /backups/shared >$LOGFILE 2>$ERRFILE

对于多个命令的情况下,重定向可能会变得很复杂,而且可能会重复出现。例如,您必须确保是将信息追加(而不是覆盖)到日志文件中(请参见清单 6)。

清单 6. 将信息追加到日志文件
cd /etc
rsync --delete --recursive . /backups/etc >>$LOGFILE >>$ERRFILE

更为简单的方法(如果支持的话)是为一组命令使用内联块,然后作为整体重定向这个块的输出。因此,可以使用清单 8 中的结构覆盖清单 7 中的行。

清单 7. 冗繁的日志记录代码
cd /shared
rsync --delete --recursive . /backups/shared >$LOGFILE 2>$ERRFILE

cd /etc
rsync --delete --recursive . /backups/etc >>$LOGFILE 2>>$ERRFILE

清单 8 显示了分组命令的内联块。

清单 8. 使用块进行日志记录
{
    cd /shared
    rsync --delete --recursive . /backups/shared 

    cd /etc
    rsync --delete --recursive . /backups/etc

} >$LOGFILE 2>$ERRFILE

配对括号意味着子外壳,因此块中的所有命令将作为独立进程的一部分执行(尽管没有必要创建外壳,但所包含的块被作为不同逻辑环境对待)。通过使用子外壳,可以对整个块(而不是单个命令)的标准和错误输出进行集体重定向。

跟踪错误并报告

子外壳的一个主要优势是,可以对脚本的主要内容进行包装,重定向错误然后发送包含脚本执行状态的具有特定格式的电子邮件。

例如,清单 9 显示了复杂的脚本,将在其中设置环境,执行实际命令和进程的大部分内容,跟踪输出,然后发送包含输出和错误信息的电子邮件。

清单 9. 使用子外壳以电子邮件形式发送更为有用的日志
LOGFILE=/tmp/$$.log
ERRFILE=/tmp/$$.err
ERRORFMT=/tmp/$$.fmt

{
    set -e

    cd /shared
    rsync --delete --recursive . /backups/shared

    cd /etc
    rsync --delete --recursive . /backups/etc

} >$LOGFILE 2>$ERRFILE

{
    echo "Reported output"
    echo
    cat /tmp/$$.log
    echo "Error output"
    echo
    cat /tmp/$$.err
} >$ERRORFMT 2>&1

mailx -s 'Log output for backup' root <$ERRORFMT

rm -f $LOGFILE $ERRFILE $ERRORFMT

如果使用子外壳,而您的外壳支持外壳选项(Bash、ksh 和 zsh),则可能会希望以可选方式设置一些外壳选项,以确保出现错误时正确地终止块。例如,Bash 中的 -e (errexit) 选项可确保在简单命令(例如,任何通过脚本调用的外部命令)导致外壳立即终止时外壳会终止。

例如,在清单 9 中,如果第一个 rsync 失败,则子外壳将继续运行下一个命令。不过,有时候可能会希望在命令失败时即停下,因为继续执行会带来更大的损害。通过设置 errexit,子外壳将直接在第一个命令停止时终止。

设置选项和确保安全性

自动化脚本的另一个问题是,确保脚本的安全性,特别是确保脚本不会由于配置不正确而失败。可以为此进程使用外壳选项。

您可能希望以独立于外壳的方式设置其他选项(通常,外壳选项越丰富,就越方便跟踪这些实例)。例如,在 Bash 外壳中,-u 可确保将任何未设置的变量作为错误处理。这可用于确保无人参与脚本不会在未正确配置所需变量的情况下尝试执行。

-C 选项 (noclobber) 可确保在文件已经存在的情况下不会覆盖文件,而且能够防止脚本覆盖其不应该访问的文件(例如系统文件),除非脚本首先采用正确的命令将原始文件删除。

其中每个选项都可以使用 set 命令设置(请参见清单 10)。

清单 10. 使用 set 命令设置选项
set -e
set -C

可以在选项前使用加号 (+) 将其禁用。

可能希望改进脚本的安全性和环境的另一个领域是使用资源限制。可以使用 ulimit 命令设置资源限制,此命令通常特定于外壳,允许对文件大小、内核、内存使用甚至脚本的持续时间进行限制,以确保脚本不会失控。

例如,可以使用以下命令以秒为单位设置 CPU 时间:

ulimit -t 600

尽管 ulimit 并不提供全面的保护,但可帮助处理脚本可能失控的情况,或程序突然使用大量内存而可能导致问题的情况。

捕获错误

我们已经了解了如何跟踪错误、输出和创建能够通过电子邮件发送到相应人员的日志,但如果要进一步具体到错误和响应,又该如何处理呢?

此处可以使用两个工具。第一个是命令的返回状态,第二个是外壳中的 trap 命令。

命令的返回状态可用于确定特定的命令是否正确运行,或者是否生成了某种类型的错误。具体返回状态代码的准确含义对特定命令是唯一的(请访问其手册页),但大家通常接受的原则是,错误代码为 0 表示命令已正确执行。

例如,假定您希望在尝试创建目录时跟踪错误。可以在 mkdir 之后检查 $?,然后通过电子邮件发送输出,如清单 11 中所示。

清单 11. 跟踪返回状态
ERRLOG=/tmp/$$.err

mkdir /tmp 2>>$ERRLOG
if [ $? -ne 0 ]
then
    mailx -s "Script failed when making directory" admin <$ERRLOG
    exit 1
fi

有时候可以通过使用 && 或 || 符号作为 andortype 语句,从而使用返回状态代码信息内联。例如,假定您希望确保创建目录并执行命令,但如果未创建目录,则不执行命令。应该使用 if 语句进行此工作(请参见清单 12)。

清单 12. 在执行命令前确保目录已创建
mkdir /tmp/out
if [ $? -eq 0 ]
then
    do_something
fi

可以将清单 12 修改为单个文件:

mkdir /tmp/out && do_something

上面的语句基本上就是这个意思“创建目录,如果成功完成,则运行目录”。实际上,只有第一个成功完成,才会执行第二个命令。

|| 符号的工作方式与此相反;如果第一个命令未成功完成,则执行第二个命令。这对跟踪命令引发错误但还提供了备用解决方案的情况非常有用。例如,当更改到某个目录时,可能会使用以下的代码行:

cd /tmp/out || mkdir /tmp/out

此代码行尝试更改目录,如果失败(可能由于目录不存在),则创建此目录。另外,还能将这些语句结合在一起使用。当然,在前一个例子中,所希望做的是更改到指定目录,或者如果不存在此目录则进行创建,然后再更改到此目录。可以将此编写为一个代码行:

cd /tmp/out || mkdir /tmp/out && cd /tmp/out

trap 命令是用于跟踪更为严重的错误的通用型解决方案,基于命令失败时发出的信号,如内核转储、内存错误或 kill 命令强行终止了命令。

要使用跟踪,可以指定跟踪到信号时执行的命令或函数、要跟踪的信号编号,如此处的清单 13 中所示。

清单 13. 跟踪信号
function catch_trap
{
    echo "killed" mailx -s "Signal trapped" admin
}

trap catch_trap 1 2 3 4 5 6 7 8 9 10 11

sleep 9000

可以通过这种方式跟踪任何信号,而且也能很好地确保捕获并有效地跟踪和报告程序崩溃的情况。

确定可报告错误

在本文中,我们已经了解了跟踪错误、保存输出以及记录问题以便进行处理和报告的各种方法。不过,如果所使用的脚本或命令输出您希望使用和报告但并不总是需要知道的信息,又该如何处理呢?

这个问题解决起来并不容易,但可以使用本文所述的技术的组合来记录错误和信息、读取或筛选信息并相应地对其报告或显示。

进行此工作的一个简单方法是,选择将输出和报告的哪些部分写入日志。或者,可以将日志进行后期处理,以选择或筛选出需要的结果。

例如,假定有一个构建文档的脚本,它在后台使用 Apache 的格式化对象处理器(Formatting Objects Processor,FOP)系统生成文档的 PDF 版本。不过,在进程执行过程中,生成了关于断字的一系列错误。您将看到这些错误,但并不会影响输出质量。在生成文件的脚本中,直接将这行从错误日志中筛选出去即可:

sed -e '/hyphenation/d' <error.log >mailerror.log

如果没有其他错误,mailerror.log 文件将为空,并将发送包含错误信息的电子邮件。

总结

在本文中,我们了解了如何在无人参与的脚本中运行命令、捕获输出和监视脚本中不同命令的执行。可以通过很多方式对信息进行记录(例如,按命令记录或全盘记录),并检查和报告进度。

在错误跟踪方面,可以监视输出和结果代码,甚至可以设置全局跟踪来在执行期间标识问题和对其进行跟踪,以便生成报告。从而可以获得各种可选方案来处理和报告与自动运行的脚本相关的问题,对于能够从错误和问题恢复的脚本而言,这一点至关重要。

参考资料

学习

获得产品和技术

  • Apache FOP (Formatting Objects Processor):这是全世界第一个由 XSL 格式设置对象(XSL Formatting Object,XSL-FO)驱动的打印格式设置工具和全世界第一个与输出无关的格式设置工具。
  • IBM 试用软件:从 developerWorks 可直接下载这些试用软件,您可以利用它们开发您的下一个项目。

讨论

条评论

developerWorks: 登录

标有星(*)号的字段是必填字段。


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件

 


在您首次登录 developerWorks 时,会为您创建一份个人概要。您的个人概要中的信息(您的姓名、国家/地区,以及公司名称)是公开显示的,而且会随着您发布的任何内容一起显示,除非您选择隐藏您的公司名称。您可以随时更新您的 IBM 帐户。

所有提交的信息确保安全。

选择您的昵称



当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

标有星(*)号的字段是必填字段。

(昵称长度在 3 至 31 个字符之间)

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

 


所有提交的信息确保安全。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=AIX and UNIX
ArticleID=252234
ArticleTitle=系统管理工具包: 构建无人参与的智能脚本
publish-date=08302007