Một ví dụ về cách sử dụng getopts trong bash


345

Tôi muốn gọi myscripttập tin theo cách này:

$ ./myscript -s 45 -p any_string

hoặc là

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

Yêu cầu của tôi là:

  • getopt ở đây để có được các đối số đầu vào
  • kiểm tra xem có -stồn tại không, nếu không trả về lỗi
  • kiểm tra xem giá trị sau -slà 45 hay 90
  • kiểm tra xem có -ptồn tại không và có một chuỗi đầu vào sau
  • nếu người dùng nhập ./myscript -hhoặc chỉ ./myscriptsau đó hiển thị trợ giúp

Tôi đã thử mã này cho đến nay:

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

Nhưng với mã đó tôi nhận được lỗi. Làm thế nào để làm điều đó với Bash và getopt?


2
Tùy chọn được cho là tùy chọn. Nếu bạn yêu cầu giá trị được chỉ định bởi -s, hãy đặt nó làm đối số vị trí : ./myscript 45 anystring.
chepner

@chepner$./myscript -s 45 -p any_string
MOHAMED

Sẽ tốt nếu -pthực sự là một tùy chọn (nghĩa là chương trình của bạn có thể tiến hành nếu không có mặt). Trong trường hợp này , ./myscript 45 -p any_string. (Tôi nghĩ rằng getoptcó thể xử lý các tùy chọn hỗn hợp và đối số vị trí, trong khi bashlệnh tích hợp getoptsyêu cầu tất cả các đối số vị trí được đặt sau các tùy chọn.)
chepner

Câu trả lời:


513
#!/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}"

Ví dụ chạy:

$ ./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

19
Trong cuộc gọi getopts, tại sao có một dấu hai chấm hàng đầu? Khi nào "h" có dấu hai chấm sau nó?
e40

7
usage()thực sự nên trả lại 1?
Pithikos

6
@Pithikos Điểm tốt. Thông thường cho tôi biết rằng khi được gọi qua -hnó sẽ quay trở lại 0, khi nhấn một cờ không tồn tại, nó sẽ trả về >0(vì đơn giản tôi không phân biệt giữa các trường hợp đó và không ai bắt bạn phải in văn bản sử dụng trong trường hợp sau) . Tôi đã thấy các chương trình luôn luôn quay trở lại != 0, mặc dù, thậm chí trên -h/--help. Có lẽ tôi nên cập nhật đoạn trích trong trường hợp mọi người sử dụng đoạn mã này như bản tóm tắt (tôi hy vọng là không)?
Adrian Frühwirth

