2013-05-10 157 views
181

我想打電話給myscript文件以這種方式使用getopts的一個例子:如何在bash

$ ./myscript -s 45 -p any_string 

$ ./myscript -h >>> should display help 
$ ./myscript >>> should display help 

我的要求是:

  • getopt這裏得到輸入參數
  • 檢查-s存在,如果不返回錯誤
  • 檢查-s後的值爲45或90
  • 檢查-p存在,並且如果用戶輸入./myscript -h或只是./myscript然後顯示幫助有後
  • 輸入字符串

我試過到目前爲止這樣的代碼:

#!/bin/bash 
while getopts "h:s:" arg; do 
    case $arg in 
    h) 
     echo "usage" 
     ;; 
    s) 
     strength=$OPTARG 
     echo $strength 
     ;; 
    esac 
done 

但與代碼我得到錯誤。如何用Bash和getopt做到這一點?

+6

[本教程](HTTP://維基。 bash-hackers.org/howto/getopts_tutorial)可能是一個很好的開始。 – jam 2013-05-10 13:18:50

+1

選項應該是可選的。如果你需要由'-s'指定的值,則使它成爲一個位置參數:'./myscript 45 anystring'。 – chepner 2013-05-10 13:19:04

+0

@chepner'$。/ myscript -s 45 -p any_string' – MOHAMED 2013-05-10 13:21:10

回答

316
#!/bin/bash 

usage() { echo "Usage: $0 [-s <45|90>] [-p <string>]" 1>&2; exit 1; } 

while getopts ":s:p:" o; do 
    case "${o}" in 
     s) 
      s=${OPTARG} 
      ((s == 45 || s == 90)) || usage 
      ;; 
     p) 
      p=${OPTARG} 
      ;; 
     *) 
      usage 
      ;; 
    esac 
done 
shift $((OPTIND-1)) 

if [ -z "${s}" ] || [ -z "${p}" ]; then 
    usage 
fi 

echo "s = ${s}" 
echo "p = ${p}" 

示例運行:

$ ./myscript.sh 
Usage: ./myscript.sh [-s <45|90>] [-p <string>] 

$ ./myscript.sh -h 
Usage: ./myscript.sh [-s <45|90>] [-p <string>] 

$ ./myscript.sh -s "" -p "" 
Usage: ./myscript.sh [-s <45|90>] [-p <string>] 

$ ./myscript.sh -s 10 -p foo 
Usage: ./myscript.sh [-s <45|90>] [-p <string>] 

$ ./myscript.sh -s 45 -p foo 
s = 45 
p = foo 

$ ./myscript.sh -s 90 -p bar 
s = 90 
p = bar 
+8

在getopts調用中,爲什麼會有一個冒號? 「h」後面什麼時候冒號? – e40 2013-08-14 14:43:22

+0

@ e40好,趕快,謝謝!可能是'h'上的複製粘貼錯誤。領先的冒號在'getopts'手冊頁中解釋。嘗試使用'$ ./myscript.sh -s 45 -p',不同之處在於它們之間的差異是顯而易見的。無論你想使用這個功能取決於你,你想如何處理整個事情。 – 2013-08-14 15:02:41

+18

@ e40前導冒號打開靜默錯誤報告。 – Chris 2013-08-18 16:06:32

26

getopt封裝(我的發行版把它放在/usr/share/getopt/getopt-parse.bash)的例子看起來像它涵蓋了所有的情況:

#!/bin/bash 

# A small example program for using the new getopt(1) program. 
# This program will only work with bash(1) 
# An similar program using the tcsh(1) script language can be found 
# as parse.tcsh 

# Example input and output (from the bash prompt): 
# ./parse.bash -a par1 'another arg' --c-long 'wow!*\?' -cmore -b " very long " 
# Option a 
# Option c, no argument 
# Option c, argument `more' 
# Option b, argument ` very long ' 
# Remaining arguments: 
# --> `par1' 
# --> `another arg' 
# --> `wow!*\?' 

# Note that we use `"[email protected]"' to let each command-line parameter expand to a 
# separate word. The quotes around `[email protected]' are essential! 
# We need TEMP as the `eval set --' would nuke the return value of getopt. 
TEMP=`getopt -o ab:c:: --long a-long,b-long:,c-long:: \ 
    -n 'example.bash' -- "[email protected]"` 

