Work the Shell - 使用 getopt 解析命令行选项

作者:Dave Taylor

我之前谈到过我是如何成为一个懒惰的 shell 脚本程序员的。这可能是因为我根本不是一个全职的专业软件开发人员,而且我甚至不再管理自己的服务器了——我把这项工作外包给了威斯康星州。

尽管我现在编程不多,但我仍然发现自己需要简单的小应用程序——能够出色地完成一项简单任务的小程序。

然后,还有一些随手写的脚本,它们会一直存在下去,最终成为人们工具包中的支柱,扩展到覆盖多种功能,并神秘地增长到 100 行或更多。

我的工具包中就有这样一个脚本,最初的目的只是为了计算图形文件的尺寸,并为 HTML 图像标签生成正确的高度和宽度属性。

现在,脚本 scale.sh 已经增长到 133 行,并且可以执行各种不同的、尽管是相关的任务。毫不奇怪,它也扩展出各种命令行参数,如下所示

$ ./scale.sh

Usage: scale {args} factor [file or files]
  -a      use URL values for APparenting.com site
  -b      add 1px solid black border around image
  -i      use URL values for intuitive.com/blog site
  -k KW   add keywords KW to the ALT tags
  -r      use 'align=right' instead of <center>
  -s      produces succinct dimensional tags only

A factor 0.9 for 90% scaling, 0.75 for 75%, or max width in pixels.
A factor of '1' produces 100%.

打开代码,你就会看到我编写脚本的小秘密——一种非常粗糙的解析命令行选项的方法

if [ "$1" = "-a" ] ; then
  baseurl="www.apparenting.com/Images/"; shift
fi

我确实警告过你我是一个懒惰的程序员,对吧?这实际上是一种非常经典的解析和处理命令行参数的方法。检查 $1 的值,如果它是一个已知的标志,则更改一两个默认变量,然后使用 shift 命令将 $2 → $1、$3 → $2 等等,有效地从命令行参数中删除已处理的标志。

问题是,当你有不止一两个标志时,这种方法真的行不通。我在我的脚本中按字母顺序遍历命令标志——例如,像这样调用脚本scale -r -a将会失败。它会处理 -r 标志,但永远看不到 -a,并生成错误条件。

幸运的是,有一个非常好的 Linux 命令叫做 getopt,它可以让你以更复杂的方式解析你的命令标志。

在 Shell 脚本中使用 getopt

getopt 命令首先要求你让它重新排列你的命令标志的组织方式,然后你使用 set 命令来更新所有的位置变量。之后,你可以使用 case 语句来遍历位置变量。

第一步是

args=`getopt FLAGS $*`
set -- $args

其中 FLAGS 应该是已知和接受的命令标志的单个字母。如果一个标志带有参数(比如 -s 30),则在其后附加一个冒号。

对于我的脚本,它看起来像这样

args=`getopt abik:rs $*`
set -- $args

为了看看会发生什么,我添加了一个额外的 echo 语句。这是结果

$ scale -abs -k fdsf 100 *png
args = -a -b -s -k fdsf -- 100 blooeeh.png

正如你所看到的, getopt 分隔出每一个命令标志,并添加一个 -- 标志,表明命令标志何时结束——真的很简单!

现在参数已经被重组,解析相对容易,尽管它看起来相当复杂(警告,为了简洁起见,我删除了几个子句)

for i; do
  case "$i" in
    -a  ) baseurl="www.apparenting.com/Images/"
          shift ;;
    -k  ) keywords=" ($2)"
          shift ; shift  ;;
    -s  ) verbose=0
          shift ;;
    --  ) shift; break ;;
  esac
done

让我们倒过来阅读。在 -- 选项处,循环将由于 break 而退出。在到达那里之前,for 循环将不断迭代,遍历所有指定的标志。这就是标志的顺序变得无关紧要的原因。

每次匹配到一个标志时,都会执行期望的操作,设置变量等等,然后 shift 命令再次出现,将所有命令标志向下移动一个位置(例如,$2 到 $1,$3 到 $2 等等)。

Shell 脚本 case 语句匹配行的形式都是

regex ) actions  ;;

双分号是一个奇怪之处,但这就是你如何指示单个 case 匹配的结束,因此是上面显示的符号。

获取 -k 标志的参数也很容易,因为 getopt 已经确保它是一个单独的参数,并且由于我们使用 shift 随着进程移动,所以 $2 将始终是参数本身。

最后,还要注意,作为一种风格方法,我在双分号前加了一个空格。这只是为了当我快速浏览脚本时,能够快速识别是否有任何 case 缺少双分号。

唯一缺少的部分是一些错误处理,因为现在,如果遇到一个错误的标志,就会发生这种情况

$ scale -ax 100 *png
getopt: illegal option -- x

不错,但是脚本没有捕获错误条件或停止运行——不太好。

为了修复它,在调用 getopt 之后,简单地测试返回值

if [ $? != 0 ] ; then ...

在条件语句中,你可能会放置一个用法说明和一个 exit 命令。对于我的脚本,我实际上也测试以确保命令行上至少有两个参数,因为没有它们脚本永远是无效的

if [ $? != 0 -o $# -lt 2 ] ; then
  echo ""
  echo "Usage: scale {args} factor [file or files]"
  echo ""

  ... stuff skipped ...

  exit 0
fi

在我们编写 shell 脚本的旅程中的这一点上,我当然希望你可以阅读那个相当神秘的条件语句并理解它的作用。

最终,以正确的方式解析命令行标志需要做一些工作,但这会使 shell 脚本更加灵活和健壮。

Dave Taylor 自 1980 年首次登录在线网络以来就一直参与 UNIX。这意味着,是的,他即将迎来 30 周年。你几乎可以在网上任何地方找到他,但从这里开始:www.DaveTaylorOnline.com

加载 Disqus 评论