1
@ A.Danischewski Đây là do ( getopts') thiết kế, không có thứ gọi là "đối số tùy chọn" với getopts. Trình phân tích cú pháp đơn giản là không thể biết liệu mã thông báo tiếp theo là một đối số cho tùy chọn hiện tại hay chính tùy chọn đó -pcó thể là giá trị dự định. Bạn có thể hack xung quanh điều này nếu bạn hoàn toàn biết rằng một tham số tùy chọn không thể trông giống như một tùy chọn hợp lệ khác, vâng, nhưng người ta có thể nói rằng có một lý do đối số tùy chọn không được xác định trong POSIX.
Adrian Frühwirth

4
@ user1011471 Bạn đã đúng! Niềng răng xoăn, có thể nói, chỉ cần hỗ trợ các nhà từ bashvựng trong việc xác định các biến. Trong nhiều trường hợp chúng không cần thiết và thực tế là tôi luôn sử dụng chúng chỉ là vấn đề của phong cách mã hóa cá nhân. Đối với tôi, sẽ dễ dàng hơn (và đẹp hơn) khi luôn luôn sử dụng chúng thay vì ghi nhớ các quy tắc phân tích cú pháp liên quan đến sự mơ hồ. Khá giống nhau tại sao người ta sẽ viết if (foo) { bar; }thay vì if (foo) bar;bằng ngôn ngữ kiểu C (tính thẩm mỹ và / hoặc tránh những sai lầm ngớ ngẩn).
Adrian Frühwirth

109

Vấn đề với mã gốc là:

  • h:mong đợi tham số không nên, vì vậy hãy thay đổi nó thành chỉ h(không có dấu hai chấm)
  • để mong đợi -p any_string, bạn cần thêm p:vào danh sách đối số

Về cơ bản :sau tùy chọn có nghĩa là nó yêu cầu đối số.


Cú pháp cơ bản của getoptslà (xem man bash:):

getopts OPTSTRING VARNAME [ARGS...]

Ở đâu:

  • OPTSTRING là chuỗi với danh sách các đối số dự kiến,

    • h- kiểm tra tùy chọn -h không có tham số; đưa ra lỗi về các tùy chọn không được hỗ trợ;
    • h:- kiểm tra tùy chọn -h với tham số; đưa ra lỗi về các tùy chọn không được hỗ trợ;
    • abc- kiểm tra cho các tùy chọn -a, -b, -c; đưa ra lỗi về các tùy chọn không được hỗ trợ;
    • :abc- kiểm tra cho các tùy chọn -a, -b, -c; lỗi im lặng trên các tùy chọn không được hỗ trợ;

      Ghi chú: Nói cách khác, dấu hai chấm trước các tùy chọn cho phép bạn xử lý các lỗi trong mã của mình. Biến sẽ chứa ?trong trường hợp tùy chọn không được hỗ trợ, :trong trường hợp thiếu giá trị.

  • OPTARG - được đặt thành giá trị đối số hiện tại,

  • OPTERR - cho biết nếu Bash sẽ hiển thị thông báo lỗi.

Vì vậy, mã có thể là:

#!/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

Ví dụ sử dụng:

$ ./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

Xem: Hướng dẫn getopts nhỏ tại Bash Hackers Wiki


2
Thay đổi chức năng sử dụng thành này : usage() { echo "$0 usage:" && grep "[[:space:]].)\ #" $0 | sed 's/#//' | sed -r 's/([a-z])\)/-\1/'; exit 0; }. Nó chỉ chiếm một ký tự khoảng trắng trước tùy chọn chữ cái, xóa # khỏi nhận xét và đặt dấu '-' trước tùy chọn chữ cái làm cho lệnh rõ ràng hơn.
poagester

2
@kenorb: Dấu hai chấm trước các tùy chọn không bỏ qua các tùy chọn không được hỗ trợ nhưng làm im lặng các lỗi từ bash và cho phép bạn xử lý nó trong mã của mình. Biến sẽ chứa '?' trong trường hợp tùy chọn không được hỗ trợ và ':' trong trường hợp thiếu giá trị.
Hynek -Pichi- Vychodil 6/2/2017

1
Cảm ơn các tài liệu chi tiết, đã không thể có được :quyền cho đến khi tôi thấy những ghi chú này. Chúng ta cần thêm một :vào các tùy chọn mà chúng ta đang mong đợi một đối số.
Aukhan

51

Sử dụng getopt

Tại sao lại nhận được?

Để phân tích các đối số dòng lệnh được xây dựng để tránh nhầm lẫn và làm rõ các tùy chọn chúng tôi đang phân tích cú pháp để người đọc các lệnh có thể hiểu những gì đang xảy ra.

Getopt là gì?

getoptđược sử dụng để chia nhỏ các tùy chọn (phân tích cú pháp) trong các dòng lệnh để dễ dàng phân tích cú pháp bằng các thủ tục shell và để kiểm tra các tùy chọn pháp lý. Nó sử dụng các getopt(3)thường trình GNU để làm điều này.

getopt có thể có các loại tùy chọn sau.

  1. Tùy chọn không có giá trị
  2. tùy chọn cặp khóa-giá trị

Lưu ý: Trong tài liệu này, trong khi giải thích cú pháp:

  • Bất cứ điều gì bên trong [] là tham số tùy chọn trong cú pháp / ví dụ.
  • là một người giữ chỗ, có nghĩa là nó nên được thay thế bằng một giá trị thực tế.

CÁCH SỬ DỤNG getopt?

Cú pháp: Mẫu đầu tiên

getopt optstring parameters

Ví dụ:

# This is correct
getopt "hv:t::" "-v 123 -t123"  
getopt "hv:t::" "-v123 -t123"  # -v and 123 doesn't have whitespace

# -h takes no value.
getopt "hv:t::" "-h -v123"


# This is wrong. after -t can't have whitespace.
# Only optional params cannot have whitespace between key and value
getopt "hv:t::" "-v 123 -t 123"

# Multiple arguments that takes value.
getopt "h:v:t::g::" "-h abc -v 123 -t21"

# Multiple arguments without value
# All of these are correct
getopt "hvt" "-htv"
getopt "hvt" "-h -t -v"
getopt "hvt" "-tv -h"

Ở đây h, v, t là các tùy chọn và -h -v -t là cách các tùy chọn nên được đưa ra trong dòng lệnh.

  1. 'h' là một tùy chọn không có giá trị.
  2. 'v:' ngụ ý rằng tùy chọn -v có giá trị và là tùy chọn bắt buộc. ':' có nghĩa là có một giá trị.
  3. 't ::' ngụ ý rằng tùy chọn -t có giá trị nhưng là tùy chọn. '::' có nghĩa là tùy chọn.

Trong tùy chọn param, giá trị không thể có sự phân tách khoảng trắng với tùy chọn. Vì vậy, trong ví dụ "-t123", -t là tùy chọn 123 là giá trị.

Cú pháp: Hình thức thứ hai

getopt [getopt_options] [--] [optstring] [parameters]

Ở đây sau khi getopt được chia thành năm phần

  • Bản thân lệnh tức là getopt
  • Getopt_options, nó mô tả cách phân tích các đối số. tùy chọn dấu gạch ngang dài, tùy chọn dấu gạch ngang kép.
  • -, tách các getopt_options khỏi các tùy chọn bạn muốn phân tích và các tùy chọn ngắn được phép
  • Các tùy chọn ngắn, được thực hiện ngay sau đó - được tìm thấy. Giống như cú pháp đầu tiên của Form.
  • Các tham số, đây là các tùy chọn mà bạn đã truyền vào chương trình. Các tùy chọn bạn muốn phân tích cú pháp và lấy các giá trị thực tế được đặt trên chúng.

Ví dụ

getopt -l "name:,version::,verbose" -- "n:v::V" "--name=Karthik -version=5.2 -verbose"

Cú pháp: Mẫu thứ ba

getopt [getopt_options] [-o options] [--] [optstring] [parameters]

Ở đây sau khi getopt được chia thành năm phần

  • Bản thân lệnh tức là getopt
  • Getopt_options, nó mô tả cách phân tích các đối số. tùy chọn dấu gạch ngang dài, tùy chọn dấu gạch ngang kép.
  • Các tùy chọn ngắn tức là -o hoặc --options. Giống như cú pháp đầu tiên của Mẫu nhưng với tùy chọn "-o" và trước dấu "-" (dấu gạch ngang kép).
  • -, tách các getopt_options khỏi các tùy chọn bạn muốn phân tích và các tùy chọn ngắn được phép
  • Các tham số, đây là các tùy chọn mà bạn đã truyền vào chương trình. Các tùy chọn bạn muốn phân tích cú pháp và lấy các giá trị thực tế được đặt trên chúng.

Ví dụ

getopt -l "name:,version::,verbose" -a -o "n:v::V" -- "-name=Karthik -version=5.2 -verbose"

GETOPT_OPTIONS

getopt_options thay đổi cách các tham số dòng lệnh được phân tích cú pháp.

Dưới đây là một số getopt_options

Tùy chọn: -l hoặc --longoptions

Có nghĩa là lệnh getopt sẽ cho phép các tùy chọn đa ký tự được nhận dạng. Nhiều tùy chọn được phân tách bằng dấu phẩy.

Ví dụ, --name=Karthiklà một tùy chọn dài được gửi trong dòng lệnh. Trong getopt, việc sử dụng các tùy chọn dài giống như

getopt "name:,version" "--name=Karthik"

Vì tên: được chỉ định, tùy chọn sẽ chứa giá trị

Tùy chọn: -a hoặc --alternative

Có nghĩa là lệnh getopt sẽ cho phép tùy chọn dài có một dấu gạch ngang '-' thay vì dấu gạch ngang kép '-'.

Ví dụ, thay vì --name=Karthikbạn chỉ có thể sử dụng-name=Karthik

getopt "name:,version" "-name=Karthik"

Một ví dụ hoàn chỉnh về kịch bản với mã:

#!/bin/bash

# filename: commandLine.sh
# author: @theBuzzyCoder

showHelp() {
# `cat << EOF` This means that cat should stop reading when EOF is detected
cat << EOF  
Usage: ./installer -v <espo-version> [-hrV]
Install Pre-requisites for EspoCRM with docker in Development mode

-h, -help,          --help                  Display help

-v, -espo-version,  --espo-version          Set and Download specific version of EspoCRM

-r, -rebuild,       --rebuild               Rebuild php vendor directory using composer and compiled css using grunt

-V, -verbose,       --verbose               Run script in verbose mode. Will print out each step of execution.

EOF
# EOF is found above and hence cat command stops reading. This is equivalent to echo but much neater when printing out.
}


export version=0
export verbose=0
export rebuilt=0

# $@ is all command line parameters passed to the script.
# -o is for short options like -v
# -l is for long options with double dash like --version
# the comma separates different long options
# -a is for long options with single dash like -version
options=$(getopt -l "help,version:,verbose,rebuild,dryrun" -o "hv:Vrd" -a -- "$@")

# set --:
# If no arguments follow this option, then the positional parameters are unset. Otherwise, the positional parameters 
# are set to the arguments, even if some of them begin with a ‘-’.
eval set -- "$options"

while true
do
case $1 in
-h|--help) 
    showHelp
    exit 0
    ;;
