使用getopts处理Shell脚本参数

来自ling
跳转至: 导航搜索

使用getopts处理Shell脚本参数

编写一个shell脚本,做一些事;改进这个脚本,更好做这件事;再改进这个脚本,帮自己做些其他的事情;再改进这个脚本帮助其他人做一些事......

简单的脚本处理,一般使用变量$0 $1 $2 ...就可以依次获得全部参数,还可以通过$#获得这个脚本一共有多少个参数。如果你需要处理的情况(或者分支)更多的时候,这个方法就不凑效了,这时候,就可以考虑使用getopts了(man getopts)。

这里将通过一个示例来介绍getopts的用法。

下面的代码,可以通过"./sample -d 5"的方式获取参数:

vi sample.sh
#!/bin/sh
day=7  #default value
while getopts ":d:" opt; do
  case $opt in
    d)
      day=$OPTARG   #get the value
      ;;
    ?)
      echo "How to use: $0 [-d DAY]" >&2
      exit 1
      ;;
    :)
      echo "Option -$OPTARG requires an argument." >&2
      exit 1
      ;;
  esac
done
echo $day

上面例子中需要解释的是下面的部分:

while getopts ":d:" opt; do

这里,第一个冒号表示忽略错误(例如出现了不认识的参数),并在脚本中通过::)来处理这样的错误;字母d则表示,接受参数-d;d后面的冒号表示参数d接收值,即“-d 7”这样的形式;(这里opt变量,可以在while循环中引用当前找到的参数,试试输出$opt试试)

如果是要有很多参数,那么写法可能是:

while getopts ":ixarm:uneh" opt; do 
while getopts 'b:u:s:t:Cm' opt
do
	case $opt in
		b) BUSYBOX_DIR=$OPTARG ;;
		u) UCLIBC_DIR=$OPTARG ;;
		t) TARGET_DIR=$OPTARG ;;
		s) FSSIZE=$OPTARG ;;
		C) CLEANUP=0 ;;
		m) MKFS='mkfs.minix' ;;
		*)
			echo "usage: `basename $0` [-bu]"
			echo "  -b DIR  path to busybox direcory (default ..)"
			echo "  -u DIR  path to uClibc direcory (default ../../uClibc)"
			echo "  -t DIR  path to target direcory (default ./loop)"
			echo "  -s SIZE size of root filesystem in Kbytes (default 4000)"
			echo "  -C      don't perform cleanup (umount target dir, gzip rootfs, etc.)"
			echo "          (this allows you to 'chroot loop/ /bin/sh' to test it)"
			echo "  -m      use minix filesystem (default is ext2)"
			exit 1
			;;
	esac
done

在编写shell脚本中,经常要处理一些输入参数,在上一篇文章中已经有了一个简短的介绍,在使用过程中发现getopts更加方便,能够很好的处理用户输入的参数和参数值。

getopts用于处理用户输入参数,举例说明使用方法:

while getopts :a:b:cdefg opt; do
    case $opts in
        a) do sth;
   ......
        cde) do another;
    esac
done

几个重要变量:

OPTIND:getopts使用OPTIND作为索引,来处理下一个需要处理的参数,记录当前的状态。 OPTARG:在上面的循环中,a,b两个参数后面各有一个冒号,冒号表示该输入的参数后面还有一个参数值,当getopts发现冒号后,会处理用户输入的参数值,这个参数值被保存在OPTARG中。 OPTSTRING:也就是上例中的 :a:b:cdefg,getopts需要处理的参数。注意,最前面的冒号“:”用于指定getopts工作于silent mode,在silent模式下,当用户输入的参数不满足OPTSTRING时,不会讲illegal option这样的错误信息打印出来,使代码看起来更加专业。如果想要工作在verbose模式下,可以去掉最前面的冒号

下面,简单的写了一个shell脚本用来描述getopts的使用方法:

#! /bin/bash
function c1() {
cmd="rhc app create -p redhat"
while getopts :a:t:sn opt; do
    case $opt in
        a) cmd=$cmd" -a $OPTARG" ;;
        t) cmd=$cmd" -t $OPTARG" ;;
        s) cmd=$cmd" -s" ;;
        n) cmd=$cmd" -n --no-dns" ;;
        \?) echo "Invalid param" ;;
    esac
done
echo $cmd
}
c1 -a app1 -t perl-5.10 -s -n

执行这个脚本,我们就会得到预期的结果

[root@LovelyLP shell]# ./getopts-silent.sh 
rhc app create -p redhat -a app1 -t perl-5.10 -s -n --no-dns

当然,有些时候,我们更新网将function写入.bashrc中,方便自己随时调用,如果将这段代码直接粘贴到.bashrc中,可能会引起问题:我没有得到预期的结果,脚本中的cmd并没有按照预想的情况得到处理,这是因为什么呢? 原因在于,保存在.bashrc后,第一次执行完成后,下一次在执行时,OPTIND不会重新产生,因为他被作为一个global variable使用,所以getopts在调用时,他的索引会变得混乱。在脚本中执行没有遇到这个问题的原因也在此,因为脚本每次执行时,都会调用一个新的shell,所以OPTIND会被设置为1。如果想要他在.bashrc中生效,必须要在最上面加上local OPTIND 让我们来稍微修改一下:

# Create apps
function create-apps() {
    local OPTIND
    cmd="rhc app create -p $OPENSHIFT_PASSWD"
    while getopts a:t:sn x
    do
        case $x in
            a) cmd=$cmd" -a $OPTARG" ;;
            t) cmd=$cmd" -t $OPTARG" ;;
            s) cmd=$cmd" -s" ;;
            n) cmd=$cmd" -n --no-dns" ;;
            \?) echo Invalid Params ;;
        esac
    done
    echo $cmd
}

source之后,我们可以直接调用,看一下是否达到了我们预期的结果:

[root@LovelyLP shell]# create-apps -a free -t jbsseap -n 
rhc app create -p redhat -a free -t jbsseap -n --no-dns