if [ $? != 0 ] ; then echo "Terminating..." >&2 ; exit 1 ; fi 

# Note the quotes around `$TEMP': they are essential! 
eval set -- "$TEMP" 

while true ; do 
    case "$1" in 
     -a|--a-long) echo "Option a" ; shift ;; 
     -b|--b-long) echo "Option b, argument \`$2'" ; shift 2 ;; 
     -c|--c-long) 
      # c has an optional argument. As we are in quoted mode, 
      # an empty parameter will be generated if its optional 
      # argument is not found. 
      case "$2" in 
       "") echo "Option c, no argument"; shift 2 ;; 
       *) echo "Option c, argument \`$2'" ; shift 2 ;; 
      esac ;; 
     --) shift ; break ;; 
     *) echo "Internal error!" ; exit 1 ;; 
    esac 
done 
echo "Remaining arguments:" 
for arg do echo '--> '"\`$arg'" ; done 
+9

外部命令getopt(1)永遠不會安全使用,除非你知道它是GNU getopt,你可以用GNU特有的方式調用它*,並確保GETOPT_COMPATIBLE不在環境中。改用getopts(shell builtin),或者簡單地遍歷位置參數。 – 2013-08-14 16:52:54

+0

@sputnick,tyvm,不知道這一點。 – 2013-08-14 17:38:19

+11

呃,沒有外部命令可以安全地使用該標準。內置getopts缺少關鍵功能,如果您想檢查GETOPT_COMPATIBLE比移植getopt的功能更容易。 – 2014-09-12 18:26:35

9

我知道,這已經回答了,但是爲了記錄以及對於和我有同樣重要性的人,我決定發佈這個相關的答案。代碼充斥着註釋來解釋代碼。

更新答案:

將文件保存爲getopt.sh

#!/bin/bash 

function get_variable_name_for_option { 
    local OPT_DESC=${1} 
    local OPTION=${2} 
    local VAR=$(echo ${OPT_DESC} | sed -e "s/.*\[\?-${OPTION} \([A-Z_]\+\).*/\1/g" -e "s/.*\[\?-\(${OPTION}\).*/\1FLAG/g") 

    if [[ "${VAR}" == "${1}" ]]; then 
     echo "" 
    else 
     echo ${VAR} 
    fi 
} 

function parse_options { 
    local OPT_DESC=${1} 
    local INPUT=$(get_input_for_getopts "${OPT_DESC}") 

    shift 
    while getopts ${INPUT} OPTION ${@}; 
    do 
     [ ${OPTION} == "?" ] && usage 
     VARNAME=$(get_variable_name_for_option "${OPT_DESC}" "${OPTION}") 
      [ "${VARNAME}" != "" ] && eval "${VARNAME}=${OPTARG:-true}" # && printf "\t%s\n" "* Declaring ${VARNAME}=${!VARNAME} -- OPTIONS='$OPTION'" 
    done 

    check_for_required "${OPT_DESC}" 

} 

function check_for_required { 
    local OPT_DESC=${1} 
    local REQUIRED=$(get_required "${OPT_DESC}" | sed -e "s/\://g") 
    while test -n "${REQUIRED}"; do 
     OPTION=${REQUIRED:0:1} 
     VARNAME=$(get_variable_name_for_option "${OPT_DESC}" "${OPTION}") 
       [ -z "${!VARNAME}" ] && printf "ERROR: %s\n" "Option -${OPTION} must been set." && usage 
     REQUIRED=${REQUIRED:1} 
    done 
} 

function get_input_for_getopts { 
    local OPT_DESC=${1} 
    echo ${OPT_DESC} | sed -e "s/\([a-zA-Z]\) [A-Z_]\+/\1:/g" -e "s/[][ -]//g" 
} 

function get_optional { 
    local OPT_DESC=${1} 
    echo ${OPT_DESC} | sed -e "s/[^[]*\(\[[^]]*\]\)[^[]*/\1/g" -e "s/\([a-zA-Z]\) [A-Z_]\+/\1:/g" -e "s/[][ -]//g" 
} 

function get_required { 
    local OPT_DESC=${1} 
    echo ${OPT_DESC} | sed -e "s/\([a-zA-Z]\) [A-Z_]\+/\1:/g" -e "s/\[[^[]*\]//g" -e "s/[][ -]//g" 
} 