-v|--version) 
    shift
    export version=$1
    ;;
-V|--verbose)
    export verbose=1
    set -xv  # Set xtrace and verbose mode.
    ;;
-r|--rebuild)
    export rebuild=1
    ;;
--)
    shift
    break;;
esac
shift
done

Chạy tệp script này:

# With short options grouped together and long option
# With double dash '--version'

bash commandLine.sh --version=1.0 -rV
# With short options grouped together and long option
# With single dash '-version'

bash commandLine.sh -version=1.0 -rV

# OR with short option that takes value, value separated by whitespace
# by key

bash commandLine.sh -v 1.0 -rV

# OR with short option that takes value, value without whitespace
# separation from key.

bash commandLine.sh -v1.0 -rV

# OR Separating individual short options

bash commandLine.sh -v1.0 -r -V


getopt vs getopts .. tuân thủ đa nền tảng rất khác nhau
Shadowbq

35

Ví dụ được đóng gói với getopt (bản phân phối của tôi đặt nó /usr/share/getopt/getopt-parse.bash) có vẻ như nó bao gồm tất cả các trường hợp của bạn:

#!/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 `"$@"' to let each command-line parameter expand to a 
# separate word. The quotes around `$@' 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' -- "$@"`

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

11
Lệnh bên ngoài getopt (1) không bao giờ an toàn để sử dụng, trừ khi bạn biết đó là GNU getopt, bạn gọi nó theo cách riêng của GNU bạn đảm bảo rằng GETOPT_COMPATIBLE không có trong môi trường. Sử dụng getopts (shell dựng sẵn) thay vào đó, hoặc đơn giản là lặp qua các tham số vị trí.
Gilles Quenot

