Work the Shell - 处理错误并使脚本更可靠

作者:Dave Taylor

我意识到在过去的几个月里,我的 shell 脚本有点随意,因为我没有谈到如何确保错误条件不会破坏程序。如果您阅读了Linux Journal的“读者来信”版块,您就会知道我没有涵盖这个主题,因为,嗯,你们已经为我涵盖了!

这个主题范围从简单到复杂,所以让我们从一个基本测试开始:应用程序或实用程序调用后的返回状态。

神奇的 $? 序列

不同的 shell 有不同的返回状态指示符(例如,C shell 使用 $status),但最基本的是 Bash/Bourne shell,自从我开始撰写 Work the Shell 以来,我们一直关注它,它使用$?.

这是一个快速示例

#!/bin/sh

mkdir /
echo "return status is $?"

mkdir /tmp/foobar
echo "return status is $?"

rmdir /tmp/foobar
echo "return status is $?"

rmdir /tmp
echo "return status is $?"

exit 0

运行此命令,您可以看到成功命令和失败命令之间的区别

mkdir: /: Is a directory
return status is 1
return status is 0
return status is 0
rmdir: /tmp: Not a directory
return status is 1

您可以看到,当调用mkdirrmdir并出现错误条件时,它们会输出错误,并且——重要的部分是——$? 返回状态是非零的。

实际上,查看像 mkdir 这样的典型命令的 man 手册,您会看到:“诊断:mkdir 实用程序在成功时退出代码为 0,如果发生错误,则退出代码 >0。”

在理想的世界中,>0返回代码实际上会告诉您发生了什么,但虽然这对于通过软件访问的函数来说是正确的,但对于 shell 来说却并非如此。

另一方面,探索如何创建一个也进行错误处理的 shell 函数仍然很有帮助。这是一个基本的示例函数

makedirectory()
{
   mkdir $1
   status=$?

   echo "return status is $status"
}

这只是创建了一个调用 mkdir 的简单函数,如果您调用它三次——两次在错误情况下,一次在没有错误的情况下,那么它的工作方式如下,这应该不足为奇

mkdir: /: Is a directory
return status is 1
mkdir: /tmp/foobar: File exists
return status is 1

mkdir在您可以简单地通过测试 $? 状态变量来生成自己的错误消息时,生成错误消息是很麻烦的。

以下是如何做到这一点的方法

makedirectory()
{
   mkdir $1 2>&1 > /dev/null
   status=$?

   echo "makedirectory failed trying to make $1 (error $status)"
}

这有点难以理解,因为您必须抑制来自mkdir的错误消息,以便您可以生成自己的错误消息。这是通过将标准错误重定向到标准输出(2>&1序列),然后将标准输出重定向到 /dev/null(> /dev/null序列)来完成的。

提示:如果您想更隐晦一些,这里有一个您可以使用的简写&>/dev/null.

但是,现在运行它时,输出要复杂得多

makedirectory failed trying to make / (error 1)
makedirectory failed trying to make /tmp/foobar (error 1)

这是一种处理错误的好方法,当然,该函数也可以返回错误代码,最后一行是return $status

使用 test 避免错误条件

处理错误的最佳方法是预先捕获错误条件。这最好通过精彩而强大的 test 命令来完成。例如,您在使用 makedirectory() 函数时会遇到的两个典型错误条件是目录已存在或脚本没有创建目录的权限。

第一个很容易测试

if [ -d "$1" ] ; then
  echo "Error: directory $1 already exists."
  exit 0
fi

第二个有点棘手,因为您需要获取请求目录的父目录部分,然后对其进行测试,以查看您是否具有写入和执行权限来创建子目录。这可以使用 dirname 函数(如果没有给出显式目录,则返回 .),后跟 -w(用于可写)和 -x(用于可执行)的测试来完成。

它像这样结合在一起

parentdir="$(dirname $1)" 
if [ ! -x $parentdir -o ! -w $parentdir ] 
then
  echo "Uh oh, can't create requested directory $1"
  exit 0
fi

这是 test 命令的复杂用法,但将“!”读作“非”,将“-o”读作“或”,您可以看到测试是“如果不可执行 $parentdir 或不可写 $parentdir 则...”,这应该是有道理的!

使用 noclobber 避免输出问题

最后,另一个需要注意的 shell 事项是,使用重定向非常容易破坏重要文件。例如,这不应该起作用

$ who > who.output
$ ls > who.output

第二个命令应该会生成一个错误,因为输出文件已经存在,对吗?但它没有,它只是在没有警告或错误的情况下破坏了 who 输出——这不好。

为了避免这个问题,您需要set -o noclobber在脚本中,或者更好的是,在您的登录 shell 中,并让它被子 shell 继承,包括运行 shell 脚本的子 shell。一个好的放置位置可以是您的 .profile 或 .bashrc。

设置了 noclobber 后,这两个命令的行为会有所不同

$ ls > who.output
-bash: who.output: cannot overwrite existing file

这对每个人都很有用,对于我们这些 shell 脚本黑客来说更是如此,对吧?

Dave Taylor 是 UNIX 领域 26 年的老将,Elm Mail System 的创建者,也是畅销书Wicked Cool Shell ScriptsTeach Yourself Unix in 24 Hours等 16 本技术书籍的作者。他的主要网站是 www.intuitive.com,他还提供技术支持,网址为 AskDaveTaylor.com。如果您愿意,可以在 Twitter 上关注他:twitter.com/DaveTaylor

加载 Disqus 评论