function usage { 
    printf "Usage:\n\t%s\n" "${0} ${OPT_DESC}" 
    exit 10 
} 

然後你可以使用它像這樣:

#!/bin/bash 
# 
# [ and ] defines optional arguments 
# 

# location to getopts.sh file 
source ./getopt.sh 
USAGE="-u USER -d DATABASE -p PASS -s SID [ -a START_DATE_TIME ]" 
parse_options "${USAGE}" ${@} 

echo ${USER} 
echo ${START_DATE_TIME} 

老答案:

我最近需要使用通用方法。我碰到這個解決方案:

#!/bin/bash 
# Option Description: 
# ------------------- 
# 
# Option description is based on getopts bash builtin. The description adds a variable name feature to be used 
# on future checks for required or optional values. 
# The option description adds "=>VARIABLE_NAME" string. Variable name should be UPPERCASE. Valid characters 
# are [A-Z_]*. 
# 
# A option description example: 
# OPT_DESC="a:=>A_VARIABLE|b:=>B_VARIABLE|c=>C_VARIABLE" 
# 
# -a option will require a value (the colon means that) and should be saved in variable A_VARIABLE. 
# "|" is used to separate options description. 
# -b option rule applies the same as -a. 
# -c option doesn't require a value (the colon absense means that) and its existence should be set in C_VARIABLE 
# 
# ~$ echo get_options ${OPT_DESC} 
# a:b:c 
# ~$ 
# 


# Required options 
REQUIRED_DESC="a:=>REQ_A_VAR_VALUE|B:=>REQ_B_VAR_VALUE|c=>REQ_C_VAR_FLAG" 

# Optional options (duh) 
OPTIONAL_DESC="P:=>OPT_P_VAR_VALUE|r=>OPT_R_VAR_FLAG" 

function usage { 
    IFS="|" 
    printf "%s" ${0} 
    for i in ${REQUIRED_DESC}; 
    do 
     VARNAME=$(echo $i | sed -e "s/.*=>//g") 
    printf " %s" "-${i:0:1} $VARNAME" 
    done 

    for i in ${OPTIONAL_DESC}; 
    do 
     VARNAME=$(echo $i | sed -e "s/.*=>//g") 
     printf " %s" "[-${i:0:1} $VARNAME]" 
    done 
    printf "\n" 
    unset IFS 
    exit 
} 

# Auxiliary function that returns options characters to be passed 
# into 'getopts' from a option description. 
# Arguments: 
# $1: The options description (SEE TOP) 
# 
# Example: 
# OPT_DESC="h:=>H_VAR|f:=>F_VAR|P=>P_VAR|W=>W_VAR" 
# OPTIONS=$(get_options ${OPT_DESC}) 
# echo "${OPTIONS}" 
# 
# Output: 
# "h:f:PW" 
function get_options { 
    echo ${1} | sed -e "s/\([a-zA-Z]\:\?\)=>[A-Z_]*|\?/\1/g" 
} 

# Auxiliary function that returns all variable names separated by '|' 
# Arguments: 
#  $1: The options description (SEE TOP) 
# 
# Example: 
#  OPT_DESC="h:=>H_VAR|f:=>F_VAR|P=>P_VAR|W=>W_VAR" 
#  VARNAMES=$(get_values ${OPT_DESC}) 
#  echo "${VARNAMES}" 
# 
# Output: 
#  "H_VAR|F_VAR|P_VAR|W_VAR" 
function get_variables { 
    echo ${1} | sed -e "s/[a-zA-Z]\:\?=>\([^|]*\)/\1/g" 
} 

# Auxiliary function that returns the variable name based on the 
# option passed by. 
# Arguments: 
# $1: The options description (SEE TOP) 
# $2: The option which the variable name wants to be retrieved 
# 
# Example: 
# OPT_DESC="h:=>H_VAR|f:=>F_VAR|P=>P_VAR|W=>W_VAR" 
# H_VAR=$(get_variable_name ${OPT_DESC} "h") 
# echo "${H_VAR}" 
# 
# Output: 
# "H_VAR" 
function get_variable_name { 
    VAR=$(echo ${1} | sed -e "s/.*${2}\:\?=>\([^|]*\).*/\1/g") 
    if [[ ${VAR} == ${1} ]]; then 
     echo "" 
    else 
     echo ${VAR} 
    fi 
} 