@sputnick, tyvm, không biết điều này.
Brian Cain

14
Eh, không có lệnh bên ngoài là an toàn để sử dụng theo tiêu chuẩn đó. Các getopts tích hợp bị thiếu các tính năng quan trọng và nếu bạn muốn kiểm tra GETOPT_COMPATIBLE thì dễ hơn so với chuyển các tính năng của getopt.
Michael Terry

12

Tôi biết rằng điều này đã được trả lời, nhưng đối với hồ sơ và cho bất kỳ ai có cùng yêu cầu như tôi, tôi quyết định đăng câu trả lời liên quan này. Mã này tràn ngập các ý kiến ​​để giải thích mã.

Cập nhật câu trả lời:

Lưu tệp dưới dạng 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
}

Sau đó, bạn có thể sử dụng nó như thế này:

#!/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}

Câu trả lời cũ:

Gần đây tôi cần sử dụng một cách tiếp cận chung chung. Tôi đã đi qua với giải pháp này:

#!/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}

Tôi đã không kiểm tra điều này một cách thô bạo, vì vậy tôi có thể có một số lỗi trong đó.


1
Nếu bạn đang sử dụng getoptsmột chức năng, hãy thêm local OPTIND OPTARGvào chức năng
glenn jackman

@glennjackman thực sự giống như một cách tiếp cận sed hơn là sử dụnggetopts
Sebastian

