在 Bash 中解析命令行参数的最佳方法是啥?

Posted

技术标签:

【中文标题】在 Bash 中解析命令行参数的最佳方法是啥?【英文标题】:Best way to parse command line args in Bash?在 Bash 中解析命令行参数的最佳方法是什么? 【发布时间】:2013-01-25 01:40:26 【问题描述】:

经过几天research,我仍然想不出在.sh 脚本中解析cmdline args 的最佳方法。根据我的参考,getopts cmd 是可行的方法,因为它“在不干扰位置参数变量的情况下提取和检查开关。意外开关或缺少参数的开关被识别并报告为错误。

位置参数(例如 2 - $@、$# 等)在涉及空格时显然不能很好地工作,但可以识别常规参数和长参数(-p 和 --longparam)。我注意到在传递带有嵌套引号的参数时,这两种方法都会失败(“this is an Ex. of ""quotes"".")。这三个代码示例中的哪一个最能说明处理 cmdline args 的方式? getopt 功能不被大师推荐,所以我试图避免它!

示例 1:

#!/bin/bash
for i in "$@"
do
case $i in
    -p=*|--prefix=*)
    PREFIX=`echo $i | sed 's/[-a-zA-Z0-9]*=//'`

    ;;
    -s=*|--searchpath=*)
    SEARCHPATH=`echo $i | sed 's/[-a-zA-Z0-9]*=//'`
    ;;
    -l=*|--lib=*)
    DIR=`echo $i | sed 's/[-a-zA-Z0-9]*=//'`
    ;;
    --default)
    DEFAULT=YES
    ;;
    *)
            # unknown option
    ;;
esac
done
exit 0

示例 2:

#!/bin/bash
echo ‘number of arguments’
echo "\$#: $#"
echo ”

echo ‘using $num’
echo "\$0: $0"
if [ $# -ge 1 ];then echo "\$1: $1"; fi
if [ $# -ge 2 ];then echo "\$2: $2"; fi
if [ $# -ge 3 ];then echo "\$3: $3"; fi
if [ $# -ge 4 ];then echo "\$4: $4"; fi
if [ $# -ge 5 ];then echo "\$5: $5"; fi
echo ”

echo ‘using $@’
let i=1
for x in $@; do
echo "$i: $x"
let i=$i+1
done
echo ”

echo ‘using $*’
let i=1
for x in $*; do
echo "$i: $x"
let i=$i+1
done
echo ”

let i=1
echo ‘using shift’
while [ $# -gt 0 ]
do
echo "$i: $1"
let i=$i+1
shift
done

[/bash]

output:

bash> commandLineArguments.bash
number of arguments
$#: 0

using $num
$0: ./commandLineArguments.bash

using $@

using $*

using shift
#bash> commandLineArguments.bash  "abc def" g h i j*

示例 3:

#!/bin/bash

while getopts ":a:" opt; do
  case $opt in
    a)
      echo "-a was triggered, Parameter: $OPTARG" >&2
      ;;
    \?)
      echo "Invalid option: -$OPTARG" >&2
      exit 1
      ;;
    :)
      echo "Option -$OPTARG requires an argument." >&2
      exit 1
      ;;
  esac
done

exit 0

【问题讨论】:

How do I parse command line arguments in bash?的可能重复 【参考方案1】:

我发现使用getopt 是最简单的。它提供了对参数的正确处理,否则会很棘手。例如,getopt 将知道如何处理在命令行中指定为 --arg=option--arg option 的长选项的参数。

解析传递给 shell 脚本的任何输入的有用之处在于使用 "$@" 变量。请参阅 bash 手册页了解这与 $@ 有何不同。它确保您可以处理包含空格的参数。

下面是我如何编写脚本来解析一些简单的命令行参数的示例:

#!/bin/bash

args=$(getopt -l "searchpath:" -o "s:h" -- "$@")

eval set -- "$args"

while [ $# -ge 1 ]; do
        case "$1" in
                --)
                    # No more options left.
                    shift
                    break
                   ;;
                -s|--searchpath)
                        searchpath="$2"
                        shift
                        ;;
                -h)
                        echo "Display some help"
                        exit 0
                        ;;
        esac

        shift
done

echo "searchpath: $searchpath"
echo "remaining args: $*"

并像这样使用来表明保留了空格和引号:

user@machine:~/bin$ ./getopt_test --searchpath "File with spaces and \"quotes\"."
searchpath: File with spaces and "quotes".
remaining args: other args

getopt的一些基本使用信息可以查看here

【讨论】:

这是 getopt Austin 的一个很好的例子。这个特定主题已在*** 上进行了广泛讨论。不同之处在于普通的旧 getopt 不如 getopts 强大,并且不适用于旧系统。只要有相同开关的短版本,Getopts 就可以解析长开关,因此需要进行一些调整。我会坚持使用 getopts。对于专业脚本,我更愿意使用内置函数而不是旧的 exec (getopt)。【参考方案2】:

如果你想避免使用getopt,你可以使用这个不错的快速方法:

将所有选项的帮助定义为## cmets(根据需要自定义)。 为每个选项定义一个同名的函数。 将此脚本的最后五行复制到您的脚本中(魔法)。

示例脚本:log.sh

#!/bin/sh
## $PROG 1.0 - Print logs [2017-10-01]
## Compatible with bash and dash/POSIX
## 
## Usage: $PROG [OPTION...] [COMMAND]...
## Options:
##   -i, --log-info         Set log level to info (default)
##   -q, --log-quiet        Set log level to quiet
##   -l, --log MESSAGE      Log a message
## Commands:
##   -h, --help             Displays this help and exists
##   -v, --version          Displays output version and exists
## Examples:
##   $PROG -i myscrip-simple.sh > myscript-full.sh
##   $PROG -r myscrip-full.sh   > myscript-simple.sh
PROG=$0##*/
LOG=info
die()  echo $@ >&2; exit 2; 

log_info() 
  LOG=info

log_quiet() 
  LOG=quiet

log() 
  [ $LOG = info ] && echo "$1"; return 1 ## number of args used

help() 
  grep "^##" "$0" | sed -e "s/^...//" -e "s/\$PROG/$PROG/g"; exit 0

version() 
  help | head -1


[ $# = 0 ] && help
while [ $# -gt 0 ]; do
  CMD=$(grep -m 1 -Po "^## *$1, --\K[^= ]*|^##.* --\K$1#--(?:[= ])" log.sh | sed -e "s/-/_/g")
  if [ -z "$CMD" ]; then echo "ERROR: Command '$1' not supported"; exit 1; fi
  shift; eval "$CMD" $@ || shift $? 2> /dev/null
done

测试

运行这个命令:

./log.sh --log yep --log-quiet -l nop -i -l yes

产生这个输出:

yep
yes

顺便说一句:兼容posix!

【讨论】:

非常有用的脚本,除了在使用引号时不会正确生成参数,例如:./log.sh --log "string in quotes" 输出string。您可能希望将此行更改为:shift; eval "$CMD" $@ || shift $? 2> /dev/nullshift; $CMD "$@" || shift $? 2> /dev/null 以进行正确的报价处理,更改似乎正常运行。

以上是关于在 Bash 中解析命令行参数的最佳方法是啥?的主要内容,如果未能解决你的问题,请参考以下文章

解析命令行参数的最佳方法是啥? [关闭]

在 C# 中处理命令行选项的最佳方法 [重复]

在 C++ 中解析命令行参数? [关闭]

在 C++ 中解析命令行参数? [关闭]

Bash编程 命令行解析与扩展

解析/传递命令行参数到bash脚本 - “$ @”和“$ *”之间有什么区别?