# Gets the required options from the required description 
REQUIRED=$(get_options ${REQUIRED_DESC}) 

# Gets the optional options (duh) from the optional description 
OPTIONAL=$(get_options ${OPTIONAL_DESC}) 

# or... $(get_options "${OPTIONAL_DESC}|${REQUIRED_DESC}") 

# The colon at starts instructs getopts to remain silent 
while getopts ":${REQUIRED}${OPTIONAL}" OPTION 
do 
    [[ ${OPTION} == ":" ]] && usage 
    VAR=$(get_variable_name "${REQUIRED_DESC}|${OPTIONAL_DESC}" ${OPTION}) 
    [[ -n ${VAR} ]] && eval "$VAR=${OPTARG}" 
done 

shift $(($OPTIND - 1)) 

# Checks for required options. Report an error and exits if 
# required options are missing. 

# Using function version ... 
VARS=$(get_variables ${REQUIRED_DESC}) 
IFS="|" 
for VARNAME in $VARS; 
do 
    [[ -v ${VARNAME} ]] || usage 
done 
unset IFS 

# ... or using IFS Version (no function) 
OLDIFS=${IFS} 
IFS="|" 
for i in ${REQUIRED_DESC}; 
do 
    VARNAME=$(echo $i | sed -e "s/.*=>//g") 
    [[ -v ${VARNAME} ]] || usage 
    printf "%s %s %s\n" "-${i:0:1}" "${!VARNAME:=present}" "${VARNAME}" 
done 
IFS=${OLDIFS} 

我沒有測試這個粗略的,所以我可能有一些錯誤在那裏。

34

與原代碼的問題是:

  • h:預計參數,它應該沒有,所以它變成只是h(沒有冒號)
  • 期待-p any_string,則需要添加p:參數列表

getopts基本語法是(見:man bash):

getopts OPTSTRING VARNAME [ARGS...] 

其中:

  • OPTSTRING是字符串預計參數列表,

    • h - 檢查選項-h沒有參數;在不支持的選項上出錯;
    • h: - 檢查選項-h參數;在不支持的選項上出錯;
    • abc - 檢查選項-a-b,-c; 在不支持的選項上給出錯誤;
    • :abc - 檢查選項-a-b,-c; 沉默不支持的選項上的錯誤;

      注:換句話說,選項前面的冒號允許您處理代碼中的錯誤。在不支持選項的情況下,變量將包含?,缺失值時包含:

  • OPTARG - 被設定爲當前參數值,

  • OPTERR - 指示是否應該擊顯示錯誤消息。

因此,代碼可以是:

#!/usr/bin/env bash 
usage() { echo "$0 usage:" && grep " .)\ #" $0; exit 0; } 
[ $# -eq 0 ] && usage 
while getopts ":hs:p:" arg; do 
    case $arg in 
    p) # Specify p value. 
     echo "p is ${OPTARG}" 
     ;; 
    s) # Specify strength, either 45 or 90. 
     strength=${OPTARG} 
     [ $strength -eq 45 -o $strength -eq 90 ] \ 
     && echo "Strength is $strength." \ 
     || echo "Strength needs to be either 45 or 90, $strength found instead." 
     ;; 
    h | *) # Display help. 
     usage 
     exit 0 
     ;; 
    esac 
done 

實例:

$ ./foo.sh 
./foo.sh usage: 
    p) # Specify p value. 
    s) # Specify strength, either 45 or 90. 
    h | *) # Display help. 
$ ./foo.sh -s 123 -p any_string 
Strength needs to be either 45 or 90, 123 found instead. 
p is any_string 
$ ./foo.sh -s 90 -p any_string 
Strength is 90. 
p is any_string 

參見:Small getopts tutorial在打擊黑客維基

+1

將使用函數更改爲:usage(){echo「$ 0 usage:」&& grep「[[:space:]]。)\#」$ 0 | sed's /#//'| sed -r's /([a-z])\)/ - \ 1 /';退出0; }'。它只在字母選項之前佔用一個空白字符,在註釋中刪除#並在字母選項之前加上' - ',使其更清晰。 – poagester 2016-10-05 19:55:26

+1

@kenorb:選項前面的冒號不會忽略不受支持的選項,但可以避免bash中的錯誤,並允許您在代碼中處理它。變量將包含'?'在不支持的選項的情況下,在缺少值的情況下爲「:」。 – 2017-02-06 07:42:25