Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1484100
  • 博文数量: 263
  • 博客积分: 10851
  • 博客等级: 上将
  • 技术积分: 2627
  • 用 户 组: 普通用户
  • 注册时间: 2008-11-26 22:40
文章分类

全部博文(263)

文章存档

2013年(4)

2012年(25)

2011年(33)

2010年(50)

2009年(138)

2008年(13)

分类: LINUX

2010-01-14 18:18:33

Bash中的script是强大的,但如果想让写出的脚本更加实用、灵活,不能简单的堆砌命令,势必要加上一些命令行参数。命令行参数除了实际的操作对象以外,还可能是一些选项(通常是用-开头的),如果还是用$1、$2这样的方式一个一个的判断参数到底是不是选项、是哪个选项就太低效了,更好的方式是用getopts,先看简单的例子:

#!/bin/bash
while getopts 'd:Dm:f:t:' OPT; do
    case $OPT in
        d)
            DEL_DAYS="$OPTARG";;
        D)
            DEL_ORIGINAL='yes';;
        f)
            DIR_FROM="$OPTARG";;
        m)
            MAILDIR_NAME="$OPTARG";;
        t)
            DIR_TO="$OPTARG";;
        ?)
            echo "Usage: `basename $0` [options] filename"
    esac
done

shift $(($OPTIND - 1))
getopts后面的字符串就是可以使用的选项列表,每个字母代表一个选项,后面带:的意味着选项除了定义本身之外,还会带上一个参数作为选项的值,比如d:在实际的使用中就会对应-d 30,选项的值就是30;getopts字符串中没有跟随:的是开关型选项,不需要再指定值,相当于true/false,只要带了这个参数就是true。如果命令行中包含了没有在getopts列表中的选项,会有警告信息,如果在整个getopts字符串前面也加上个:,就能消除警告信息了。

使用getopts识别出各个选项之后,就可以配合case来进行相应的操作了。操作中有两个相对固定的“常量”,一个是OPTARG,用来取当前选项的值,另外一个是OPTIND,代表当前选项在参数列表中的位移。注意case中的最后一个选择──?,代表这如果出现了不认识的选项,所进行的操作。

选项参数识别完成之后,如果要取剩余的其它命令行参数,可以使用shift把选项参数抹去,就像例子里面的那样,对整个参数列表进行左移操作,最左边的参数就丢失了(已经用case判断并进行了处理,不再需要了),位移的长度正好是刚才case循环完毕之后的OPTIND - 1,因为参数从1开始编号,选项处理完毕之后,正好指向剩余其它参数的第一个。在这里还要知道,getopts在处理参数的时候,处理一个开关型选项,OPTIND加1,处理一个带值的选项参数,OPTIND则会加2。

最后,真正需要处理的参数就是$1~$#了,可以用for循环依次处理。

使用getopts处理参数虽然是更加方便了,但仍然有两个小小的局限:

1.选项参数的格式必须是-d val,而不能是中间没有空格的-dval。
2.所有选项参数必须写在其它参数的前面,因为getopts是从命令行前面开始处理,遇到非-开头的参数,或者选项参数结束标记--就中止了,如果中间遇到非选项的命令行参数,后面的选项参数就都取不到了。
3.不支持长选项, 也就是--debug之类的选项

 
 
发一个自己写用getopt支持长选项的例子, 感觉用getopt还是比较麻烦:
 
#!/bin/bash
# 每天把三个月前的数据导入到备份库里面(即该表只存最近3个月的数据)
# 缘由:The table 't_xx_log_intime' is full

# 定义环境变量
export PATH="${PATH}:/bin:/usr/bin:/usr/local/bin:/usr/bsd:/usr/X11R6/bin:/usr/bin/X11:/usr/local/app/xx_bin:/usr/local/mysql-4.1.15/bin:/usr/local/app/bin:/usr/local/bin:/usr/bin:/usr/X11R6/bin:/bin:/usr/games:/opt/gnome/bin:/usr/lib/mit/bin:/usr/lib/mit/sbin"
export LANG=C

