ตัวอย่างของวิธีใช้ getopts ใน bash


345

ฉันต้องการโทรหาmyscriptไฟล์ด้วยวิธีนี้:

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


2
ตัวเลือกควรจะเป็นตัวเลือก หากคุณต้องการค่าที่ระบุโดย-sทำให้เป็นอาร์กิวเมนต์ตำแหน่ง: ./myscript 45 anystring.
chepner

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

ไม่เป็นไรถ้า-pเป็นตัวเลือก (นั่นคือโปรแกรมของคุณสามารถดำเนินการต่อได้หากไม่มีตัวเลือก) ในกรณี./myscript 45 -p any_stringนี้ (ฉันคิดว่าgetoptสามารถจัดการกับตัวเลือกแบบผสมและข้อโต้แย้งตำแหน่งในขณะที่bashคำสั่งในตัวgetoptsต้องมีข้อโต้แย้งตำแหน่งทั้งหมดจะถูกวางไว้หลังจากตัวเลือก.)
chepner

คำตอบ:


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}"

ตัวอย่างการทำงาน:

$ ./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
ในการโทร getopts ทำไมจึงมีลำไส้ใหญ่นำ? "h" จะมีเครื่องหมายจุดคู่หลังเมื่อใด
e40

7
ควรusage()คืนค่า 1 หรือไม่
Pithikos

6
@Pithikos จุดที่ดี สามัญสำนึกบอกฉันว่าเมื่อเรียกผ่าน-hมันควรจะกลับมา0เมื่อกดปุ่มธงที่ไม่มีอยู่ก็ควรกลับ>0(เพื่อความเรียบง่ายฉันไม่ได้แยกความแตกต่างระหว่างกรณีเหล่านั้นและไม่มีใครบังคับให้คุณพิมพ์ข้อความการใช้งานในกรณีหลัง) . ฉันได้เห็นโปรแกรมซึ่งมักจะกลับมาแต่แม้ใน!= 0 -h/--helpบางทีฉันควรอัปเดตเกร็ดเล็กเกร็ดน้อยในกรณีที่ผู้คนใช้สิ่งนี้เป็นหม้อไอน้ำ (ฉันหวังว่าไม่ได้)?
Adrian Frühwirth