8

Ví dụ POSIX 7

Cũng đáng để kiểm tra ví dụ từ tiêu chuẩn: http://pub.opengroup.org/onlinepub/9699919799/utilities/getopts.html

aflag=
bflag=
while getopts ab: name
do
    case $name in
    a)    aflag=1;;
    b)    bflag=1
          bval="$OPTARG";;
    ?)   printf "Usage: %s: [-a] [-b value] args\n" $0
          exit 2;;
    esac
done
if [ ! -z "$aflag" ]; then
    printf "Option -a specified\n"
fi
if [ ! -z "$bflag" ]; then
    printf 'Option -b "%s" specified\n' "$bval"
fi
shift $(($OPTIND - 1))
printf "Remaining arguments are: %s\n" "$*"

Và sau đó chúng ta có thể dùng thử:

$ sh a.sh
Remaining arguments are: 
$ sh a.sh -a
Option -a specified
Remaining arguments are: 
$ sh a.sh -b
No arg for -b option
Usage: a.sh: [-a] [-b value] args
$ sh a.sh -b myval
Option -b "myval" specified
Remaining arguments are: 
$ sh a.sh -a -b myval
Option -a specified
Option -b "myval" specified
Remaining arguments are: 
$ sh a.sh remain
Remaining arguments are: remain
$ sh a.sh -- -a remain
Remaining arguments are: -a remain

Đã thử nghiệm trong Ubuntu 17.10, shlà dấu gạch ngang 0,5.8.


0

"Getops" và "getopt" rất hạn chế. Mặc dù "getopt" được đề nghị hoàn toàn không được sử dụng, nhưng nó cung cấp các tùy chọn dài. Trong khi "getopts" chỉ cho phép các tùy chọn ký tự đơn lẻ như "-a" "-b". Có một vài nhược điểm nữa khi sử dụng một trong hai.

Vì vậy, tôi đã viết một kịch bản nhỏ thay thế "getopts" và "getopt". Đó là một khởi đầu, nó có thể có thể được cải thiện rất nhiều.

Cập nhật 08-04-2020 : Tôi đã thêm hỗ trợ cho dấu gạch ngang, ví dụ: "- package-name".

Cách sử dụng: "./script.sh gói cài đặt - package" tên với dấu cách "--build --archive"

# Example:
# parseArguments "${@}"
# echo "${ARG_0}" -> package
# echo "${ARG_1}" -> install
# echo "${ARG_PACKAGE}" -> "name with space"
# echo "${ARG_BUILD}" -> 1 (true)
# echo "${ARG_ARCHIVE}" -> 1 (true)
function parseArguments() {
  PREVIOUS_ITEM=''
  COUNT=0
  for CURRENT_ITEM in "${@}"
  do
    if [[ ${CURRENT_ITEM} == "--"* ]]; then
      printf -v "ARG_$(formatArgument "${CURRENT_ITEM}")" "%s" "1" # could set this to empty string and check with [ -z "${ARG_ITEM-x}" ] if it's set, but empty.
    else
      if [[ $PREVIOUS_ITEM == "--"* ]]; then
        printf -v "ARG_$(formatArgument "${PREVIOUS_ITEM}")" "%s" "${CURRENT_ITEM}"
      else
        printf -v "ARG_${COUNT}" "%s" "${CURRENT_ITEM}"
      fi
    fi

    PREVIOUS_ITEM="${CURRENT_ITEM}"
    (( COUNT++ ))
  done
}

# Format argument.
function formatArgument() {
  ARGUMENT="${1^^}" # Capitalize.
  ARGUMENT="${ARGUMENT/--/}" # Remove "--".
  ARGUMENT="${ARGUMENT//-/_}" # Replace "-" with "_".
  echo "${ARGUMENT}"
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.