# 用户自定义变量
BASE_NAME="${0##*/}"
IS_HELP="false"
IS_DEBUG="false"
IS_DATE="false"
SPLITED_DATE=""
QUARTER=""
DB_NAME=""
TABLE_NAME=""
DAYS_HOLD="90"
# 自定义函数
# 用法
function usage()
{
    echo "Usage: ${BASE_NAME} [--debug] [-h|--help] [-d|--date YYYYMMDD] --db DB_NAME --tab TAB_NAME"   
    exit 1
}
# 初始化
function init()
{
    ARGS="`getopt -u -o "d:h" -l "date:,help,debug,db:,tab:" -- "$@"`"
    [ $? -ne 0 ] && usage
    set -- ${ARGS}
   
    while [ true ] ; do
        case $1 in
            --debug)
                IS_DEBUG="true"
                ;;
            -h|--help)
                IS_HELP="true"
                ;;
            -d|--date)
                IS_DATE="true"
                SPLITED_DATE=$2
                shift
                ;;
            --db)
                DB_NAME="$2"
                shift
                ;;
            --tab)
                TABLE_NAME="$2"
                shift;
                ;;
            --)
                shift
                break
                ;;
            *)
                usage
                ;;
        esac
        shift
    done
   
    [ "${IS_HELP}" == "true" ] && usage
    [ "${IS_DEBUG}" == "true" ] && set -x
    [ -z "${DB_NAME}" ] && usage
    [ -z "${TABLE_NAME}" ] && usage
    if [ -z "${SPLITED_DATE}" ] ; then
        SPLITED_DATE="`date -d"${DAYS_HOLD} days ago" +%F`"
    else
        SPLITED_DATE="`date -d"${SPLITED_DATE}" +%F 2>/dev/null`" || { echo "Date format error"; usage; }   # 判断输入日期的格式
    fi
    return
}
# 处理过程
function do_process()
{
    MONTH="${SPLITED_DATE:5:2}"
    YEAR="${SPLITED_DATE:0:4}"
    QUARTER="`expr ${MONTH} \* 10 / 33 + 1`"    # 判断该月属于哪个季度
    Y_QUAR="${YEAR}Q${QUARTER}"
    BAK_TAB_NAME="${TABLE_NAME}_${Y_QUAR}"
   
    #set -f
    # 建备份表
    echo "[${SPLITED_DATE}]"
    SQL_STRING="CREATE TABLE IF NOT EXISTS ${BAK_TAB_NAME} AS SELECT * FROM ${TABLE_NAME} WHERE 1 = 2;"
    echo "${SQL_STRING}"
    mysql -uroot -D${DB_NAME} -s -e "${SQL_STRING}"
    # 向备份表中插入数据
    SQL_STRING="INSERT INTO ${BAK_TAB_NAME} SELECT * FROM ${TABLE_NAME} WHERE flow_date = '${SPLITED_DATE}'"
    echo "${SQL_STRING}"
    mysql -uroot -D${DB_NAME} -s -e "${SQL_STRING}"
    [ $? -ne 0 ] && { echo "INSERT INTO ${BAK_TAB_NAME} error, exit"; exit 2; }
   
    # 删除源表中的相关数据
    SQL_STRING="DELETE FROM ${TABLE_NAME} WHERE flow_date = '${SPLITED_DATE}'"
    echo "$SQL_STRING"
    mysql -uroot -D${DB_NAME} -s -e "${SQL_STRING}"
    [ $? -ne 0 ] && { echo "DELETE FROM ${TABLE_NAME} error, exit"; exit 2; }
    echo -e "[${SPLITED_DATE}]\n"
    #set +f
    return
}
init "$@"
do_process
 
exit 0
 
set  --      If no arguments follow this option, then the positional parameters are  unset.   Otherwise,  the positional parameters are set to the args, even if some of them begin with a -.
 
