2017-10-18 63 views
2

我一直在學習命令行參數解析。有長的絲這個了,我並不想在這裏挑起一個:

Using getopts in bash shell script to get long and short command line options

How do I parse command line arguments in Bash?

使用getopts的,如果你想分析的參數/值對,如「--opt值「,一種方法是讓getopts將其視爲名爲」 - 「的參數,並將該值變爲」-opt「。然後我們解析它,並通過符號${!OPTIND}獲取用戶值。我需要更多地瞭解它。

在上面引用的第一個線程中,使用了${!OPTIND},有人說「那是什麼?」。答案是「其間接替代」。在閱讀關於間接參考資料的說明之後,尤其是https://unix.stackexchange.com/questions/41292/variable-substitution-with-an-exclamation-mark-in-bashhttp://mywiki.wooledge.org/BashFAQ/006,我通常理解間接性,但我不明白${!OPTIND}作爲它的一個例子。

$OPTIND的值是一個整數,是下一個命令行參數的索引。這不是另一個數組中的值。

在上面的BashFAQ/006鏈接中,有關於間接性和一般性建議不使用它的警告。也許這沒什麼大不了的,但我希望儘可能避免危險。

我們可以避免間接嗎?似乎我應該能夠使用${OPTIND}作爲一個整數取值從[email protected],[email protected][$OPTIND]}

如果你想要例子,這裏是一個腳本,我稱之爲「cli-6.sh」,它將接收長表單參數沒有等號。像這樣運行:

$ ./cli-6.sh -v --fred good --barney bad --wilma happy 

離開-v以減少冗長。

$ ./cli-6.sh --fred good --barney bad --wilma happy 

After Parsing values, ordinary getopts 

VERBOSE 0 
Arrays of opts and values 
optary: fred barney wilma 
valary: good bad happy 

我們希望,這會運行你太:)我沒有使用關聯數組,因爲我希望這會在其他炮彈工作,最終保存的值。

#/usr/bin/env bash 

die() { 
    printf '%s\n' "$1" >&2 
    exit 1 
} 


printparse(){ 
    if [ ${VERBOSE} -gt 0 ]; then 
     printf 'Parse: %s%s%s\n' "$1" "$2" "$3" >&2; 
    fi 
} 

showme(){ 
    if [ ${VERBOSE} -gt 0 ]; then 
     printf 'VERBOSE: %s\n' "$1" >&2; 
    fi 
} 


VERBOSE=0 

## index for imported values in optary and valary arrays 
idx=0 
## Need v first so VERBOSE is set early 
optspec=":vh-:" 
while getopts "$optspec" OPTCHAR; do 
    case "${OPTCHAR}" in 
     -) 
      showme "OPTARG: ${OPTARG[*]}" 
      showme "OPTIND: ${OPTIND[*]}" 
      showme "OPTCHAR: ${OPTCHAR}" 
      showme "There is no equal sign in ${OPTARG}" 
      opt=${OPTARG} 
      val="${!OPTIND}"; OPTIND=$(($OPTIND + 1)) 
      printparse "--${OPTARG}" " " "\"${val}\"" 
      if [[ "$val" == -* ]]; then 
       die "ERROR: $opt value must be supplied" 
      fi 
      optary[${idx}]=${opt} 
      valary[${idx}]=${val} 
      idx=$(($idx + 1)) 
      ;; 
     h) 
      echo "usage: $0 [-v] [--anyOptYouQant[=]<valueIsRequired>] [--another[=]<value>]" >&2 
      exit 2 
      ;; 
     v) 
      ## if -v flag is present, it means TRUE 
      VERBOSE=1 
      ;; 
     *) 
      if [ "$OPTERR" != 1 ] || [ "${optspec:0:1}" = ":" ]; then 
       die "Undefined argument: '-${OPTARG}'" 
      fi 
      ;; 
    esac 
done 

echo "After Parsing values, ordinary getopts" 
echo "VERBOSE $VERBOSE" 

echo 'Arrays of opts and values' 
echo "optary: ${optary[*]}" 
echo "valary: ${valary[*]}" 
+1

因爲它是一個整數,所以它會引導你到一個位置參數$ 1,$ 2,... –

+0

