一站式搞定Bash脚本的参数处理问题

以下是来自StackOverflow网站的答案中的代码,写的实在是太好了,引用在这里,以供查阅。


第一段代码,这个例子展示了如何解析处理以空格分隔的参数(例如:–-option argument 这样的传参方式),不使用getopt和getopts函数来实现。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#!/bin/bash

# Usage: /tmp/demo-space-separated.sh -e conf -s /etc -l /usr/lib /etc/hosts

POSITIONAL=()
while [[ $# -gt 0 ]]
do
key="$1"

case $key in
    -e|--extension)
    EXTENSION="$2"
    shift # past argument
    shift # past value
    ;;
    -s|--searchpath)
    SEARCHPATH="$2"
    shift # past argument
    shift # past value
    ;;
    -l|--lib)
    LIBPATH="$2"
    shift # past argument
    shift # past value
    ;;
    --default)
    DEFAULT=YES
    shift # past argument
    ;;
    *)    # unknown option
    POSITIONAL+=("$1") # save it in an array for later
    shift # past argument
    ;;
esac
done

set -- "${POSITIONAL[@]}" # restore positional parameters

echo "FILE EXTENSION  = ${EXTENSION}"
echo "SEARCH PATH     = ${SEARCHPATH}"
echo "LIBRARY PATH    = ${LIBPATH}"
echo "DEFAULT         = ${DEFAULT}"
echo "Number files in SEARCH PATH with EXTENSION:" $(ls -1 "${SEARCHPATH}"/*."${EXTENSION}" | wc -l)
if [[ -n $1 ]]; then
    echo "Last line of file specified as non-opt/last argument:"
    tail -1 "$1"
fi

一些说明:

  • POSITIONAL=()   ---  声明了一个空的数组。详见这里
  • $#  --- 这是一个表示参数个数的特殊变量。详见这里
  • $1, $2  --- 表示第一个参数,第二个参数的特殊变量。当然,$0表示第零个参数,一般保存的是脚本的名称。详见这里
  • “$1”  --- 被双引号用括住的内容,将被视为单一字串。它防止通配符扩展,但允许变量扩展。这点与单引号的处理方式不同。被单引号用括住的内容,将被视为单一字串。在引号内的代表变量的$符号,没有作用,也就是说,它被视为一般符号处理,防止任何变量扩展。详见这里
  • shift --- Shift命令一次移动参数的个数由其所带的参数指定。如果不带参数,则每次运行shift, 销毁一个参数,后面的参数前移。详见这里
  • POSITIONAL+=("$1")  --- 向数组的最末的位置插入一个元素。详情见这里
  • ${POSITIONAL[@]}  --- 这个语法的意思是取回数组中的所有变量,${arr[@]}。详情见这里
  • tail –1 “$1”  --- tail 命令可用于查看文件的内容, tail –1 somefile.txt 会把somefile.txt的最后一行打印在屏幕上。
  • if [[ -n $1 ]];  ---  [[ 可以理解为更高级更现代的[, 即test命令。这里的语法对于用惯了高级语言的程序员会看起来很不习惯,感觉像if的条件里的比较少了一个操作数一样。把双方括号换成test命令就好理解了, 大概是这个样子:if (test –x option) then …
    • test 命令有很多参数.
      • -n 的意思是 “the length of STRING is nonzero”.
      • -f 的意思是 “FILE exists and is a regular file”.


第二段代码,这个例子展示了如何门解析处理以等号分隔的参数(例如:–-option=argument 这样的传参方式),不使用getopt和getopts函数实现。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#!/bin/bash

# Usage: /tmp/demo-equals-separated.sh -e=conf -s=/etc -l=/usr/lib /etc/hosts

for i in "$@"
do
case $i in
    -e=*|--extension=*)
    EXTENSION="${i#*=}"
    shift # past argument=value
    ;;
    -s=*|--searchpath=*)
    SEARCHPATH="${i#*=}"
    shift # past argument=value
    ;;
    -l=*|--lib=*)
    LIBPATH="${i#*=}"
    shift # past argument=value
    ;;
    --default)
    DEFAULT=YES
    shift # past argument with no value
    ;;
    *)
          # unknown option
    ;;
esac
done
echo "FILE EXTENSION  = ${EXTENSION}"
echo "SEARCH PATH     = ${SEARCHPATH}"
echo "LIBRARY PATH    = ${LIBPATH}"
echo "DEFAULT         = ${DEFAULT}"
echo "Number files in SEARCH PATH with EXTENSION:" $(ls -1 "${SEARCHPATH}"/*."${EXTENSION}" | wc -l)
if [[ -n $1 ]]; then
    echo "Last line of file specified as non-opt/last argument:"
    tail -1 $1
fi

一些说明:

  • ${i#*=}  --- 这里的语义通过上下文能看出来是移除了“-e=conf”这样一个字符串里的值为“-e=”的子字符串。简单的语法是${string#substring},即从string的前头开始,删除最短的substring的一个匹配,详情看这里的“Substring Removal”的部分。这个语句中的*是一个通配符(wildcard),*=的意思是任意字符直到遇到等号的子字符串。关于删除子字符串,这里给出了一个删除前缀和后缀的例子。


第三段代码,使用getopts函数实现。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#!/bin/sh

# Usage: /tmp/demo-getopts.sh -vf /etc/hosts foo bar

# A POSIX variable
# Reset in case getopts has been used previously in the shell.
OPTIND=1         

# Initialize our own variables:
output_file=""
verbose=0

while getopts "h?vf:" opt; do
    case "$opt" in
    h|\?)
        show_help
        exit 0
        ;;
    v)  verbose=1
        ;;
    f)  output_file=$OPTARG
        ;;
    esac
done

shift $((OPTIND-1))

echo "verbose=$verbose, output_file='$output_file', Leftovers: $@"

一些说明:

  • $((OPTIND-1)) --- 这条语句中有两个知识点:
    • $(( )) 的功能是进行算术运算,括号中的内容为数学表达式,使用 $(( )) 可以求数学表达式的值。这里的意思就是说会对OPTIND-1这个表达式进行算术运算,用以求值。详情请看这里。
    • OPTIND 函数getopts配合case来进行操作时有两个隐含变量:一个是OPTARG,用来取当前选项的值,另外一个是OPTIND,代表下一个要处理的元素位置。OPTIND是一个特殊的变量,它的初始值是1,每次getopts处理完一个命令参数后就递增它,得到getopts要处理的下一个参数。详情见这里这里
  • getopts "h?vf:"   ---  这里的意思是getops 接受-h –v –f 为合规选项,?问号的意思就是不合规的选项进来的时候,就会执行给问好设定好的代码,程序员可以利用这个机制给用户提供恰当的信息指导。冒号的意思是关掉系统默认的处理不合规的选项的方式(disable the default error handling of invalid options),建议关掉系统默认的处理不合规选项。

对于这个使用getopts的实现方法:

  • 优势:
    • 可移植性更好,其他的类似的shell比如dash,可以兼容。
    • 能自动地处理多重单字母选项,比如-vf filename, 这也是Unix的典型的处理方法。
  • 劣势:
    • 如果不额外添加代码的话,只能处理短选项,比如-h可以,但--help 就不行。


参考资料

==============

https://stackoverflow.com/questions/192249/how-do-i-parse-command-line-arguments-in-bash

http://mywiki.wooledge.org/BashFAQ/035#getopts

posted on 2020-12-15 00:20  中道学友  阅读(100)  评论(0编辑  收藏  举报

导航

技术追求准确,态度积极向上