# getopt/getopts的区别
1. getopts是bash内建命令的, 而getopt是外部命令
2. getopts不支持长选项, 比如: --date
3. 在使用getopt的时候, 每处理完一个位置参数后都需要自己shift来跳到下一个位置, getopts只需要在最后使用shift $(($OPTIND - 1))来跳到parameter的位置。
4. 使用getopt时, 在命令行输入的位置参数是什么, 在getopt中需要保持原样, 比如 -t , 在getopt的case语句中也要使用-t,  而getopts中不要前面的-。
5. getopt往往需要跟set配合使用
6. getopt -o的选项注意一下
 
 
 
 
 
###############################################################################
Getopt and getopts    getopt和getopts
Both "getopt" and getopts are tools to use for processing and validating shell script arguments. They are similar, but not identical. More confusingly, functionality may vary from place to place, so you need to read the man pages carefully if your usage is more than casual.
"getopt"和"getopts"这两个工具都是用来处理和验证shell脚本的参数。 他们很相似, 但是又不是完全相同。 更令人迷惑不解的是, 在不同的地方功能可能还不一样, 所以如果你经常使用它们, 就需要仔细查看他们的手册页。
Properly handling command line arguments is difficult if you want the usage to be flexible. It's easy to write a script that demands arguments in a specific order; much harder to allow any order at all. It's also hard to allow bunched together arguments or spaced out to be equivalent:
假如你要求命令行参数的使用更加灵活, 处理起来可能会比较困难一些, 要写一个处理特定顺序参数的脚本很容易, 但是要处理任意顺序的命令行参数, 或者要让每个参数组合一起跟把每个参数用空格分开达到一样的效果, 就变得非常困难:
foo -a -b -c
foo -abc
foo -a -c +b
foo -ac +b
These are the problems that getopt(s) are designed to handle. They ease the job considerably, but introduce their own little quirks that your script will need to deal with.
这就是getopt(s)要做的工作, 这可以大大的减少工作量,  但是也会带来这个工具本身的一些小小的怪癖, 你需要在脚本中稍微处理下。
getopt
This is a standalone executable that has been around a long time. Older versions lack the ability to handle quoted arguments (foo a "this won't work" c) and the versions that can, do so clumsily. If you are running a recent Linux version, your "getopt" can do that; SCO OSR5, Mac OS X 10.2.6 and FreeBSD 4.4 has an older version that does not.
getopt是一个独立的可执行程序, 它出现已经有一段时间了。 较老的版本不能处理引号引起来的参数(foo a "this won't work" c), 现在的版本已经能够处理这种“丑陋的”参数了 。 如果你的linux的是最近版本的linux, 里面的getopt可以处理引号引起来的参数; SCO OSR5, MAC OS X 10.2.6还有 FreeBSD 4.4中的getopt版本较老, 不能处理引号引起来的参数。
The simple use of "getopt" is shown in this mini-script:
这个mini脚本中展示了getopt的简单用法:
#!/bin/bash
echo "Before getopt"
for i
do
  echo $i
done
args=`getopt abc:d $*`
set -- $args
echo "After getopt"
for i
do
  echo "-->$i"
done
 
What we have said is that any of -a, -b, -c or -d will be allowed, but that -c is followed by an argument (the "c:" says that).
如之前我们所说, 任意顺序或组合的-a, -b, -c或者-d都可以被允许, 不过这里-c后面需要跟一个参数("c:"表示这样)
If we call this "g" and try it out:
把这个脚本命名为g, 然后执行:
bash-2.05a$ ./g -abc foo
Before getopt
-abc
foo
After getopt
-->-a
-->-b
-->-c
-->foo
-->--
 
We start with two arguments, and "getopt" breaks apart the options and puts each in its own argument. It also added "--".
用两个参数来执行这个脚本, "getopt"会把每个选项都拆分开, 然后在后面加上"--" 。
Of course "getopt" doesn't care that we didn't use "-d"; if that were important, it would be up to your script to notice and complain. However, "getopt" will notice if we try to use a flag that wasn't specified:
当然getopt不会在意我们是不是使用了"-d"选项, 如果这个选项是必须的, 这就要依赖与你的脚本来处理提醒和解释。 不过, 如果你使用了一个没有指定的符号, getopt就会提醒你:
bash-2.05a$ ./g -abc foo -d -f
Before getopt
-abc
foo
-d
-f
getopt: illegal option -- f
After getopt
-->-a
-->-b
-->-c
-->foo
-->-d
-->--
 