1
@ A.Danischewski แห่งนี้คือริม ( getopts') การออกแบบที่ไม่มีสิ่งดังกล่าวเป็น 'ข้อโต้แย้งที่ไม่จำเป็น' getoptsกับ ตัวแยกวิเคราะห์ไม่สามารถรู้ได้ว่าโทเค็นถัดไปเป็นอาร์กิวเมนต์ของตัวเลือกปัจจุบันหรือตัวเลือกด้วยตัวเองเนื่องจาก-pอาจเป็นค่าที่ต้องการ คุณสามารถแฮ็คสิ่งนี้หากคุณรู้อย่างแน่นอนว่าพารามิเตอร์ตัวเลือกไม่สามารถมีลักษณะเหมือนตัวเลือกที่ถูกต้องได้อีก แต่ใช่ว่าอาจมีเหตุผลที่ไม่จำเป็นต้องระบุอาร์กิวเมนต์ใน POSIX
Adrian Frühwirth

4
@ user1011471 คุณถูกต้อง! การจัดฟันเป็นลอนเพื่อที่จะพูดเพียงแค่ช่วยผู้ใช้เล็กซ์เซอร์bashในการระบุตัวแปร พวกเขาอยู่ในหลายกรณีที่ไม่จำเป็นและความจริงที่ว่าฉันมักจะใช้พวกเขาเป็นเพียงรูปแบบการเข้ารหัสส่วนบุคคล สำหรับฉันมันง่ายกว่า (และสวยกว่า) ที่จะใช้มันเสมอแทนที่จะจำกฎการแจงโดยคำนึงถึงความกำกวม ค่อนข้างเหมือนกันว่าทำไมจะเขียนif (foo) { bar; }แทนif (foo) bar;ในภาษา C สไตล์ (สุนทรียภาพและ / หรือหลีกเลี่ยงความผิดพลาดโง่ ๆ )
Adrian Frühwirth

109

ปัญหาของรหัสต้นฉบับคือ:

  • 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 - ระบุว่า Bash ควรแสดงข้อความผิดพลาดหรือไม่

ดังนั้นรหัสสามารถ:

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

ดู: กวดวิชา getopts ขนาดเล็กที่ Bash แฮกเกอร์ Wiki


2
usage() { echo "$0 usage:" && grep "[[:space:]].)\ #" $0 | sed 's/#//' | sed -r 's/([a-z])\)/-\1/'; exit 0; }เปลี่ยนฟังก์ชั่นการใช้งานไปนี้: เพียงบัญชีสำหรับตัวอักษรช่องว่างเดียวก่อนตัวเลือกตัวอักษรลบ # ออกจากความคิดเห็นและเสริม '-' ก่อนตัวเลือกตัวอักษรทำให้ชัดเจนสำหรับคำสั่ง
Poagester

2
@kenorb: โคลอนที่ด้านหน้าของตัวเลือกไม่ได้ละเว้นตัวเลือกที่ไม่ได้รับการสนับสนุน แต่ปิดเสียงข้อผิดพลาดจากการทุบตีและช่วยให้คุณจัดการกับมันในรหัสของคุณ ตัวแปรจะมี '?' ในกรณีของตัวเลือกที่ไม่สนับสนุนและ ':' ในกรณีที่มีค่าขาดหายไป
Hynek -Pichi- Vychodil

1
ขอบคุณสำหรับเอกสารรายละเอียดไม่สามารถได้รับ:สิทธิ์จนกว่าฉันจะเห็นบันทึกเหล่านี้ เราจำเป็นต้องเพิ่ม:ตัวเลือกที่เราคาดว่าจะมีข้อโต้แย้ง
Aukhan

51

ใช้ getopt

ทำไม getopt

ในการแยกวิเคราะห์อาร์กิวเมนต์บรรทัดคำสั่งที่ซับซ้อนเพื่อหลีกเลี่ยงความสับสนและชี้แจงตัวเลือกที่เราแยกวิเคราะห์เพื่อให้ผู้อ่านคำสั่งสามารถเข้าใจสิ่งที่เกิดขึ้น

getopt คืออะไร

getoptใช้เพื่อแยกตัวเลือก (แยกวิเคราะห์) ในบรรทัดคำสั่งเพื่อให้ง่ายในการแยกวิเคราะห์โดยเชลล์โพรซีเดอร์และเพื่อตรวจสอบตัวเลือกทางกฎหมาย มันใช้รูทีน GNU getopt(3)เพื่อทำสิ่งนี้

getopt สามารถมีตัวเลือกประเภทต่อไปนี้

  1. ตัวเลือกที่ไม่มีค่า
  2. ตัวเลือกคู่คีย์ - ค่า

หมายเหตุ: ในเอกสารนี้ระหว่างอธิบายไวยากรณ์:

  • สิ่งใดภายใน [] เป็นพารามิเตอร์ทางเลือกในไวยากรณ์ / ตัวอย่าง
  • เป็นตัวยึดตำแหน่งซึ่งหมายความว่าควรแทนที่ด้วยค่าจริง

จะใช้งานgetoptอย่างไร?

ไวยากรณ์: แบบฟอร์มแรก

getopt optstring parameters

ตัวอย่าง:

# 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"

ที่นี่ h, v, t เป็นตัวเลือกและ -h -v -t เป็นวิธีที่ตัวเลือกควรได้รับในบรรทัดคำสั่ง

  1. 'h' เป็นตัวเลือกที่ไม่มีค่า
  2. 'v:' แสดงถึงตัวเลือกที่ -v มีค่าและเป็นตัวเลือกที่จำเป็น ':' หมายถึงมีค่า
  3. 't ::' หมายถึงตัวเลือกที่ -t มีค่า แต่เป็นตัวเลือก '::' หมายถึงตัวเลือก

ในพารามิเตอร์เสริมค่าไม่สามารถมีการแยกช่องว่างด้วยตัวเลือก ดังนั้นในตัวอย่าง "-t123" -t คือตัวเลือก 123 คือค่า

ไวยากรณ์: รูปแบบที่สอง

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

หลังจาก getopt ถูกแบ่งออกเป็นห้าส่วน

  • คำสั่งตัวเองเช่น getopt
  • getopt_options อธิบายวิธีแยกอาร์กิวเมนต์ ตัวเลือกเส้นประยาวเดี่ยวตัวเลือกเส้นประคู่
  • - แยก getopt_options ออกจากตัวเลือกที่คุณต้องการแยกวิเคราะห์และตัวเลือกแบบย่อที่อนุญาต
  • ตัวเลือกสั้น ๆ จะได้รับทันทีหลังจาก - พบ เหมือนกับไวยากรณ์รูปแบบแรก
  • พารามิเตอร์เหล่านี้คือตัวเลือกที่คุณได้ส่งผ่านเข้าไปในโปรแกรม ตัวเลือกที่คุณต้องการแยกวิเคราะห์และรับค่าจริงที่ตั้งค่าไว้

ตัวอย่าง

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

ไวยากรณ์: รูปแบบที่สาม

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

หลังจาก getopt ถูกแบ่งออกเป็นห้าส่วน

  • คำสั่งตัวเองเช่น getopt
  • getopt_options อธิบายวิธีแยกอาร์กิวเมนต์ ตัวเลือกเส้นประยาวเดี่ยวตัวเลือกเส้นประคู่
  • ตัวเลือกสั้น ๆ คือ -o หรือ --options เช่นเดียวกับไวยากรณ์รูปแบบแรก แต่มีตัวเลือก "-o" และก่อนหน้า "-" (เส้นประสองครั้ง)
  • - แยก getopt_options ออกจากตัวเลือกที่คุณต้องการแยกวิเคราะห์และตัวเลือกแบบย่อที่อนุญาต
  • พารามิเตอร์เหล่านี้คือตัวเลือกที่คุณได้ส่งผ่านเข้าไปในโปรแกรม ตัวเลือกที่คุณต้องการแยกวิเคราะห์และรับค่าจริงที่ตั้งค่าไว้

ตัวอย่าง

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

GETOPT_OPTIONS

getopt_options เปลี่ยนวิธีแยกพารามิเตอร์บรรทัดคำสั่ง

ด้านล่างนี้คือ getopt_options บางส่วน

ตัวเลือก: -l หรือ - longoptions

หมายถึงคำสั่ง getopt ควรอนุญาตให้รู้จักตัวเลือกหลายตัวได้ ตัวเลือกหลายตัวคั่นด้วยเครื่องหมายจุลภาค

ตัวอย่างเช่น--name=Karthikเป็นตัวเลือกยาวที่ส่งในบรรทัดคำสั่ง ใน getopt การใช้งานตัวเลือกแบบยาวนั้นเป็นอย่างไร

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

เนื่องจาก name: ถูกระบุตัวเลือกควรมีค่า

ตัวเลือก: - หรือ - ทางเลือก

หมายถึงคำสั่ง getopt ควรอนุญาตให้ตัวเลือกแบบยาวมีเครื่องหมายขีดกลาง '-' แทนที่จะเป็นเครื่องหมายขีดกลางคู่ '-'

ตัวอย่างแทนที่จะ--name=Karthikใช้เพียง-name=Karthik

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

ตัวอย่างสคริปต์ที่สมบูรณ์พร้อมรหัส:

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

เรียกใช้ไฟล์สคริปต์นี้:

# 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

แหล่งที่มา: linkedin.com/pulse/…
theBuzzyCoder

getopt vs getopts .. ความสอดคล้องข้ามแพลตฟอร์มที่แตกต่างกันมาก
shadowbq

35

ตัวอย่างที่บรรจุด้วยgetopt(distro ของฉันใส่ไว้/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 `"$@"' 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
คำสั่งภายนอก getopt (1) ไม่เคยปลอดภัยที่จะใช้นอกเสียจากคุณจะรู้ว่ามันคือ GNU getopt คุณเรียกมันในแบบเฉพาะของ GNU และคุณมั่นใจได้ว่า GETOPT_COMPATIBLE ไม่ได้อยู่ในสภาพแวดล้อม ใช้ getopts (shell builtin) แทนหรือเพียงแค่วนรอบพารามิเตอร์ตำแหน่ง
Gilles Quenot

@ sputnick, tyvm, ไม่รู้เรื่องนี้
Brian Cain

14
ใช่ไม่มีคำสั่งภายนอกที่ปลอดภัยที่จะใช้โดยมาตรฐานนั้น GETOPT_COMPATIBLE ในตัว getopts ในตัวขาดคุณสมบัติที่สำคัญและหากคุณต้องการตรวจสอบ GETOPT_COMPATIBLE ง่ายกว่าการย้ายคุณสมบัติของ getopt
Michael Terry

12

ฉันรู้ว่านี่เป็นคำตอบแล้ว แต่สำหรับบันทึกและสำหรับทุกคนที่มีความต้องการเดียวกันกับฉันฉันตัดสินใจที่จะโพสต์คำตอบที่เกี่ยวข้องนี้ รหัสเต็มไปด้วยความคิดเห็นเพื่ออธิบายรหัส

คำตอบที่อัปเดต:

บันทึกไฟล์เป็น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}

ฉันไม่ได้ทดสอบสิ่งนี้อย่างคร่าวๆดังนั้นฉันจึงอาจมีข้อบกพร่องบางอย่างในนั้น


1
หากคุณกำลังใช้getoptsฟังก์ชั่นอยู่ให้เพิ่มlocal OPTIND OPTARGเข้าไปในฟังก์ชั่น
เกล็นแจ็คแมน

@glennjackman จริง ๆ แล้วมันเป็นเหมือนวิธีการที่ดีกว่าการใช้getopts
Sebastian

8

ตัวอย่าง POSIX 7

นอกจากนี้ยังควรตรวจสอบตัวอย่างจากมาตรฐาน: http://pubs.opengroup.org/onlinepubs/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" "$*"

จากนั้นเราสามารถลอง:

$ 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

ทดสอบใน Ubuntu 17.10 shเป็นเส้นประ 0.5.8


0

"getops" และ "getopt" มี จำกัด มาก แม้ว่าจะไม่แนะนำให้ใช้ "getopt" แต่ก็มีตัวเลือกยาว โดยที่ "getopts" อนุญาตเพียงตัวเลือกอักขระเดียวเช่น "-a" "-b" มีข้อเสียเพิ่มเติมเล็กน้อยเมื่อใช้อย่างใดอย่างหนึ่ง

ดังนั้นฉันจึงได้เขียนสคริปต์ขนาดเล็กที่แทนที่ "getopts" และ "getopt" มันเป็นการเริ่มต้นมันน่าจะปรับปรุงได้มาก

อัปเดต 08-04-2020 : ฉันได้เพิ่มการรองรับยัติภังค์เช่น "- แพ็คเกจชื่อ"

การใช้งาน: "./script.sh แพคเกจติดตั้ง - แพ็คเกจ" ชื่อพร้อมช่องว่าง "- สร้าง - เก็บถาวร"

# 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}"
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.