這似乎是一個非常不正當的手段,用getopts來解析長選項。我會使用['getopt'](http://manpages.ubuntu.com/manpages/xenial/en/man1/getopt.1.html)來代替([示例用法])(https://git.launchpad.net/~usd-import-team/ubuntu/+source/util-linux/tree/misc-utils/getopt-parse.bash)) –

+0

getopt(來自util-linux)在某些平臺上不可用,所以我們選擇不依靠它。 – pauljohn32

回答

0

不知道這是否有助於但這裏是bash的唯一版本的CLI選項解析器不使用getopts的,但接受短期和長期爭論的。它還處理短形式參數的組。這在不支持最新版本的getopts的系統上應該很有用。

#!/bin/bash 
# 
# Copyright (c) 2017 by Joe Linoff 
# MIT Open Source License. 
# 
# This script shows how to implement an argument parser with 
# 4 options. Two of the options are simply flags, one of 
# of them has a single argument and the other has 2 arguments. 
# 
# It is meant to show bash can support reasonably complex 
# argument parsing idioms that will make shell scripts 
# more user friendly without using getopts. It is useful 
# for cases where getopts is not available. 
# 
# The options demonstrated are: 
# 
# 1. -h or --help 
# 2. -v or --verbose 
# 3. -f ARG or --file ARG or --file=ARG 
# 4. -c ARG1 ARG2 or --compare ARG1 ARG2 
# 
# The options parsing allows the following specifications. 
# 
# 1. -h 
# 2. --help 
# 3. -v 
# 4. --verbose 
# 5. -vv 
# 6. -f ARG1 
# 7. --file ARG1 
# 8. --file=ARG1 
# 9. -c ARG1 ARG2 
# 10. --compare ARG1 ARG2 
# 
# This example does not show how to implement best match which would 
# mean accepting an option like "--com" (because it is the best unique 
# match to --compare). That could be added but i am not convinced 
# that it is worth the overhead. 
# 
# The options parser is global in this example because it is setting 
# global (script wide) variables. 

# ======================================================================== 
# Functions 
# ======================================================================== 
# Simple error function that prints the line number of the caller and 
# highlights the message in red. 
function _err() { 
    echo -e "\033[31;1mERROR:\033[0;31m:${BASH_LINENO[0]} $*\033[0m" 
    exit 1 
} 

# ======================================================================== 
# Main 
# ======================================================================== 
CARGS=() 
FILE='' 
HELP=0 
VERBOSE=0 

# The OPT_CACHE is to cache short form options. 
OPT_CACHE=() 
while (($#)) || ((${#OPT_CACHE[@]})) ; do 
    if ((${#OPT_CACHE[@]} > 0)) ; then 
     OPT="${OPT_CACHE[0]}" 
     if ((${#OPT_CACHE[@]} > 1)) ; then 
      OPT_CACHE=(${OPT_CACHE[@]:1}) 
     else 
      OPT_CACHE=() 
     fi 
    else 
     OPT="$1" 
     shift 
    fi 
    case "$OPT" in 
     # Handle the case of multiple short arguments in a single 
     # string: 
     # -abc ==> -a -b -c 
     -[!-][a-zA-Z0-9\-_]*) 
      for ((i=1; i<${#OPT}; i++)) ; do 
       # Note that the leading dash is added here. 
       CHAR=${OPT:$i:1} 
       OPT_CACHE+=("-$CHAR") 
      done 
      ;; 
     -h|--help) 
      ((HELP++)) 
      ;; 
     -v|--verbose) 
      # Increase the verbosity. 
      # Can accept: -v -v OR -vv. 
      ((VERBOSE++)) 
      ;; 
     -f|--file|--file=*) 
      # Can be specified multiple times but we only accept the 
      # last one. 
      # Can accept: --file foo and --file=foo 
      if [ -z "${OPT##*=*}" ] ; then 
       FILE="${OPT#*=}" 
      else 
       FILE="$1" 
       shift 
      fi 
      [[ -z "$FILE" ]] && _err "Missing argument for '$OPT'." 
      ;; 
     -c|--compare) 
      # Can be specified multiple times but we only accept the 
      # last one. 
      # Can accept: 
      # --compare ARG1 ARG2 
      # Cannot accept: 
      # --compare=* 
      # The reason for not accepting the '=' sign is to reduce 
      # complexity because of the ambiguity of separators. If 
      # you decide that you will always use a comma as the 
      # separator, that is fine until one of the arguments 
      # contains a comma. 
      CARG1="$1" 
      CARG2="$2" 
      shift 2 
      [[ -z "$CARG1" ]] && _err "Missing both arguments for '$OPT'." 
      [[ -z "$CARG2" ]] && _err "Missing second argument for '$OPT'." 
      CARGS=() 
      CARGS+=("$CARG1") 
      CARGS+=("$CARG2") 
      ;; 
     -*) 
      _err "Unrecognized option '$OPT'." 
      ;; 
     *) 
      _err "Unrecognized argument '$OPT'." 
      ;; 
    esac 
done 

echo "COMPARE : ${CARGS[@]}" 
echo "FILE : ${FILE}" 
echo "HELP : ${HELP}" 
echo "VERBOSE : ${VERBOSE}" 

該代碼也可從https://gist.github.com/jlinoff/1876972c0b37259c82367d51c8313171獲得。