However, if you preface the option string with a colon:
不过, 你也可以在选项字符串前面加一个冒号:
args=`getopt :abc:d $*`
 
"getopt" will be silent about the unwanted flag.
这样的话当用了一个没有指定的选项时, getopt就不会提示错误。
As noted at the beginning, if we give "getopt" arguments containing spaces, it breaks:
正如开头提及的, 如果我们提供给getopt的参数包含空格, 这个参数就会被分割开:
bash-2.05a$ ./g -abc "foo bar"
Before getopt
-abc
foo bar
After getopt
-->-a
-->-b
-->-c
-->foo
-->--
-->bar
 
Not only has "foo bar" become two arguments, but they have been separated. This will be true whether you have the newer version that is capable of handling those arguments or not, because it requires different syntax to handle them. If you do have the newer "getopt", you'd need to write the script differently:
"foo bar"被分割了, 被getopt认为是两个参数。 不论你用的是否是新版本的getopt, 这个都会发生, 因为getopt需要另外一种语法来处理这种情况。 如果你不是新版的getopt, 那么就需要用另外一种不同的方法来写这个脚本:
#!/bin/bash
echo "Before getopt"
for i
do
  echo $i
done
args=`getopt -o abc: -- "$@"`
eval set -- "$args"
echo "After getopt"
for i
do
  echo "-->$i"
done
 
We've added a "-o", changed $* to $@ in quotes, and used an "eval" for the set. With the newer (as is on Linux) version, that works:
我们增加了-o选项, 把$*变成$@, 还有在set之前使用了eval。 用这个新的脚本(在linux上), 会这样工作:
bash-2.05a$ ./g -abc "foo bar"
Before getopt
-abc
foo bar
After getopt
-->-a
-->-b
-->-c
-->foo bar
-->--
 
However, if you use that script with the older getopt, you get a useless result:
但是, 如果脚本中的getopt是较老的版本, 就会得到一个无用的结果:
bash-2.05a$ ./gg -abc "foo bar"
Before getopt
-abc
foo bar
After getopt
-->--
-->abc:
-->--
-->-abc
-->foo
-->bar
 
It's unfortunately easy to get bad results from "getopt" by misquoting or using the wrong syntax. Whenever I've had to use this, I make sure to print out the arguments as I did in the "After getopt" while testing. Once you get it right, using it is easy:
如果错误的用了引号, 或者错误的语法, 使用getopt会很容易得到不想要的结果。  不管有没有这样用, 都要在"After getopt"后面的while循环中打印出参数来验证结果。 如果你正确理解了, 用起来就会很容易:
#!/bin/bash
# (old version)
args=`getopt abc: $*`
if test $? != 0
     then
         echo 'Usage: -a -b -c file'
         exit 1
fi
set -- $args
for i
do
  case "$i" in
        -c) shift;echo "flag c set to $1";shift;;
        -a) shift;echo "flag a set";;
        -b) shift;echo "flag b set";;
  esac
done
 
and the results are as expected.
得到了我们想要的结果:
bash-2.05a$ ./g -abc "foo"
flag a set
flag b set
flag c set to foo
bash-2.05a$
 
However, note the "Usage" section which prints if "getopt" doesn't like what you gave it: an extra flag, or not giving an argument to a flag that requires one. Using the this newest script, we can test some of that:
不过, 注意, 如果你给的参数不是getopt所希望的就会打印出Usage。  用这个新的脚本, 我们可以这样测试:
bash-2.05a$ ./g  -ab  -c
getopt: option requires an argument -- c
Usage: -a -b -c file
Bash-2.05a$ ./g  -abj foo
getopt: illegal option -- j
Usage: -a -b -c file
 
But "getopt" is easily fooled:
但是"getopt"也很容易糊弄:
bash-2.05a$ ./g  -a -c -b foo
flag a set
flag c set to -b
flag b set
 
