zoukankan      html  css  js  c++  java
  • 使用bash编写Linux shell脚本参数和子壳

    为了成为一个灵活的工具,一个合格的脚本必须提供额外的信息来说明此脚本的作用,如何执行此脚本以及在哪儿执行此脚本。和命令一样脚本也使用参数。开关和参数提高了重用性同时也减少了成本,节省了时间。

    定位的参数

    有三种有效的方法可以使Linux脚本使用参数。第一种使用定位参数。脚本根据在命令行出现参数的位置调用参数。因为其他两种依赖于定位参数,所以先讨论这个。

    Bash变量使用“$0”标示脚本的路径。不必是全路径名,但是它定义了执行脚本所在的路径。

    $ printf “%s\n” “$0”

    /bin/bash

    在这个例子中,Bash会和开始命令/bin/bash。

    当参数命令组合了basename命令时,只留下脚本的名字,其余的路径部分被删除了。

    一些微缩版本使用Bash的字符串替换功能来避免执行外面的程序。

    $ declare -rxSCRIPT=${0##*/}

    $ printf “%s\n” “$SCRIPT”

    Bash

    通过使用“$0”来找到脚本的名字,在脚本被拷贝和重新命名之后,就不会出现错误文件名的潜在威胁了。SCRIPT总是保持这正确的脚本名。

    变量“$#”包含有脚本或外壳会话参数的个数。如果没有参数,$#总是0。这个参数没有将脚本名包含在$0中。

    $ printf “%d\n” $#

    0

    前面九个参数放置在变量$1~$9中。(九个之后的参数如果要访问使用大括号)。如果设置了nounset外壳选项,访问一个未定义的参数会产生一个错误,就像未定义变量名一样的错误。

    $ printf “%s\n” $9

    bash: $9: unboundvariable

    变量“$@”或者是“?*”将所有参数作为一个字符串返回。

    当使用定位参数时,Bash并不区分它们是参数还是开关,对于脚本来说在命令行的每一个项目作为独立的参数来对待。

    考虑一下下面的脚本,显示在列表9.1中:

    Listing 9.1 params.sh

    #!/bin/bash

    #

    # params.sh: apositional parameter demonstration

    printf “There are %dparameter(s)\n” “$#”

    printf “The completelist is %s\n” “$@”

    printf “The firstparameter is %s\n” “$1”

    printf “The secondparameter is %s\n” “$2”

    当运行此脚本并带上参数“-c”和“t2341”,它表示“$1”是“-c”,“$2”是“t2341”。

    $ bash parms.sh -c t2341

    There are 2 parameter(s)

    The complete list is -ct2341

    The first parameter is-c

    The second parameter ist2341

    虽然“$@”和“$*”都表示所有的参数,但是如果他们用双引号封装起来的含义是有所不同的。“$@”根据IFS变量的第一个字符进行分割,如果IFS为空则使用空格,如果IFS没有定义,则不使用任何东西。“$*”将一组参数作为一个单独的组。

    “$@”总是使用空格进行分割,并将参数视为一个个单独的项目,即使它们使用双引号包起来也是这样。“$@”通常用于将整个开关集合传输给另一个命令(例如:ls $@)。

    虽然定位参数是一个简单的方法来遍历开关和参数,它们并不是总是这样直接遍历参数列表的,有一个内置命令shift,它可以将参数“$1”给丢弃掉,将后面的参数前移一位。使用shift命令,你可以检查每一个参数,就像它们总是第一个参数一样。

    列表9.2展示了如何使用shift的完整例子:

    Listing 9.2 param2.sh

    #!/bin/bash

    #

    # param2.sh

    #

    # This script expectsthe switch -c and a company name. --help (-h)

    # is also allowed.

    shopt -s -o nounset

    declare -rxSCRIPT=${0##*/}

    # Make sure there is atleast one parameter or accessing $1

    # later will be anerror.

    if [ $# -eq 0 ] ; then

    printf “%s\n” “Type--help for help.”

    exit 192

    fi

    # Process the parameters

    while [ $# -gt 0 ] ; do

    case “$1” in

    -h | --help) # Show help

    printf “%s\n” “usage:$SCRIPT [-h][--help] -c companyid”

    exit 0

    ;;

    -c ) shift

    if [ $# -eq 0 ] ; then

    printf “$SCRIPT:$LINENO:%s\n” “company for -c is missing” >&2

    exit 192

    fi

    COMPANY=”$1”

    ;;

    -* ) printf“$SCRIPT:$LINENO: %s\n” “switch $1 not supported” >&2

    exit 192

    ;;

    * ) printf“$SCRIPT:$LINENO: %s\n” “extra argument or missing switch” >&2

    exit 192

    ;;

    esac

    shift

    done

    if [ -z “$COMPANY” ] ;then

    printf “%s\n” “companyname missing” >&2

    exit 192

    fi

    # <-- begin work here

    exit 0

    最后一个有关的参数是“$_”(美元符号加上下划线)。这个开关有两个作用,首先当外壳脚本首先开始时,它表示为外壳或外壳脚本的路径名,其次,在每个命令执行之后,当前命令被放置在环境变量中。

    $ /bin/date

    Fri Jun 29 14:39:58 EDT2001

    $ printf “%s\n” “$_”

    /bin/date

    $ date

    Fri Jun 29 14:40:04 EDT2001

    $ printf “%s\n” “$_”

    date

    你可以使用“$_”来重复上一次的参数。

    getopts命令

    使用定位参数有两个限制,首先,他需要编程者自己测试错误并建立相应的消息。其次,shift命令会删除掉所有的参数,如果你想在以后再次访问他们,将是不可能的。

    为了处理这些问题。Bash包含了一个内置命令getopts,它可以提取并检查开关而不会弄乱定位参数。意外出现的参数或缺少的参数会重新识别并报告错误。

    使用getopts需要坐一些准备工作,首先,你必须定于一个想要使用开关的字符串。通常这个变量称之为OPTSTRING。如果开关需要一个参数,在该开关后加一个冒号。

    例如param2.sh需要-h和-c加上公司标识的参数,OPTSTRING是“hc:”。

    在选项列表后面还需要第二个参数,该参数保存外壳命令当前使用的参数。

    每次getopts运行,命令行的第二个开关将会被检查是否包含在参数列表中,并将名字保存在变量SWITCH中。下一个要检查的参数的位置称之为 OPTING。如果它不存在,OPTING在第一个脚本参数检查之前自动设置为1。如果有参数,他被保存在变量OPTARG中。列表9.3展示一个脚步, 它会测试脚本的第一个参数。

    Listing 9.3 getopts.sh

    #!/bin/bash

    #

    # getopts.sh

    declare SWITCH

    getopts “hc:” SWITCH

    printf “The first switchis SWITCH=%s OPTARG=%s OPTIND=%s\n” \

    “$SWITCH” “$OPTARG”“$OPTIND”

    在这个脚本中,未知的开关被分配一个问号给SWITCH变量,并显示一条错误信息。

    $ bash getopts.sh -h

    The first switch isSWITCH=h OPTARG= OPTIND=2

    $ bash getopts.sh -c a4327

    The first switch isSWITCH=c OPTARG=a4327 OPTIND=3

    $ bash gettopts.sh -a

    t.sh: illegal option --a

    The first switch isSWITCH=? OPTARG= OPTIND=1

    错误信息可以在开关列表的第一字符前加一个冒号进行隐藏,通过使用“:hc:”,使用错误开关-a时就不会显示错误了,但是该错误开关会被保存在OPTARG中,以便自定义错误信息用。

    $ bash getopts.sh -a

    The first switch isSWITCH=? OPTARG=a OPTIND=1

    你也可以通过建立OPTERR变量并赋值为0来隐藏错误消息。它将被合法的开关字符串所覆盖掉。

    开关通常使用while和case语句进行检查,请看列表9.4:

    Listing 9.4 getopts_demo.sh

    # getopts_demo.sh

    #

    # This script expectsthe switch -c and a company name. --help (-h)

    # is also allowed.

    shopt -s -o nounset

    declare -rxSCRIPT=${0##*/}

    declare -rOPTSTRING=”hc:”

    declare SWITCH

    declare COMPANY

    # Make sure there is atleast one parameter

    if [ $# -eq 0 ] ; then

    printf “%s\n” “Type--help for help.”

    exit 192

    fi

    # Examine individualoptions

    while getopts“$OPTSTRING” SWITCH ; do

    case $SWITCH in

    h) printf “%s\n” “usage:$SCRIPT [-h] -c companyid”

    exit 0

    ;;

    c) COMPANY=”$OPTARG”

    ;;

    \?) exit 192

    ;;

    *) printf“$SCRIPT:$LINENO: %s\n” “script error: unhandled argument”

    exit 192

    ;;

    esac

    done

    printf “$SCRIPT: %s\n”“Processing files for $COMPANY...”

    This script is shorterthan the positional

    这个脚本比定位参数的脚本更短,如果getopts出错,switch语句会不运行。

    作为一个特定的情况,如果提供getopts命令作为一个额外的参数,getopts能够处理这些变量而不是脚本参数,这样可以使用特定的参数来测试开关。

    getopt命令

    虽然getopts命令使得脚本的编程稍微容易点,但是它没有遵循Linux开关标准,特别是getopts不允许使用双减号长开关。

    为了绕开这个限制,Linux包含了它自己的getopt命令(注意不是前面的getopts)。同getopts的作用类似,但是getopt可以使用长开关并具有一些getopts没有的特性。它在脚本中以一种完全不同的方法使用。

    因为getopt是一个外部命令,它不能像想getopts那样将开关保存在变量中。它没有办法将环境变量输出回给脚本。

    同样,getopt不知道外壳脚本有哪些开关,除非使用“$@”命令将开关复制给getopt命令。最终,getopt不是使用循环,而是将所有的参数作为单独的一个组进行一次性处理。

    如同getopts,getopt使用OPTSTRING的列表选项,这个列表可以使--options(-o)引导,以便使系统清楚后面是开关的列表,开关可以使用逗号进行分割。

    传递给脚本的选项表必须使用双减号和“$@”追加给getopt命令。双减号表明getopt开关结束的地方和脚本开始的地方。

    列表9.5展示的脚本是使用getopt命令完成getopts.sh一样的功能。注意--name(或者-n)开关用于将脚本的名字传递给getopt命令用在任何错误的消息中。

    Listing 9.5 getopt.sh

    #!/bin/bash

    #

    #getopt.sh – ademonstration of getopt

    declare -rxSCRIPT=${0##*/}

    declare RESULT

    RESULT=’getopt --name“$SCRIPT” --options “-h, -c:” -- “$@”’

    printf “status code=$?result=\”$RESULT\”\n”

    下面是运行程序的结果:

    $ bash getopt.sh -h

    status code=0 result=”-h --”

    $ bash getopt.sh -c

    getopt.sh: optionrequires an argument -- c

    status code=1 result=”--”

    $ bash getopt.sh -x

    getopt.sh: invalidoption -- x

    status code=1 result=”--”

    状态码(status code)表明运行结果是否成功。状态码为1,表示getopt显示错误信息。状态码为2表示给getopt命令的选项有问题。

    长开关使用--longoptions(或者-l)。它包含逗号分隔的长选项列表。例如:允许使用--help则使用下面的语法:

    RESULT=’getopt--name “$SCRIPT” --options “-h, -c:” --longoptions “help” -- “$@”’

    getopt还有一个增强。为了给一个长选项指定一个选项参数,增加一个等号和参数名。

    如果双冒号跟着开关名,它表明该开关是一个可选的参数而不是必需使用的。如果POSIXLY_COMPATIBLE变量存在,选项表以“+”开始。开关不允许使用参数且第一个参数作为开关项目的结束。

    如果GETOPT_COMPATIBLE外壳变量存在,getopt的行为更新C语言标准库中的getopt。一些老版本中的getopt将这种行 为作为缺省值。如果你需要检查这种行为,使用--test(或者-T)开关来测试它的C语言兼容模式:如果不是在兼容模式,状态码返回4。

    在getopt命令检查完开关后要做什么呢?它们使用set命令来替换原始参数。

    evalset – “$RESULT”

    现在参数可以使用定位参数检查也可以使用内置的getopts检查,如列表9.6所示:

    Listing 9.6 getopt_demo.sh

    #!/bin/bash

    #

    # getopt_demo.sh

    #

    # This script expects the switch -c and a companyname. --help (-h)

    # is also allowed.

    shopt -s -o nounset

    declare -rx SCRIPT=${0##*/}

    declare -r OPTSTRING=”-h,-c:”

    declare COMPANY

    declare RESULT

    # Check getopt mode

    getopt -T

    if [ $? -ne 4 ] ; then

    printf “$SCRIPT: %s\n” “getopt is in compatibilitymode” >&2

    exit 192

    fi

    # Test parameters

    RESULT=’getopt --name “$SCRIPT” --options “$OPTSTRING”\

    --longoptions “help” \ -- “$@”’

    if [ $? -gt 0 ] ; then

    exit 192

    fi

    # Replace the parameters with the results of getopt

    eval set -- “$RESULT”

    # Process the parameters

    while [ $# -gt 0 ] ; do

    case “$1”in

    -h | --help) # Show help

    printf “%s\n” “usage: $SCRIPT [-h][--help] -ccompanyid”

    exit 0

    ;;

    -c ) shift

    if [ $# -eq 0 ] ; then

    printf “$SCRIPT:$LINENO: %s\n” “company for -c ismissing” >&2

    exit 192

    fi

    COMPANY=”$1”

    ;;

    esac

    shift

    done

    if [ -z “$COMPANY” ] ; then

    printf “%s\n” “company name missing” >&2

    exit 192

    fi

    printf “$SCRIPT: %s\n” “Processing files for$COMPANY...”

    # <-- begin work here

    exit 0

    看上去好像多做了许多工作,但是当脚本有许多复杂的开关时,getopt使得处理参数变得更容易些。

    还有一些特殊的开关,--alternative(或者-a)开关允许长选项使用一个单独的减号作为前导字符。使用这个开关违背了Linux协议约 定。--quiet-output(或者-Q)可以在检查完后不返回已处理列表给标准输出设备。--quiet(或者-q)表明只返回状态码不返回任何错 误信息,以便你定义自己的错误信息。--shell开关使用引号来保护特定字符。例如空格等。它也许是外壳处理这些字符的一种特殊的方法(只有在C语言兼 容模式才有用)。

    子外壳(subshell)

    第七章中“复合命令”提到的一组命令可以使用大括号组合在一起。这些命令就像被分配给了一个组,而且只返回一个状态码。

    $ { sleep 5 ; printf “%s\n” “Slept for 5 seconds” ;}

    休眠5秒。

    子外壳是使用小括号包含起来的一组命令。和命令组不同,如果子外壳单独占用一行,最后一个命令不需要使用分号。

    $ ( sleep 5 ; printf “%s\n” “Slept for 5 seconds” )

    休眠5秒。

    子外壳就像使用括号括起来的命令组和独立脚本的混合体。象命令组一样它返回单独的状态码,象独立的外壳脚本,它有自己的环境变量。

    $ declare -ix COUNT=15

    $ { COUNT=10 ; printf “%d\n” “$COUNT” ; }

    10

    $ printf “%d\n” “$COUNT”

    10

    $ ( COUNT=20 ; printf “%d\n” “$COUNT” )

    20

    $ printf “%d\n” “$COUNT”

    10

    在这个示例中,命令组可以改变变量COUNT的值,而在子外壳中,没有改变COUNT的值,因为子外壳中的COUNT是父外壳中COUNT的一个副本,其值的变更不影响父外壳中值。

    子外壳通常用于管道的连接。使用管道命令的结果可以重定向到子外壳中处理。这些数据似乎就是子外壳的标准输入,如列表9.7所示:

    Listing 9.7 subshell.sh

    #!/bin/bash

    #

    # subshell.sh

    #

    # Perform some operation to all the files in adirectory

    shopt -s -o nounset

    declare -rx SCRIPT=${0##*/}

    declare -rx INCOMING_DIRECTORY=”incoming”

    ls -1 “$INCOMING_DIRECTORY” |

    (

    while read FILE ; do

    printf “$SCRIPT: Processing %s...\n” “$FILE”

    # <-- do something here

    done

    )

    printf “Done\n”

    exit 0

    read命令一次从标准输入读入一行,在本实例中,它读取有ls命令建立的一个文件列表。

    $ bashsubshell.sh

    subshell.sh: Processing alabama_orders.txt...

    subshell.sh: Processing new_york_orders.txt...

    subshell.sh: Processing ohio_orders.txt...

    Done

    子外壳不仅仅继承了环境变量,更详细的内容参见第14章“函数和脚本的执行”。

    参数处理大大的增加了脚本使用的灵活性,子外壳命令是一个不可缺少的工具。但是在脚本真正的做到完美还有许多基础知识需要掌握。没有作业控制和信号处理的脚本仍不能称之为完美无缺。

    命令参考

    getopt命令开关

    --longoptions(or -l)—期望长选项使用逗号分隔的列表。

    --alternative(or -a)—允许长选项只使用一个单独的减号引导。

    --quiet-output(or -Q)—检查开关并不将处理结果返回到标准输出中。

    --quiet (or -q)—任何错误都不显示出错信息。

    --shell (or -u)—使用引号来保护特定字符。

    --test ( or -T)—用于C语言兼容性的测试。

    来源:http://blog.csdn.net/fox_lht/article/details/7010962


    微信公众号: 猿人谷
    如果您认为阅读这篇博客让您有些收获,不妨点击一下右下角的【推荐】
    如果您希望与我交流互动,欢迎关注微信公众号
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。

  • 相关阅读:
    python编程学习进度二
    python编程学习进度一
    阅读笔记(6)-《高效程序员的45个习惯》
    阅读笔记(5)-《高效程序员的45个习惯》
    阅读笔记(4)-《高效程序员的45个习惯》
    阅读笔记(3)-《高效程序员的45个习惯》
    阅读笔记(2)-《高效程序员的45个习惯》
    寒假生活15
    寒假生活14(补)
    寒假生活13
  • 原文地址:https://www.cnblogs.com/heyonggang/p/2859397.html
Copyright © 2011-2022 走看看