You'd have to deal with that nastiness yourself.
你必须自己去处理这些讨厌的事。
getopts
sh and bash builtin. Easier to use and generally better than getopt, though of course not available in csh-like shells. You shouldn't be using those anyway.
getopts是sh和bash的内建命令。 通常会比getopt更容易使用也会更好, 不过在类csh 的shell中不包含getopts。
This works rather differently than "getopt". First, because it's a built-in, you usually won't find a separate man page for it, though "help getopts" may give you what you need.
getopts工作起来跟getopt会很不一样。 首先, getopts是一个内置命令, 通常你找不到他专门的手册页, 不过help getopts可能会得到你想要的。
The old "getopt" is called once, and it modifies the environment as we saw above. The builtin "getopts" is called each time you want to process an argument, and it doesn't change the original arguments . A simple script to test with:
如上面看到的, getopt被调用一次, 就会改变他的环境。  但是在getopts中, 你要处理一个参数, 你都需要调用getopts一次。 而且他也不会改变初始的参数, 一个简单的例子如下:
#!/bin/bash
while getopts  "abc:" flag
do
  echo "$flag" $OPTIND $OPTARG
done
 
Trying this produces good results:
这样执行, 可以得到正确的结果:
bash-2.05a$ ./g -abc "foo"
a 1
b 1
c 3 foo
 
The "$OPTIND" will contain the index of the argument that will be examined next. If you really needed to, you could tell from that whether arguments were bunched together or given separately, but the real point of it is to let you reset it to re-process the arguments. Try this slightly more complicated version (we'll call it "gg"):
$OPTIND的值是下一个将被检查的参数的索引。 如果你真的需要, 你可以把这些参数组合在一起, 也可以把他们分开单独写。 不过这里真正的意图是让你重置OPTIDX的值, 然后重新处理这些参数。 试下这个稍微复杂的脚本:
#!/bin/bash
while getopts  "abc:def:ghi" flag
do
  echo "$flag" $OPTIND $OPTARG
done
echo "Resetting"
OPTIND=1
while getopts  "abc:def:ghi" flag
do
  echo "$flag" $OPTIND $OPTARG
done
 
We'll give it more arguments so that you can observe it at work:
我们给他多一些参数, 这样就可以看出他是如何工作的:
bash-2.05a$ ./gg -a -bc foo -f "foo bar" -h -gde
a 2
b 2
c 4 foo
f 6 foo bar
h 7
g 7
d 7
e 8
Resetting
a 2
b 2
c 4 foo
f 6 foo bar
h 7
g 7
d 7
e 8
 
The leading ":" works like it does in "getopt" to suppress errors, but "getopt" gives you more help. Back to our first simple version:
开头的: 跟getopt中的一样, 也是忽略掉错误。 但是getopt会提供更多的帮助信息。 回顾下我们第一个简单的例子:
sh-2.05a$ ./g  -a -c -b foo
a 2
c 4 -b
 
The builtin "getopts" doesn't get fooled: the "-b" is the argument to c, but it doesn't think that b is set also.
内建的getopts也不那么容易被糊弄: 这里的-b是c的一个参数,不会认为-b也是一个选项。
If "getopts" encounters an unwanted argument, and hasn't been silenced by a leading ":", the "$flag" in our script above will be set to "?":
如果给了getopts他不需要的参数, 而且没有在开头放置:,  $flag就会被设置为? :
bash-2.05a$ ./g  -a -c foo  -l
a 2
c 4 foo
./g: illegal option -- l
? 4
bash-2.05a$ ./g  -a -c        
a 2
./g: option requires an argument -- c
? 3
 
With a leading ":" (while getopts ":abc:d" flag), things are different:
如果在开头放置了: , 结果会不一样:
bash-2.05a$ ./g  -a -c
a 2
: 3 c
bash-2.05a$ ./g  -a -c foo  -l
a 2
c 4 foo
? 4 l
bash-2.05a$ ./g  -a -c
a 2
: 3 c
 
If an argument is not given for a flag that needs one, "$flag" gets set to ":" and OPTARG has the misused flag. If an unknown argument is given, a "?" is put in "$flag", and OPTARG again has the unrecognized flag.
 
阅读(2249) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~