การใช้ getopts เพื่อประมวลผลตัวเลือกบรรทัดคำสั่งแบบยาวและแบบสั้น


410

ฉันต้องการที่จะเรียกใช้ตัวเลือกบรรทัดคำสั่งแบบยาวและสั้นโดยใช้เชลล์สคริปต์ของฉัน

ฉันรู้ว่าgetoptsสามารถใช้ได้ แต่เหมือนใน Perl ฉันไม่สามารถทำเช่นเดียวกันกับเชลล์ได้

ความคิดเห็นใด ๆ เกี่ยวกับวิธีการทำสิ่งนี้เพื่อให้ฉันสามารถใช้ตัวเลือกเช่น:

./shell.sh --copyfile abc.pl /tmp/
./shell.sh -c abc.pl /tmp/

ในข้างต้นคำสั่งทั้งสองมีความหมายเหมือนกับเชลล์ของฉัน แต่การใช้getoptsฉันไม่สามารถใช้สิ่งเหล่านี้ได้หรือไม่


2
IMHO คำตอบที่ยอมรับไม่ใช่คำตอบที่ดีที่สุด ไม่แสดงวิธีใช้ getopts เพื่อจัดการอาร์กิวเมนต์ทั้ง "-" และ "-" ซึ่งสามารถทำได้ตามที่ @Arvid Requate สาธิต ฉันแทรกคำตอบอื่นโดยใช้แนวคิดที่คล้ายกัน แต่ยังเกี่ยวข้องกับข้อผิดพลาดของผู้ใช้ "ลืม" เพื่อแทรกค่าสำหรับอาร์กิวเมนต์ที่จำเป็น จุดสำคัญ: getopts สามารถทำงานได้ ผู้ใช้ควรหลีกเลี่ยงการใช้ "getopt" แทนหากจำเป็นต้องพกพาข้ามแพลตฟอร์ม นอกจากนี้ getopts ยังเป็นส่วนหนึ่งของมาตรฐาน POSIX สำหรับเชลล์ดังนั้นจึงน่าจะพกพาได้
pauljohn32

คำตอบ:


304

มีการใช้งานสามแบบที่อาจพิจารณาได้:

  • getoptsทุบตีในตัว สิ่งนี้ไม่สนับสนุนชื่อตัวเลือกแบบยาวด้วยคำนำหน้าแบบ double-dash รองรับเฉพาะตัวเลือกอักขระเดียวเท่านั้น

  • การใช้ BSD UNIX ของgetoptคำสั่งแบบสแตนด์อโลน(ซึ่งเป็นสิ่งที่ MacOS ใช้) สิ่งนี้ไม่สนับสนุนตัวเลือกแบบยาวเช่นกัน

  • getoptการดำเนินงานของกนูแบบสแตนด์อโลน GNU getopt(3)(ใช้โดยบรรทัดคำสั่งgetopt(1)บน Linux) รองรับการแยกวิเคราะห์ตัวเลือกแบบยาว


บางคำตอบอื่น ๆ แสดงวิธีแก้ปัญหาสำหรับการใช้ bash builtin getoptsเพื่อเลียนแบบตัวเลือกแบบยาว วิธีการแก้ปัญหานั้นทำให้ตัวเลือกสั้น ๆ ที่มีตัวอักษร "-" ดังนั้นคุณจะได้รับ "-" เป็นธง แล้วอะไรต่อไปนี้ที่จะกลายเป็น OPTARG และคุณทดสอบ OPTARG caseที่มีซ้อนกัน

นี่มันฉลาด แต่มันมาพร้อมกับ caveats:

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

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


18
ดังนั้น. โซลูชันแบบพกพาข้ามแพลตฟอร์มคืออะไร?
troelskn

6
GNU Getopt น่าจะเป็นทางเลือกเดียว บน Mac ให้ติดตั้ง GNU getopt จาก macports ใน Windows ฉันจะติดตั้ง GNU getopt ด้วย Cygwin
Bill Karwin

2
เห็นได้ชัดว่า ksh getopts สามารถจัดการกับตัวเลือกยาว
Tgr

1
@Bill +1 แม้ว่ามันจะค่อนข้างง่ายในการสร้าง getopt จากแหล่งที่มา ( software.frodo.looijaard.name/getopt ) บน Mac คุณยังสามารถตรวจสอบรุ่นของ getopt ที่ติดตั้งในระบบของคุณจากภายในสคริปต์ด้วย "getopt -T; echo $?"
Chinasaur

8
@Bill Karwin: "bash getopts builtin ไม่สนับสนุนชื่อตัวเลือกแบบยาวพร้อมคำนำหน้าแบบ double-dash" แต่สามารถสร้าง getopts เพื่อสนับสนุนทางเลือกยาว ๆ : ดูstackoverflow.com/a/7680682/915044ด้านล่าง
TomRoche

305

getoptและgetoptsเป็นสัตว์ที่แตกต่างกันและผู้คนดูเหมือนจะมีความเข้าใจผิดบ้างเกี่ยวกับสิ่งที่พวกเขาทำ getoptsเป็นคำสั่งในตัวbashเพื่อประมวลผลตัวเลือกบรรทัดคำสั่งในลูปและกำหนดตัวเลือกและค่าที่พบแต่ละตัวให้กับตัวแปรบิวด์อินดังนั้นคุณจึงสามารถประมวลผลได้ getoptอย่างไรก็ตามเป็นโปรแกรมอรรถประโยชน์ภายนอกและไม่ได้ประมวลผลตัวเลือกของคุณสำหรับคุณเช่น bash getopts, GetoptโมดูลPerl หรือ Python optparse/ argparsemodules ทำ สิ่งที่getoptทำคือกำหนดตัวเลือกที่ส่งผ่าน - ให้แปลงเป็นรูปแบบมาตรฐานมากขึ้นเพื่อให้เชลล์สคริปต์ประมวลผลได้ง่ายขึ้น ตัวอย่างเช่นแอปพลิเคชันgetoptอาจแปลงสิ่งต่อไปนี้:

myscript -ab infile.txt -ooutfile.txt

เป็นนี้

myscript -a -b -o outfile.txt infile.txt

คุณต้องทำการประมวลผลจริงด้วยตัวเอง คุณไม่จำเป็นต้องใช้getoptเลยหากคุณมีข้อ จำกัด ต่าง ๆ เกี่ยวกับวิธีที่คุณสามารถระบุตัวเลือก:

  • ใส่เพียงหนึ่งตัวเลือกต่อการโต้แย้ง;
  • ตัวเลือกทั้งหมดไปข้างหน้าพารามิเตอร์ตำแหน่งใด ๆ (เช่นอาร์กิวเมนต์ที่ไม่ใช่ตัวเลือก);
  • สำหรับตัวเลือกที่มีค่า (เช่น-oด้านบน) ค่าจะต้องเป็นอาร์กิวเมนต์แยกต่างหาก (หลังจากเว้นวรรค)

ทำไมต้องใช้getoptแทนgetopts? เหตุผลพื้นฐานคือมีเพียง GNU ที่getoptให้การสนับสนุนตัวเลือกบรรทัดคำสั่งที่มีชื่อยาว 1 (GNU getoptเป็นค่าเริ่มต้นบน Linux Mac OS X และ FreeBSD มาพร้อมกับรุ่นพื้นฐานและไม่มีประโยชน์มากgetoptแต่สามารถติดตั้งรุ่น GNU ได้ดูด้านล่าง)

ตัวอย่างเช่นนี่คือตัวอย่างของการใช้ GNU getoptจากสคริปต์ของฉันที่เรียกว่าjavawrap:

# NOTE: This requires GNU getopt.  On Mac OS X and FreeBSD, you have to install this
# separately; see below.
TEMP=`getopt -o vdm: --long verbose,debug,memory:,debugfile:,minheap:,maxheap: \
             -n 'javawrap' -- "$@"`

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

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

VERBOSE=false
DEBUG=false
MEMORY=
DEBUGFILE=
JAVA_MISC_OPT=
while true; do
  case "$1" in
    -v | --verbose ) VERBOSE=true; shift ;;
    -d | --debug ) DEBUG=true; shift ;;
    -m | --memory ) MEMORY="$2"; shift 2 ;;
    --debugfile ) DEBUGFILE="$2"; shift 2 ;;
    --minheap )
      JAVA_MISC_OPT="$JAVA_MISC_OPT -XX:MinHeapFreeRatio=$2"; shift 2 ;;
    --maxheap )
      JAVA_MISC_OPT="$JAVA_MISC_OPT -XX:MaxHeapFreeRatio=$2"; shift 2 ;;
    -- ) shift; break ;;
    * ) break ;;
  esac
done

สิ่งนี้ช่วยให้คุณระบุตัวเลือกเช่น--verbose -dm4096 --minh=20 --maxhe 40 --debugfi="/Users/John Johnson/debug.txt"หรือคล้ายกัน ผลกระทบของการเรียกไปยังgetoptคือกำหนดตัวเลือกให้เป็นมาตรฐานเพื่อ--verbose -d -m 4096 --minheap 20 --maxheap 40 --debugfile "/Users/John Johnson/debug.txt"ให้คุณสามารถดำเนินการได้ง่ายขึ้น การอ้างถึง"$1"และ"$2"มีความสำคัญเนื่องจากทำให้มั่นใจได้ว่าข้อโต้แย้งที่มีช่องว่างในนั้นจะได้รับการจัดการอย่างถูกต้อง

หากคุณลบ 9 บรรทัดแรก (ทุกอย่างจนถึงeval setบรรทัด) รหัสจะยังใช้งานได้ ! อย่างไรก็ตามรหัสของคุณจะเป็นตัวเลือกที่ดีกว่าในประเภทตัวเลือกที่ยอมรับ: โดยเฉพาะอย่างยิ่งคุณจะต้องระบุตัวเลือกทั้งหมดในแบบฟอร์ม "มาตรฐาน" ตามที่อธิบายไว้ข้างต้น อย่างไรก็ตามด้วยการใช้งานgetoptคุณสามารถจัดกลุ่มตัวเลือกแบบตัวอักษรเดียวใช้แบบยาวแบบไม่คลุมเครือของตัวเลือกแบบยาวใช้แบบ--file foo.txtหรือ--file=foo.txtแบบใช้แบบ-m 4096หรือ-m4096แบบตัวเลือกแบบผสมและแบบไม่มีตัวเลือกในลำดับใด ๆ ฯลฯ getoptเอาท์พุทข้อความแสดงข้อผิดพลาดหากพบตัวเลือกที่ไม่รู้จักหรือคลุมเครือ

หมายเหตุ : จริง ๆ แล้วมีสองรุ่นที่แตกต่างกันโดยสิ้นเชิงของgetoptพื้นฐานgetoptและ GNU ที่getoptมีคุณสมบัติที่แตกต่างกันและการประชุมที่แตกต่างกัน 2พื้นฐานgetoptค่อนข้างเสียหาย: ไม่เพียงจัดการกับตัวเลือกที่ยาว แต่ยังไม่สามารถจัดการช่องว่างที่ฝังอยู่ภายในอาร์กิวเมนต์หรือว่างเปล่าในขณะที่getoptsทำสิ่งนี้ถูกต้อง getoptรหัสข้างต้นจะไม่ทำงานในขั้นพื้นฐาน GNU ได้getoptรับการติดตั้งตามค่าเริ่มต้นบน Linux แต่สำหรับ Mac OS X และ FreeBSD จะต้องติดตั้งแยกต่างหาก บน Mac OS X ให้ติดตั้ง MacPorts ( http://www.macports.org ) จากนั้นทำการsudo port install getoptติดตั้ง GNU getopt(โดยทั่วไป/opt/local/bin) และตรวจสอบให้แน่ใจว่า/opt/local/binอยู่ในเส้นทางเชลล์ของคุณก่อน/usr/binและให้แน่ใจว่า บน FreeBSD ให้ติดตั้งmisc/getopt.

คู่มือรวดเร็วในการปรับเปลี่ยนโค้ดตัวอย่างสำหรับโปรแกรมของคุณเอง: ในไม่กี่บรรทัดแรกทั้งหมดคือ "ต้นแบบ" getoptที่ควรจะอยู่เหมือนกันยกเว้นสายที่โทร คุณควรเปลี่ยนชื่อโปรแกรมหลังจาก-nระบุตัวเลือกสั้น ๆ หลังจากและตัวเลือกนานหลังจากที่-o --longใส่เครื่องหมายจุดคู่หลังตัวเลือกที่รับค่า

สุดท้ายหากคุณเห็นรหัสที่ได้มาเพียงแค่setแทนeval setมันถูกเขียนขึ้นสำหรับ getoptBSD คุณควรเปลี่ยนไปใช้eval setรูปแบบซึ่งทำงานได้ดีกับทั้งสองรุ่นgetoptในขณะที่ธรรมดาsetไม่ได้ทำงานที่ถูกต้องกับ getoptGNU

1อันที่จริงgetoptsในksh93การสนับสนุนระยะยาวที่มีชื่อตัวเลือก bashแต่เปลือกนี้จะไม่ใช้บ่อยเท่า ในzshใช้zparseoptsเพื่อรับฟังก์ชั่นนี้

2 ในทางเทคนิค "GNU getopt" เป็นชื่อเรียกที่ไม่ถูกต้อง รุ่นนี้เขียนขึ้นจริงสำหรับ Linux มากกว่าโครงการ GNU อย่างไรก็ตามมันเป็นไปตามอนุสัญญา GNU ทั้งหมดและgetoptมักใช้คำว่า "GNU " (เช่นใน FreeBSD)


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

2
getoptบน Linux ไม่ใช่ยูทิลิตี้ GNU และแบบดั้งเดิมgetoptไม่ได้มาจาก BSD แต่มาจาก AT&T Unix ksh93's getopts(จาก AT&T) รองรับตัวเลือกแบบยาวของ GNU
Stephane Chazelas

@StephaneChazelas - แก้ไขเพื่อแสดงความคิดเห็นของคุณ ฉันยังคงชอบคำว่า "GNU getopt" แม้ว่าจะเป็นชื่อเรียกที่ไม่ถูกต้องเนื่องจากรุ่นนี้เป็นไปตามแบบแผนของ GNU และโดยทั่วไปจะทำหน้าที่เหมือนโปรแกรม GNU (เช่นการใช้ประโยชน์POSIXLY_CORRECT) ในขณะที่ "getopt ที่ปรับปรุงด้วย Linux" ผิด ๆ ลินุกซ์
Urban Vagabond

1
มันมาจากแพคเกจ util-linux ดังนั้นจึงเป็น Linux เท่านั้นเนื่องจากชุดซอฟต์แวร์นั้นมีไว้สำหรับ Linux เท่านั้น (ซึ่งgetoptสามารถส่งไปยัง Unices อื่น ๆ ได้ง่าย แต่ซอฟต์แวร์อื่น ๆ จำนวนมากutil-linuxเป็นเฉพาะ Linux) ทุกโปรแกรมที่ไม่ใช่ GNU การใช้ GNU getopt (3) $POSIX_CORRECTเข้าใจ ตัวอย่างเช่นคุณจะไม่พูดว่านั่นaplayคือ GNU ในพื้นที่เหล่านั้น ฉันสงสัยว่าเมื่อ FreeBSD พูดถึง GNU getopt พวกเขาหมายถึง GNU getopt (3) C API
Stephane Chazelas

@StephaneChazelas - FreeBSD มีข้อความแสดงข้อผิดพลาด "Build dependency: โปรดติดตั้ง GNU getopt" ที่อ้างถึงการใช้งานอย่างชัดเจนgetoptไม่ใช่ getopt (3)
Urban Vagabond

202

ฟังก์ชั่น Bash builtin getopts สามารถใช้ในการแยกวิเคราะห์ตัวเลือกแบบยาวโดยการใส่ตัวอักษรเส้นประตามด้วยเครื่องหมายจุดคู่ลงใน optspec:

#!/usr/bin/env bash 
optspec=":hv-:"
while getopts "$optspec" optchar; do
    case "${optchar}" in
        -)
            case "${OPTARG}" in
                loglevel)
                    val="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 ))
                    echo "Parsing option: '--${OPTARG}', value: '${val}'" >&2;
                    ;;
                loglevel=*)
                    val=${OPTARG#*=}
                    opt=${OPTARG%=$val}
                    echo "Parsing option: '--${opt}', value: '${val}'" >&2
                    ;;
                *)
                    if [ "$OPTERR" = 1 ] && [ "${optspec:0:1}" != ":" ]; then
                        echo "Unknown option --${OPTARG}" >&2
                    fi
                    ;;
            esac;;
        h)
            echo "usage: $0 [-v] [--loglevel[=]<value>]" >&2
            exit 2
            ;;
        v)
            echo "Parsing option: '-${optchar}'" >&2
            ;;
        *)
            if [ "$OPTERR" != 1 ] || [ "${optspec:0:1}" = ":" ]; then
                echo "Non-option argument: '-${OPTARG}'" >&2
            fi
            ;;
    esac
done

หลังจากคัดลอกไปยังไฟล์ชื่อปฏิบัติการ = getopts_test.shในไดเรกทอรีการทำงานปัจจุบันหนึ่งสามารถผลิตเช่น

$ ./getopts_test.sh
$ ./getopts_test.sh -f
Non-option argument: '-f'
$ ./getopts_test.sh -h
usage: code/getopts_test.sh [-v] [--loglevel[=]<value>]
$ ./getopts_test.sh --help
$ ./getopts_test.sh -v
Parsing option: '-v'
$ ./getopts_test.sh --very-bad
$ ./getopts_test.sh --loglevel
Parsing option: '--loglevel', value: ''
$ ./getopts_test.sh --loglevel 11
Parsing option: '--loglevel', value: '11'
$ ./getopts_test.sh --loglevel=11
Parsing option: '--loglevel', value: '11'

เห็นได้ชัดว่า getopts ไม่ทำการOPTERRตรวจสอบหรือแยกอาร์กิวเมนต์ตัวเลือกสำหรับตัวเลือกยาว ส่วนของสคริปต์ด้านบนแสดงให้เห็นว่าวิธีการนี้สามารถทำได้ด้วยตนเอง หลักการพื้นฐานยังใช้งานได้ใน Debian Almquist shell ("dash") หมายเหตุกรณีพิเศษ:

getopts -- "-:"  ## without the option terminator "-- " bash complains about "-:"
getopts "-:"     ## this works in the Debian Almquist shell ("dash")

โปรดทราบว่าในขณะที่ GreyCat จากที่http://mywiki.wooledge.org/BashFAQชี้ให้เห็นว่าเคล็ดลับนี้ใช้ประโยชน์จากพฤติกรรมที่ไม่ได้มาตรฐานของเชลล์ซึ่งอนุญาตให้ใช้ตัวเลือกอาร์กิวเมนต์ (เช่นชื่อไฟล์ใน "-f filename") ที่จะต่อกันกับตัวเลือก (ดังใน "-filename") POSIXมาตรฐานกล่าวว่าจะต้องมีช่องว่างระหว่างพวกเขาซึ่งในกรณีของ "- longoption" จะยกเลิกตัวเลือกการแยกและเปิด longoptions ทั้งหมดลงในข้อโต้แย้งที่ไม่ใช่ตัวเลือก


2
คำถามหนึ่ง: อะไรคือความหมายของ!ในval="${!OPTIND}?
TomRoche

2
@TomRoche มันเป็นการทดแทนทางอ้อม: unix.stackexchange.com/a/41293/84316
ecbrodie

2
@ecbrodie: เป็นเพราะมีการโต้แย้งกันสองข้อที่ได้รับการประมวลผลจริง ๆ อาร์กิวเมนต์แรกคือคำว่า "LogLevel" และต่อไปคือการโต้แย้งไปว่าข้อโต้แย้ง ในขณะเดียวกันgetoptsเพิ่มทีละOPTIND1 โดยอัตโนมัติแต่ในกรณีของเราเราจำเป็นต้องเพิ่มทีละ 2 ดังนั้นเราจึงเพิ่มทีละ 1 ด้วยตนเองจากนั้นให้getoptsเพิ่มทีละ 1 อีกครั้งโดยอัตโนมัติ
Victor Zamanian

3
เนื่องจากเราอยู่ใน bash equilibrism ที่นี่: อนุญาตให้ใช้ชื่อตัวแปรเปล่าภายในนิพจน์ทางคณิตศาสตร์ไม่$จำเป็น อาจจะเป็นแค่OPTIND=$(( $OPTIND + 1 )) OPTIND=$(( OPTIND + 1 ))มากยิ่งขึ้นที่น่าสนใจที่คุณยังสามารถกำหนดและการเพิ่มขึ้นของตัวแปรภายในการแสดงออกทางคณิตศาสตร์จึงเป็นไปได้ที่จะย่อมันต่อไป: $(( ++OPTIND ))หรือแม้กระทั่ง(( ++OPTIND ))คำนึงถึงว่า++OPTINDจะเป็นบวกดังนั้นมันจะไม่เดินทางขึ้นวิ่งเปลือกกับ-eตัวเลือก :-) gnu.org/software/bash/manual/html_node/Shell-Arithmetic.html
clacke

3
ทำไมไม่--very-badเตือน
Tom Hale

148

getoptsคำสั่งในตัวยังคง AFAIK จำกัด ตัวเลือกอักขระเดียวเท่านั้น

มี (หรือเคยเป็น) โปรแกรมภายนอกgetoptที่จะจัดระเบียบชุดของตัวเลือกใหม่เพื่อให้ง่ายต่อการแยกวิเคราะห์ คุณสามารถปรับการออกแบบเพื่อจัดการกับตัวเลือกที่ยาวเกินไป ตัวอย่างการใช้งาน:

aflag=no
bflag=no
flist=""
set -- $(getopt abf: "$@")
while [ $# -gt 0 ]
do
    case "$1" in
    (-a) aflag=yes;;
    (-b) bflag=yes;;
    (-f) flist="$flist $2"; shift;;
    (--) shift; break;;
    (-*) echo "$0: error - unrecognized option $1" 1>&2; exit 1;;
    (*)  break;;
    esac
    shift
done

# Process remaining non-option arguments
...

คุณสามารถใช้ชุดรูปแบบที่คล้ายกันกับgetoptlongคำสั่ง

โปรดทราบว่าจุดอ่อนพื้นฐานกับgetoptโปรแกรมภายนอกคือความยากลำบากในการจัดการข้อโต้แย้งด้วยช่องว่างในพวกเขาและในการรักษาช่องว่างเหล่านั้นอย่างถูกต้อง นี่คือเหตุผลว่าทำไมบิวด์อินgetoptsจึงยอดเยี่ยมถึงแม้ว่าจะถูก จำกัด ด้วยความจริงที่ว่ามันรองรับเฉพาะตัวอักษรตัวเดียวเท่านั้น


11
getopt ยกเว้นเวอร์ชัน GNU (ซึ่งมีรูปแบบการโทรที่แตกต่างกัน) จะใช้งานไม่ได้ อย่าใช้มัน โปรดใช้ ** getopts แทนbash-hackers.org/wiki/doku.php/howto/getopts_tutorial
hendry

9
@hendry - จากลิงก์ของคุณ: "โปรดทราบว่า getopts ไม่สามารถแยกตัวเลือกแบบยาวของ GNU (--myoption) หรือตัวเลือกแบบยาวของ XF86 (-myoption)!"
Tom Auger

1
Jonathan - คุณควรเขียนตัวอย่างเพื่อใช้eval setกับเครื่องหมายคำพูด (ดูคำตอบของฉันด้านล่าง) เพื่อให้มันทำงานได้อย่างถูกต้องกับ GNU getopt (ค่าเริ่มต้นบน Linux) และจัดการช่องว่างอย่างถูกต้อง
Urban Vagabond

@UrbanVagabond: ฉันไม่แน่ใจว่าทำไมฉันควรทำเช่นนั้น คำถามถูกติดแท็ก Unix ไม่ใช่ Linux ฉันกำลังแสดงกลไกแบบดั้งเดิมโดยเจตนาและมีปัญหาเกี่ยวกับช่องว่างในข้อโต้แย้ง ฯลฯ คุณสามารถสาธิตเวอร์ชันเฉพาะ Linux ที่ทันสมัยหากคุณต้องการและคำตอบของคุณก็เป็นเช่นนั้น (ฉันสังเกตเห็นว่ารหัสผ่านที่คุณใช้${1+"$@"}นั้นแปลกตาและขัดแย้งกับสิ่งที่จำเป็นในเชลล์สมัยใหม่และโดยเฉพาะกับเชลล์ใด ๆ ที่คุณพบบน Linux ดูการใช้ $ 1: + "$ @"} ใน / bin / shสำหรับ การอภิปรายของสัญกรณ์นั้น)
Jonathan Leffler

คุณควรทำเพราะeval setทำสิ่งที่ถูกต้องกับทั้ง GNU และ BSD getoptในขณะที่ธรรมดาsetทำในสิ่งที่ถูกต้องกับ BSD getoptเท่านั้น ดังนั้นคุณอาจใช้eval setเพื่อส่งเสริมให้ผู้คนมีนิสัยในการทำเช่นนี้ ขอบคุณ BTW ฉันไม่ได้ตระหนักว่า${1+"$@"}ไม่จำเป็นอีกต่อไป ฉันต้องเขียนสิ่งต่าง ๆ ที่ใช้งานได้ทั้งบน Mac OS X และ Linux - ระหว่างสองรายการนี้พวกเขาบังคับให้พกพาได้มากมาย ฉันเพียงแค่การตรวจสอบและ"$@"ไม่แน่นอนทำสิ่งที่ถูกต้องในทุกsh, bash, kshและzshภายใต้ Mac OS X; แน่นอนภายใต้ลินุกซ์เช่นกัน
Urban Vagabond

78

นี่คือตัวอย่างที่ใช้ getopt กับตัวเลือกแบบยาว:

aflag=no
bflag=no
cargument=none

# options may be followed by one colon to indicate they have a required argument
if ! options=$(getopt -o abc: -l along,blong,clong: -- "$@")
then
    # something went wrong, getopt will put out an error message for us
    exit 1
fi

set -- $options

while [ $# -gt 0 ]
do
    case $1 in
    -a|--along) aflag="yes" ;;
    -b|--blong) bflag="yes" ;;
    # for options with required arguments, an additional shift is required
    -c|--clong) cargument="$2" ; shift;;
    (--) shift; break;;
    (-*) echo "$0: error - unrecognized option $1" 1>&2; exit 1;;
    (*) break;;
    esac
    shift
done

1
คุณควรเขียนตัวอย่างเพื่อใช้eval setกับเครื่องหมายคำพูด (ดูคำตอบของฉันด้านล่าง) เพื่อให้ทำงานได้อย่างถูกต้องกับ GNU getopt (ค่าเริ่มต้นบน Linux) และจัดการช่องว่างอย่างถูกต้อง
Urban Vagabond

2
นี้กำลังใช้getoptในขณะที่คำถามที่เป็นเรื่องเกี่ยวกับgetoptsว่า
Niklas Berglund

1
มี(--, (-*และ(*รูปแบบที่ถูกต้อง? วิธีที่พวกเขาจะแตกต่างจาก--, -*และ*?
Maëlan

1
@ Maëlan - วงเล็บเปิดชั้นนำเป็นตัวเลือกดังนั้น(--)จะเหมือนกับ--)ในcasestanza มันแปลกที่จะเห็นการเยื้องที่ไม่สม่ำเสมอและการใช้ parens นำที่ไม่จำเป็นนั้น แต่รหัสปัจจุบันของคำตอบนั้นดูสมเหตุสมผลสำหรับฉัน
Adam Katz

59

ตัวเลือกแบบยาวสามารถแยกวิเคราะห์โดยgetoptsbuiltin มาตรฐานเป็น "อาร์กิวเมนต์" กับ-"ตัวเลือก"

นี่คือเปลือก POSIX แบบพกพาและดั้งเดิม - ไม่ต้องใช้โปรแกรมหรือ bashisms ภายนอก

คู่มือนี้จะดำเนินการตัวเลือกยาวที่เป็นข้อโต้แย้งไปที่-ตัวเลือกเพื่อให้--alphaเห็นgetoptsเป็น-ที่มีการโต้แย้งalphaและ--bravo=fooถูกมองว่าเป็นที่มีการโต้แย้ง- อาร์กิวเมนต์จริงสามารถเก็บเกี่ยวได้มีการเปลี่ยนง่าย:bravo=foo${OPTARG#*=}

ในตัวอย่างนี้-bและ-c(และรูปแบบยาว--bravoและ--charlie) มีอาร์กิวเมนต์ที่จำเป็น อาร์กิวเมนต์ของตัวเลือกแบบยาวนั้นมาจากเครื่องหมายเท่ากับเช่น--bravo=foo(ตัวคั่นช่องว่างสำหรับตัวเลือกแบบยาวจะใช้งานได้ยากดูด้านล่าง)

เพราะนี้ใช้getoptsbuiltinนี้ใช้วิธีการแก้ปัญหาเช่นการสนับสนุนcmd --bravo=foo -ac FILE(ซึ่งได้รวมตัวเลือก-aและ-cและ interleaves ตัวเลือกยาวที่มีตัวเลือกมาตรฐาน) ในขณะที่คำตอบอื่น ๆ ส่วนใหญ่ที่นี่การต่อสู้หรือล้มเหลวในการทำเช่นนั้น

die() { echo "$*" >&2; exit 2; }  # complain to STDERR and exit with error
needs_arg() { if [ -z "$OPTARG" ]; then die "No arg for --$OPT option"; fi; }

while getopts ab:c:-: OPT; do
  # support long options: https://stackoverflow.com/a/28466267/519360
  if [ "$OPT" = "-" ]; then   # long option: reformulate OPT and OPTARG
    OPT="${OPTARG%%=*}"       # extract long option name
    OPTARG="${OPTARG#$OPT}"   # extract long option argument (may be empty)
    OPTARG="${OPTARG#=}"      # if long option argument, remove assigning `=`
  fi
  case "$OPT" in
    a | alpha )    alpha=true ;;
    b | bravo )    needs_arg; bravo="$OPTARG" ;;
    c | charlie )  needs_arg; charlie="$OPTARG" ;;
    ??* )          die "Illegal option --$OPT" ;;  # bad long option
    \? )           exit 2 ;;  # bad short option (error reported via getopts)
  esac
done
shift $((OPTIND-1)) # remove parsed options and args from $@ list

เมื่อตัวเลือกเป็นเส้นประ ( -) จะเป็นตัวเลือกที่ยาว getoptsจะมีการแยกวิเคราะห์ตัวเลือกที่ยาวที่เกิดขึ้นจริงเข้ามา$OPTARGเช่น--bravo=fooเดิมกำหนดและOPT='-' ชุดฉันท์กับเนื้อหาของก่อนที่จะเข้าสู่ระบบเท่ากับครั้งแรก ( ในตัวอย่างของเรา) แล้วเอาว่าจากจุดเริ่มต้นของ(ผลผลิตในขั้นตอนนี้หรือสตริงที่ว่างเปล่าถ้าไม่มี) ในที่สุดเราก็ดึงข้อโต้แย้งของผู้นำOPTARG='bravo=foo'if$OPT$OPTARGbravo$OPTARG=foo==สุดท้ายเราตัดอาร์กิวเมนต์ชั้นนำณ จุด$OPTนี้อาจเป็นตัวเลือกสั้น ๆ (หนึ่งตัวอักษร) หรือตัวเลือกยาว (2+ ตัวอักษร)

caseแล้วตรงกับทั้งสั้นหรือยาวตัวเลือก สำหรับตัวเลือกสั้น ๆgetoptsบ่นโดยอัตโนมัติเกี่ยวกับตัวเลือกและข้อโต้แย้งที่ขาดหายไปดังนั้นเราจึงต้องทำซ้ำตัวเองโดยใช้needs_argฟังก์ชั่นซึ่งออกอย่างร้ายแรงเมื่อ$OPTARGว่างเปล่า ??*สภาพจะตรงกับตัวเลือกที่ยาวใด ๆ ที่เหลือ ( ?ตรงกับตัวอักษรตัวเดียวและ*ตรงกับศูนย์หรือมากกว่าดังนั้น??*ตรงกับ 2+ ตัวอักษร) ช่วยให้เราสามารถออกข้อผิดพลาด "ตัวเลือกที่ผิดกฎหมาย" ก่อนที่จะออก

(หมายเหตุเกี่ยวกับชื่อตัวแปร all-uppercase: โดยทั่วไปคำแนะนำคือการสงวนตัวแปร all-uppercase สำหรับการใช้ระบบฉันเก็บ$OPTเป็น all-uppercase เพื่อให้เป็นไปตาม$OPTARGแต่สิ่งนี้จะทำลายการประชุมนั้นฉันคิดว่ามัน พอดีเพราะนี่เป็นสิ่งที่ระบบควรทำและควรปลอดภัยเพราะไม่มีมาตรฐาน (afaik) ที่ใช้ตัวแปรดังกล่าว)


หากต้องการบ่นเกี่ยวกับข้อโต้แย้งที่ไม่คาดคิดกับตัวเลือกแบบยาวให้เลียนแบบสิ่งที่เราทำเพื่อหาข้อโต้แย้งที่บังคับ: ใช้ฟังก์ชันตัวช่วย เพียงพลิกการทดสอบไปรอบ ๆ เพื่อร้องเรียนเกี่ยวกับการโต้แย้งเมื่อไม่มีใครคาดคิด:

no_arg() { if [ -n "$OPTARG" ]; then die "No arg allowed for --$OPT option"; fi; }

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

นี่จะสำเร็จได้โดยใช้หนึ่งในเทคนิคเหล่านี้:

แล้วก็สรุปด้วยบางอย่างเช่น [ $# -gt $OPTIND ] && OPTIND=$((OPTIND+1))


วิธีการแก้ปัญหาในตัวที่ดีมาก คำถามหนึ่ง: เนื่องจากletter-cไม่มีข้อโต้แย้งมันจะไม่เพียงพอที่จะใช้letter-c)หรือไม่? *ดูเหมือนซ้ำซ้อน
ฟิลิป Kearns

1
@Arne อาร์กิวเมนต์ตำแหน่งเป็น UX ที่ไม่ดี; พวกเขายากที่จะเข้าใจและข้อโต้แย้งเพิ่มเติมค่อนข้างยุ่ง getoptsหยุดที่อาร์กิวเมนต์ตำแหน่งแรกเนื่องจากไม่ได้ออกแบบมาเพื่อจัดการกับพวกเขา สิ่งนี้อนุญาตให้คำสั่งย่อยที่มีอาร์กิวเมนต์ของตัวเองเช่นgit diff --colorดังนั้นฉันจะตีความcommand --foo=moo bar --baz wazว่า--fooเป็นอาร์กิวเมนต์เพื่อcommandและ--baz wazเป็นอาร์กิวเมนต์ (พร้อมตัวเลือก) ให้กับbarคำสั่งย่อย ซึ่งสามารถทำได้ด้วยรหัสข้างต้น ฉันปฏิเสธ--bravo -blahเพราะ--bravoต้องมีการโต้แย้งและไม่ชัดเจนว่า-blahไม่ใช่ตัวเลือกอื่น
Adam Katz

1
ฉันไม่เห็นด้วยเกี่ยวกับ UX: อาร์กิวเมนต์ตำแหน่งมีประโยชน์และง่ายตราบใดที่คุณ จำกัด จำนวนของพวกเขา (ให้มากที่สุด 2 หรือ 1 บวก N-of-the-the-type เดียวกัน) มันควรจะเป็นไปได้ที่จะแยกพวกเขาด้วยข้อโต้แย้งคำหลักเพราะผู้ใช้สามารถสร้างคำสั่งทีละขั้นตอน (เช่น ls abc -la)
Arne Babenhauserheide

1
@ AdamKatz: ฉันเขียนบทความเล็ก ๆ นี้: draketo.de/english/free-software/shell-argument-parsing - รวมถึงการอ่านซ้ำของอาร์กิวเมนต์ที่เหลือเพื่อจับตัวเลือกที่ต่อท้าย
Arne Babenhauserheide

1
@ArneBabenhauserheide: ฉันได้อัปเดตคำตอบนี้เพื่อสนับสนุนข้อโต้แย้งที่คั่นด้วยช่องว่าง เนื่องจากต้องการevalใน POSIX เชลล์จึงแสดงรายการด้านล่างของคำตอบที่เหลือ
Adam Katz

33

ลองดูที่shFlagsซึ่งเป็นไลบรารี่แบบพกพา (หมายถึง: sh, bash, dash, ksh, zsh บน Linux, Solaris และอื่น ๆ )

มันทำให้การเพิ่มการตั้งค่าสถานะใหม่ง่ายเหมือนการเพิ่มหนึ่งบรรทัดในสคริปต์ของคุณและมันมีฟังก์ชั่นการใช้งานที่สร้างขึ้นโดยอัตโนมัติ

นี่คือการHello, world!ใช้shFlagง่าย ๆ:

#!/bin/sh

# source shflags from current directory
. ./shflags

# define a 'name' command-line string flag
DEFINE_string 'name' 'world' 'name to say hello to' 'n'

# parse the command-line
FLAGS "$@" || exit 1
eval set -- "${FLAGS_ARGV}"

# say hello
echo "Hello, ${FLAGS_name}!"

สำหรับระบบปฏิบัติการที่มี getopt ขั้นสูงที่รองรับตัวเลือกแบบยาว (เช่น Linux) คุณสามารถทำได้:

$ ./hello_world.sh --name Kate
Hello, Kate!

สำหรับส่วนที่เหลือคุณต้องใช้ตัวเลือกสั้น ๆ :

$ ./hello_world.sh -n Kate
Hello, Kate!

DEFINE_ callเพิ่มธงใหม่เป็นง่ายๆเป็นเพิ่มใหม่


2
มันยอดเยี่ยม แต่น่าเสียดายที่ getopt ของฉัน (OS X) ไม่รองรับช่องว่างในการโต้แย้ง: / สงสัยว่ามีทางเลือกอื่น
Alastair Stuart

@AlastairStuart - มีอีกทางเลือกหนึ่งใน OS X ใช้ MacPorts เพื่อติดตั้ง GNU getopt (โดยปกติจะติดตั้งใน / opt / local / bin / getopt)
Urban Vagabond

3
@UrbanVagabond - การติดตั้งเครื่องมือเริ่มต้นที่ไม่ใช่ระบบนั้นไม่ใช่ความต้องการที่ยอมรับได้สำหรับเครื่องมือแบบพกพาที่เพียงพอ
Alastair Stuart

@AlastairStuart - ดูคำตอบของฉันสำหรับโซลูชันแบบพกพาที่ใช้ getopts ในตัวแทนที่จะเป็น GNU getopt มันเหมือนกับการใช้งาน getopts พื้นฐาน แต่มีการวนซ้ำเป็นพิเศษสำหรับตัวเลือกที่ยาว
Adam Katz

31

ใช้getoptsกับตัวเลือกสั้นและยาวและข้อโต้แย้ง


ใช้ได้กับชุดค่าผสมทั้งหมดเช่น:

  • foobar -f --bar
  • foobar --f--b
  • foobar -bf --bar --foobar
  • foobar -fbFBAshorty --bar -FB - อาร์กิวเมนต์ = longhorn
  • foobar -fA "text shorty" -B --arguments = "text longhorn"
  • ทุบตี foobar -F --barfoo
  • sh foobar -B --foobar - ...
  • bash ./foobar -F --bar

การประกาศบางอย่างสำหรับตัวอย่างนี้

Options=$@
Optnum=$#
sfoo='no '
sbar='no '
sfoobar='no '
sbarfoo='no '
sarguments='no '
sARG=empty
lfoo='no '
lbar='no '
lfoobar='no '
lbarfoo='no '
larguments='no '
lARG=empty

ลักษณะการใช้งานจะเป็นอย่างไร

function _usage() 
{
  ###### U S A G E : Help and ERROR ######
  cat <<EOF
   foobar $Options
  $*
          Usage: foobar <[options]>
          Options:
                  -b   --bar            Set bar to yes    ($foo)
                  -f   --foo            Set foo to yes    ($bart)
                  -h   --help           Show this message
                  -A   --arguments=...  Set arguments to yes ($arguments) AND get ARGUMENT ($ARG)
                  -B   --barfoo         Set barfoo to yes ($barfoo)
                  -F   --foobar         Set foobar to yes ($foobar)
EOF
}

[ $# = 0 ] && _usage "  >>>>>>>> no options given "

getops ด้วยธงยาว / สั้นรวมทั้งอาร์กิวเมนต์ที่มีความยาว

while getopts ':bfh-A:BF' OPTION ; do
  case "$OPTION" in
    b  ) sbar=yes                       ;;
    f  ) sfoo=yes                       ;;
    h  ) _usage                         ;;   
    A  ) sarguments=yes;sARG="$OPTARG"  ;;
    B  ) sbarfoo=yes                    ;;
    F  ) sfoobar=yes                    ;;
    -  ) [ $OPTIND -ge 1 ] && optind=$(expr $OPTIND - 1 ) || optind=$OPTIND
         eval OPTION="\$$optind"
         OPTARG=$(echo $OPTION | cut -d'=' -f2)
         OPTION=$(echo $OPTION | cut -d'=' -f1)
         case $OPTION in
             --foo       ) lfoo=yes                       ;;
             --bar       ) lbar=yes                       ;;
             --foobar    ) lfoobar=yes                    ;;
             --barfoo    ) lbarfoo=yes                    ;;
             --help      ) _usage                         ;;
             --arguments ) larguments=yes;lARG="$OPTARG"  ;; 
             * )  _usage " Long: >>>>>>>> invalid options (long) " ;;
         esac
       OPTIND=1
       shift
      ;;
    ? )  _usage "Short: >>>>>>>> invalid options (short) "  ;;
  esac
done

เอาท์พุต

##################################################################
echo "----------------------------------------------------------"
echo "RESULT short-foo      : $sfoo                                    long-foo      : $lfoo"
echo "RESULT short-bar      : $sbar                                    long-bar      : $lbar"
echo "RESULT short-foobar   : $sfoobar                                 long-foobar   : $lfoobar"
echo "RESULT short-barfoo   : $sbarfoo                                 long-barfoo   : $lbarfoo"
echo "RESULT short-arguments: $sarguments  with Argument = \"$sARG\"   long-arguments: $larguments and $lARG"

รวมข้างต้นเป็นสคริปต์ที่เหนียว

#!/bin/bash
# foobar: getopts with short and long options AND arguments

function _cleanup ()
{
  unset -f _usage _cleanup ; return 0
}

## Clear out nested functions on exit
trap _cleanup INT EXIT RETURN

###### some declarations for this example ######
Options=$@
Optnum=$#
sfoo='no '
sbar='no '
sfoobar='no '
sbarfoo='no '
sarguments='no '
sARG=empty
lfoo='no '
lbar='no '
lfoobar='no '
lbarfoo='no '
larguments='no '
lARG=empty

function _usage() 
{
  ###### U S A G E : Help and ERROR ######
  cat <<EOF
   foobar $Options
   $*
          Usage: foobar <[options]>
          Options:
                  -b   --bar            Set bar to yes    ($foo)
                    -f   --foo            Set foo to yes    ($bart)
                      -h   --help           Show this message
                  -A   --arguments=...  Set arguments to yes ($arguments) AND get ARGUMENT ($ARG)
                  -B   --barfoo         Set barfoo to yes ($barfoo)
                  -F   --foobar         Set foobar to yes ($foobar)
EOF
}

[ $# = 0 ] && _usage "  >>>>>>>> no options given "

##################################################################    
#######  "getopts" with: short options  AND  long options  #######
#######            AND  short/long arguments               #######
while getopts ':bfh-A:BF' OPTION ; do
  case "$OPTION" in
    b  ) sbar=yes                       ;;
    f  ) sfoo=yes                       ;;
    h  ) _usage                         ;;   
    A  ) sarguments=yes;sARG="$OPTARG"  ;;
    B  ) sbarfoo=yes                    ;;
    F  ) sfoobar=yes                    ;;
    -  ) [ $OPTIND -ge 1 ] && optind=$(expr $OPTIND - 1 ) || optind=$OPTIND
         eval OPTION="\$$optind"
         OPTARG=$(echo $OPTION | cut -d'=' -f2)
         OPTION=$(echo $OPTION | cut -d'=' -f1)
         case $OPTION in
             --foo       ) lfoo=yes                       ;;
             --bar       ) lbar=yes                       ;;
             --foobar    ) lfoobar=yes                    ;;
             --barfoo    ) lbarfoo=yes                    ;;
             --help      ) _usage                         ;;
               --arguments ) larguments=yes;lARG="$OPTARG"  ;; 
             * )  _usage " Long: >>>>>>>> invalid options (long) " ;;
         esac
       OPTIND=1
       shift
      ;;
    ? )  _usage "Short: >>>>>>>> invalid options (short) "  ;;
  esac
done

สิ่งนี้ใช้ไม่ได้กับอาร์กิวเมนต์ที่มีความยาวมากกว่าหนึ่งตัว (-) ดูเหมือนว่าจะอ่านเพียงคนแรกสำหรับฉัน
Sinaesthetic

@Sinaesthetic - ใช่ฉันกำลังเล่นกับevalวิธีการสำหรับข้อโต้แย้งระยะยาวในตัวเลือกยาวและพบว่ามันไม่น่าเชื่อถือกับเชลล์บางตัว (แม้ว่าฉันคาดว่ามันจะใช้งานได้กับ bash ซึ่งในกรณีนี้คุณไม่จำเป็นต้องใช้eval) ดูคำตอบของฉันสำหรับวิธีการยอมรับอาร์กิวเมนต์ตัวเลือกแบบยาว=และความพยายามในการใช้พื้นที่ โซลูชันของฉันไม่โทรออกภายนอกขณะที่สายนี้ใช้cutสองสามครั้ง
Adam Katz

24

อีกวิธีหนึ่ง ...

# translate long options to short
for arg
do
    delim=""
    case "$arg" in
       --help) args="${args}-h ";;
       --verbose) args="${args}-v ";;
       --config) args="${args}-c ";;
       # pass through anything else
       *) [[ "${arg:0:1}" == "-" ]] || delim="\""
           args="${args}${delim}${arg}${delim} ";;
    esac
done
# reset the translated args
eval set -- $args
# now we can process with getopt
while getopts ":hvc:" opt; do
    case $opt in
        h)  usage ;;
        v)  VERBOSE=true ;;
        c)  source $OPTARG ;;
        \?) usage ;;
        :)
        echo "option -$OPTARG requires an argument"
        usage
        ;;
    esac
done

1
สิ่งนี้ไม่ต้องการพื้นที่ในการ$argsมอบหมายใหม่แต่ละครั้งหรือไม่? สิ่งนี้สามารถทำได้โดยไม่ใช้วิธีการทุบตี แต่รหัสนี้จะสูญเสียช่องว่างในตัวเลือกและการขัดแย้ง (ฉันไม่คิดว่า$delimเคล็ดลับจะทำงานได้) คุณสามารถเรียกใช้แทนset ภายในforห่วงถ้าคุณมีความระมัดระวังพอที่จะว่างบนเท่านั้นซ้ำแรก นี่คือรุ่นที่ปลอดภัยยิ่งขึ้นโดยไม่มีการทุบตี
Adam Katz

18

ฉันได้รับการแก้ไขด้วยวิธีนี้:

# A string with command options
options=$@

# An array with all the arguments
arguments=($options)

# Loop index
index=0

for argument in $options
  do
    # Incrementing index
    index=`expr $index + 1`

    # The conditions
    case $argument in
      -a) echo "key $argument value ${arguments[index]}" ;;
      -abc) echo "key $argument value ${arguments[index]}" ;;
    esac
  done

exit;

ฉันเป็นคนงี่เง่าหรือเปล่า? getoptและgetoptsทำให้สับสน


1
ดูเหมือนว่าจะใช้งานได้สำหรับฉันฉันไม่ทราบว่าปัญหาเกิดขึ้นจากวิธีนี้ แต่ดูเหมือนง่ายดังนั้นจึงต้องมีเหตุผลที่ทุกคนไม่ได้ใช้งาน
Billy Moon

1
@Billy ใช่นี่เป็นเรื่องง่ายเพราะฉันไม่ได้ใช้สคริปต์ใด ๆ เพื่อจัดการพารามิเตอร์ของฉันและอื่น ๆ โดยทั่วไปฉันจะแปลงสตริงอาร์กิวเมนต์ ($ @) เป็นอาร์เรย์และฉันวนไปเรื่อย ๆ ในลูปค่าปัจจุบันจะเป็นกุญแจสำคัญและค่าถัดไปจะเป็นค่า เรียบง่ายเหมือนที่

1
@ ธีโอดอร์ฉันดีใจที่นี่มีประโยชน์กับคุณ! มันเป็นความเจ็บปวดสำหรับฉันเช่นกัน หากคุณสนใจคุณสามารถดูตัวอย่างของการดำเนินการได้ที่นี่: raw.github.com/rafaelrinaldi/swf-to-html/master/swf-to-html.sh

2
แน่นอนว่าเป็นวิธีที่ง่ายที่สุดที่ฉันเคยเห็น ฉันเปลี่ยนมันเล็กน้อยเช่นใช้ i = $ (($ i + 1)) แทน expr แต่คอนเซ็ปต์ไม่แน่น
โทมัส Dignan

6
คุณไม่ได้โง่เลย แต่คุณอาจขาดคุณสมบัติ: getopt (s) สามารถจดจำตัวเลือกต่าง ๆ ที่ผสมกัน (เช่น: -ltrหรือ-lt -rเช่นเดียวกับ-l -t -r) และยังให้การจัดการข้อผิดพลาดและวิธีที่ง่ายในการเลื่อนพารามิเตอร์ที่ได้รับการรักษาออกไปเมื่อการรักษาตัวเลือกเสร็จสิ้น
Olivier Dulac

14

ในกรณีที่คุณไม่ต้องการการgetoptพึ่งพาคุณสามารถทำได้:

while test $# -gt 0
do
  case $1 in

  # Normal option processing
    -h | --help)
      # usage and help
      ;;
    -v | --version)
      # version info
      ;;
  # ...

  # Special cases
    --)
      break
      ;;
    --*)
      # error unknown (long) option $1
      ;;
    -?)
      # error unknown (short) option $1
      ;;

  # FUN STUFF HERE:
  # Split apart combined short options
    -*)
      split=$1
      shift
      set -- $(echo "$split" | cut -c 2- | sed 's/./-& /g') "$@"
      continue
      ;;

  # Done with options
    *)
      break
      ;;
  esac

  # for testing purposes:
  echo "$1"

  shift
done

แน่นอนว่าคุณไม่สามารถใช้ตัวเลือกสไตล์แบบยาวกับขีดสั้น ๆ และถ้าคุณต้องการเพิ่มเวอร์ชันที่ย่อให้สั้นลง (เช่น --verbos แทน --verbose) คุณต้องเพิ่มมันด้วยตนเอง

แต่ถ้าคุณกำลังมองหาgetoptsฟังก์ชั่นพร้อมกับตัวเลือกยาว ๆ นี่เป็นวิธีที่ง่ายที่จะทำ

ฉันยังวางข้อมูลนี้ในส่วนสำคัญ


ดูเหมือนว่าจะใช้งานได้กับตัวเลือกที่ยาวเพียงตัวเดียวในแต่ละครั้ง ขอบคุณ!
kingjeffrey

ในกรณีพิเศษ--)ดูเหมือนว่าจะshift ;หายไป ในขณะนี้--จะยังคงเป็นอาร์กิวเมนต์ตัวเลือกแรก
dgw

ฉันคิดว่านี่เป็นคำตอบที่ดีกว่าแม้ว่า dgw จะชี้ให้เห็นว่า--ตัวเลือกนั้นจำเป็นต้องshiftมี ฉันบอกว่าสิ่งนี้ดีกว่าเพราะทางเลือกเป็นทั้งเวอร์ชันที่ขึ้นกับแพลตฟอร์มของgetoptหรือgetopts_longคุณต้องบังคับให้ใช้ตัวเลือกสั้น ๆ เมื่อเริ่มต้นคำสั่ง (เช่น - คุณใช้getoptsแล้วประมวลผลตัวเลือกที่ยาวหลังจากนั้น) และการควบคุมที่สมบูรณ์
Haravikk

คำตอบนี้ทำให้ฉันสงสัยว่าทำไมเรามีหลายสิบคำตอบในการทำงานที่สามารถทำได้โดยไม่มีอะไรมากไปกว่าวิธีการแก้ปัญหาที่ชัดเจนและตรงไปตรงมาและถ้ามีเหตุผลสำหรับพันล้าน getopt (s) ใช้กรณีอื่นนอกเหนือจากการพิสูจน์ ตัวเอง
Florian Heigl

11

บิวท์อินgetoptsไม่สามารถทำได้ มีโปรแกรมgetopt (1) ภายนอกที่สามารถทำสิ่งนี้ได้ แต่คุณจะหาได้จาก Linux จากแพ็กเกจutil-linux มันมาพร้อมกับสคริปต์ตัวอย่างgetopt-parse.bash

นอกจากนี้ยังมีการgetopts_longเขียนเป็นฟังก์ชั่นเปลือก


3
The getoptถูกรวมอยู่ใน FreeBSD เวอร์ชัน 1.0 ในปี 1993 และเป็นส่วนหนึ่งของ FreeBSD ตั้งแต่นั้นมา ดังนั้นมันจึงถูกนำมาใช้จาก FreeBSD 4.x เพื่อรวมไว้ในโครงการ Darwin ของ Apple ตั้งแต่ OS X 10.6.8 หน้า man ที่รวมอยู่ใน Apple ยังคงเป็นหน้าสำเนาของ FreeBSD ที่ซ้ำกัน ใช่แล้วมันรวมอยู่ใน OS X และ gobs ของระบบปฏิบัติการอื่นนอกเหนือจาก Linux -1 ในคำตอบนี้สำหรับข้อมูลที่ผิด
ghoti

8
#!/bin/bash
while getopts "abc:d:" flag
do
  case $flag in
    a) echo "[getopts:$OPTIND]==> -$flag";;
    b) echo "[getopts:$OPTIND]==> -$flag";;
    c) echo "[getopts:$OPTIND]==> -$flag $OPTARG";;
    d) echo "[getopts:$OPTIND]==> -$flag $OPTARG";;
  esac
done

shift $((OPTIND-1))
echo "[otheropts]==> $@"

exit

.

#!/bin/bash
until [ -z "$1" ]; do
  case $1 in
    "--dlong")
      shift
      if [ "${1:1:0}" != "-" ]
      then
        echo "==> dlong $1"
        shift
      fi;;
    *) echo "==> other $1"; shift;;
  esac
done
exit

2
คำอธิบายจะดี สคริปต์แรกยอมรับตัวเลือกสั้น ๆ เฉพาะในขณะที่สคริปต์ที่สองมีข้อบกพร่องในการแยกอาร์กิวเมนต์ตัวเลือกยาว ตัวแปรควรใช้"${1:0:1}"สำหรับอาร์กิวเมนต์ # 1, สตริงย่อยที่ดัชนี 0, ความยาว 1 ซึ่งไม่อนุญาตให้ผสมตัวเลือกแบบสั้นและแบบยาว
Adam Katz

7

ในksh93, getoptsไม่สนับสนุนชื่อที่ยาว ...

while getopts "f(file):s(server):" flag
do
    echo "$flag" $OPTIND $OPTARG
done

หรือดังนั้นบทเรียนที่ฉันได้พบได้กล่าวว่า ลองและดู


4
นี่คือ getopts ของ ksh93 ในตัว นอกเหนือจากซินแท็กซ์นี้มันยังมีซินแทกซ์ที่ซับซ้อนกว่าซึ่งอนุญาตให้ตัวเลือกแบบยาวโดยไม่ต้องเทียบเท่ากันสั้น ๆ
jilles

2
คำตอบที่สมเหตุสมผล OP ไม่ได้ระบุว่าเชลล์คืออะไร
ghoti

6

ฉันเขียนเชลล์สคริปท์ตอนนี้และออกจากการฝึกฝนดังนั้นข้อเสนอแนะใด ๆ ที่ชื่นชม

การใช้กลยุทธ์ที่เสนอโดย @Arvid Requate เราสังเกตเห็นข้อผิดพลาดของผู้ใช้บางคน ผู้ใช้ที่ลืมรวมค่าจะบังเอิญได้ชื่อของตัวเลือกถัดไปที่ถือว่าเป็นค่า:

./getopts_test.sh --loglevel= --toc=TRUE

จะทำให้ค่าของ "loglevel" ถูกมองว่าเป็น "--toc = TRUE" สิ่งนี้สามารถหลีกเลี่ยงได้

ฉันปรับเปลี่ยนแนวคิดบางประการเกี่ยวกับการตรวจสอบข้อผิดพลาดของผู้ใช้สำหรับ CLI จาก http://mwiki.wooledge.org/BashFAQ/035การอภิปรายการแยกวิเคราะห์ด้วยตนเอง ฉันแทรกการตรวจสอบข้อผิดพลาดในการจัดการทั้งอาร์กิวเมนต์ "-" และ "-"

จากนั้นฉันก็เริ่มเล่นซอกับไวยากรณ์ดังนั้นข้อผิดพลาดใด ๆ ในที่นี้เป็นความผิดของฉันอย่างเคร่งครัดไม่ใช่ผู้เขียนต้นฉบับ

วิธีการของฉันช่วยให้ผู้ใช้ที่ต้องการเข้าสู่ระยะยาวโดยมีหรือไม่มีเครื่องหมายเท่ากับ นั่นคือควรมีการตอบสนองแบบเดียวกันกับ "--loglevel 9" เป็น "--loglevel = 9" ในเมธอด - / space เป็นไปไม่ได้ที่จะทราบได้อย่างแน่นอนว่าผู้ใช้ลืมอาร์กิวเมนต์ดังนั้นจำเป็นต้องมีการคาดเดาบางอย่าง

  1. หากผู้ใช้มีรูปแบบเครื่องหมายยาว / เท่ากับ (--opt =) ดังนั้นช่องว่างหลังจาก = จะก่อให้เกิดข้อผิดพลาดเนื่องจากไม่ได้ระบุอาร์กิวเมนต์
  2. หากผู้ใช้มีข้อโต้แย้งยาว / พื้นที่ (--opt) สคริปต์นี้ทำให้เกิดความล้มเหลวหากไม่มีข้อโต้แย้งดังต่อไปนี้ (สิ้นสุดของคำสั่ง) หรือถ้าอาร์กิวเมนต์เริ่มต้นด้วยเส้นประ)

ในกรณีที่คุณเริ่มต้นจากสิ่งนี้มีความแตกต่างที่น่าสนใจระหว่างรูปแบบ "--opt = value" และ "--opt value" ด้วยเครื่องหมายเท่ากับอาร์กิวเมนต์บรรทัดคำสั่งถูกมองว่าเป็น "opt = value" และงานที่ต้องจัดการนั่นคือการแยกสตริงเพื่อแยกที่ "=" ในทางตรงกันข้ามกับ "--opt value" ชื่อของอาร์กิวเมนต์คือ "opt" และเรามีความท้าทายในการรับค่าถัดไปที่ระบุในบรรทัดคำสั่ง นั่นคือที่ @Arvid Requestate ใช้ $ {! OPTIND} การอ้างอิงทางอ้อม ฉันยังไม่เข้าใจอย่างนั้นเลยและความคิดเห็นใน BashFAQ ดูเหมือนจะเตือนสไตล์นั้น ( http://mywiki.wooledge.org/BashFAQ/006 ) BTW ฉันไม่คิดว่าข้อคิดเห็นของผู้โพสต์ก่อนหน้าเกี่ยวกับความสำคัญของ OPTIND = $ (($ OPTIND + 1)) ถูกต้อง ฉันหมายถึงพูด

ในสคริปต์เวอร์ชันล่าสุดแฟล็ก -v หมายถึง VERBOSE printout

บันทึกไว้ในไฟล์ชื่อ "cli-5.sh" ทำให้สามารถเรียกใช้งานได้และสิ่งเหล่านี้จะสามารถใช้งานได้หรือล้มเหลวในวิธีที่ต้องการ

./cli-5.sh  -v --loglevel=44 --toc  TRUE
./cli-5.sh  -v --loglevel=44 --toc=TRUE
./cli-5.sh --loglevel 7
./cli-5.sh --loglevel=8
./cli-5.sh -l9

./cli-5.sh  --toc FALSE --loglevel=77
./cli-5.sh  --toc=FALSE --loglevel=77

./cli-5.sh   -l99 -t yyy
./cli-5.sh   -l 99 -t yyy

นี่คือตัวอย่างเอาต์พุตของการตรวจสอบข้อผิดพลาดบน user intpu

$ ./cli-5.sh  --toc --loglevel=77
ERROR: toc value must not have dash at beginning
$ ./cli-5.sh  --toc= --loglevel=77
ERROR: value for toc undefined

คุณควรพิจารณาที่จะเปิด -v เพราะมันจะพิมพ์ internals ของ OPTIND และ OPTARG

#/usr/bin/env bash

## Paul Johnson
## 20171016
##

## Combines ideas from
## /programming/402377/using-getopts-in-bash-shell-script-to-get-long-and-short-command-line-options
## by @Arvid Requate, and http://mwiki.wooledge.org/BashFAQ/035

# What I don't understand yet: 
# In @Arvid REquate's answer, we have 
# val="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 ))
# this works, but I don't understand it!


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
loglevel=0
toc="TRUE"

optspec=":vhl:t:-:"
while getopts "$optspec" OPTCHAR; do

    showme "OPTARG:  ${OPTARG[*]}"
    showme "OPTIND:  ${OPTIND[*]}"
    case "${OPTCHAR}" in
        -)
            case "${OPTARG}" in
                loglevel) #argument has no equal sign
                    opt=${OPTARG}
                    val="${!OPTIND}"
                    ## check value. If negative, assume user forgot value
                    showme "OPTIND is {$OPTIND} {!OPTIND} has value \"${!OPTIND}\""
                    if [[ "$val" == -* ]]; then
                        die "ERROR: $opt value must not have dash at beginning"
                    fi
                    ## OPTIND=$(( $OPTIND + 1 )) # CAUTION! no effect?
                    printparse "--${OPTARG}" "  " "${val}"
                    loglevel="${val}"
                    shift
                    ;;
                loglevel=*) #argument has equal sign
                    opt=${OPTARG%=*}
                    val=${OPTARG#*=}
                    if [ "${OPTARG#*=}" ]; then
                        printparse "--${opt}" "=" "${val}"
                        loglevel="${val}"
                        ## shift CAUTION don't shift this, fails othewise
                    else
                        die "ERROR: $opt value must be supplied"
                    fi
                    ;;
                toc) #argument has no equal sign
                    opt=${OPTARG}
                    val="${!OPTIND}"
                    ## check value. If negative, assume user forgot value
                    showme "OPTIND is {$OPTIND} {!OPTIND} has value \"${!OPTIND}\""
                    if [[ "$val" == -* ]]; then
                        die "ERROR: $opt value must not have dash at beginning"
                    fi
                    ## OPTIND=$(( $OPTIND + 1 )) #??
                    printparse "--${opt}" " " "${val}"
                    toc="${val}"
                    shift
                    ;;
                toc=*) #argument has equal sign
                    opt=${OPTARG%=*}
                    val=${OPTARG#*=}
                    if [ "${OPTARG#*=}" ]; then
                        toc=${val}
                        printparse "--$opt" " -> " "$toc"
                        ##shift ## NO! dont shift this
                    else
                        die "ERROR: value for $opt undefined"
                    fi
                    ;;

                help)
                    echo "usage: $0 [-v] [--loglevel[=]<value>] [--toc[=]<TRUE,FALSE>]" >&2
                    exit 2
                    ;;
                *)
                    if [ "$OPTERR" = 1 ] && [ "${optspec:0:1}" != ":" ]; then
                        echo "Unknown option --${OPTARG}" >&2
                    fi
                    ;;
            esac;;
        h|-\?|--help)
            ## must rewrite this for all of the arguments
            echo "usage: $0 [-v] [--loglevel[=]<value>] [--toc[=]<TRUE,FALSE>]" >&2
            exit 2
            ;;
        l)
            loglevel=${OPTARG}
            printparse "-l" " "  "${loglevel}"
            ;;
        t)
            toc=${OPTARG}
            ;;
        v)
            VERBOSE=1
            ;;

        *)
            if [ "$OPTERR" != 1 ] || [ "${optspec:0:1}" = ":" ]; then
                echo "Non-option argument: '-${OPTARG}'" >&2
            fi
            ;;
    esac
done



echo "
After Parsing values
"
echo "loglevel  $loglevel" 
echo "toc  $toc"

OPTIND=$(( $OPTIND + 1 )): มันเป็นสิ่งจำเป็นเมื่อใดก็ตามที่คุณ 'ฮุบ' พารามิเตอร์ของ OPTIND (ตัวอย่างเช่น: เมื่อใช้แล้ว--toc value : ค่าอยู่ในหมายเลขพารามิเตอร์ $ OPTIND เมื่อคุณดึงข้อมูลสำหรับค่าของ toc คุณควรบอก getopts ว่าพารามิเตอร์ตัวถัดไปในการแยกไม่ใช่ค่า แต่อย่างใดอย่างหนึ่งหลังจากนั้น (ดังนั้น: OPTIND=$(( $OPTIND + 1 )) . และสคริปต์ของคุณ (เช่นเดียวกับสคริปต์ที่คุณอ้างถึง) จะหายไปหลังจากทำเสร็จแล้ว: shift $(( $OPTIND -1 ))(เมื่อ getopts ออกจากหลังจากการแยกพารามิเตอร์ parameterrs 1 ถึง OPTIND-1 คุณต้องเลื่อนออก$@ขณะนี้เป็นพารามิเตอร์ "ที่ไม่ใช่ตัวเลือก" ที่เหลืออยู่
Olivier Dulac

โอ้ขณะที่คุณเลื่อนตัวเองคุณ "เปลี่ยน" พารามิเตอร์ภายใต้ getopts ดังนั้น OPTIND จึงชี้สิ่งที่ถูกต้องเสมอ ... แต่ฉันคิดว่ามันสับสนมาก ฉันเชื่อว่า (ไม่สามารถทดสอบสคริปต์ของคุณได้ในขณะนี้) ว่าคุณยังต้องการกะ $ (($ OPTIND - 1)) หลังจาก getopts ขณะที่วนซ้ำดังนั้นตอนนี้ $ 1 จะไม่ชี้ไปที่ $ 1 เดิม (ตัวเลือก) แต่ เป็นอาร์กิวเมนต์แรกที่เหลืออยู่ (ตัวเลือกที่ทำหลังจากตัวเลือกทั้งหมดและค่าของอาร์กิวเมนต์) เช่น myrm -foo -bar = baz นี้ thenthisone thanoneanan
Olivier Dulac

5

คิดค้นล้ออีกรุ่นหนึ่ง ...

ฟังก์ชั่นนี้คือการเปลี่ยนเชลล์เบิร์นธรรมดา (หวังว่า) POSIX สำหรับ GNU getopt รองรับตัวเลือกแบบสั้น / ยาวที่สามารถยอมรับอาร์กิวเมนต์บังคับ / ตัวเลือก / ไม่มีและวิธีการระบุตัวเลือกนั้นเกือบจะเหมือนกับ GNU getopt ดังนั้นการแปลงจึงเป็นเรื่องเล็กน้อย

แน่นอนว่านี่ยังคงเป็นโค้ดขนาดใหญ่ที่จะดรอปลงในสคริปต์ แต่มันก็ประมาณครึ่งหนึ่งของฟังก์ชั่นเชลล์ที่รู้จักกันดีของ getopt_long และอาจจะดีกว่าในกรณีที่คุณต้องการแทนที่ GNU getopt ที่ใช้อยู่ในปัจจุบัน

นี่เป็นรหัสใหม่ที่สวยดังนั้น YMMV (และแน่นอนโปรดแจ้งให้เราทราบหากนี่ไม่ใช่ POSIX ที่เข้ากันได้จริงไม่ว่าด้วยเหตุผลใด - การพกพาเป็นความตั้งใจตั้งแต่เริ่มแรก แต่ฉันไม่มีสภาพแวดล้อมการทดสอบ POSIX ที่มีประโยชน์)

การใช้รหัสและตัวอย่างดังต่อไปนี้:

#!/bin/sh
# posix_getopt shell function
# Author: Phil S.
# Version: 1.0
# Created: 2016-07-05
# URL: http://stackoverflow.com/a/37087374/324105

# POSIX-compatible argument quoting and parameter save/restore
# http://www.etalabs.net/sh_tricks.html
# Usage:
# parameters=$(save "$@") # save the original parameters.
# eval "set -- ${parameters}" # restore the saved parameters.
save () {
    local param
    for param; do
        printf %s\\n "$param" \
            | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/"
    done
    printf %s\\n " "
}

# Exit with status $1 after displaying error message $2.
exiterr () {
    printf %s\\n "$2" >&2
    exit $1
}

# POSIX-compatible command line option parsing.
# This function supports long options and optional arguments, and is
# a (largely-compatible) drop-in replacement for GNU getopt.
#
# Instead of:
# opts=$(getopt -o "$shortopts" -l "$longopts" -- "$@")
# eval set -- ${opts}
#
# We instead use:
# opts=$(posix_getopt "$shortopts" "$longopts" "$@")
# eval "set -- ${opts}"
posix_getopt () { # args: "$shortopts" "$longopts" "$@"
    local shortopts longopts \
          arg argtype getopt nonopt opt optchar optword suffix

    shortopts="$1"
    longopts="$2"
    shift 2

    getopt=
    nonopt=
    while [ $# -gt 0 ]; do
        opt=
        arg=
        argtype=
        case "$1" in
            # '--' means don't parse the remaining options
            ( -- ) {
                getopt="${getopt}$(save "$@")"
                shift $#
                break
            };;
            # process short option
            ( -[!-]* ) {         # -x[foo]
                suffix=${1#-?}   # foo
                opt=${1%$suffix} # -x
                optchar=${opt#-} # x
                case "${shortopts}" in
                    ( *${optchar}::* ) { # optional argument
                        argtype=optional
                        arg="${suffix}"
                        shift
                    };;
                    ( *${optchar}:* ) { # required argument
                        argtype=required
                        if [ -n "${suffix}" ]; then
                            arg="${suffix}"
                            shift
                        else
                            case "$2" in
                                ( -* ) exiterr 1 "$1 requires an argument";;
                                ( ?* ) arg="$2"; shift 2;;
                                (  * ) exiterr 1 "$1 requires an argument";;
                            esac
                        fi
                    };;
                    ( *${optchar}* ) { # no argument
                        argtype=none
                        arg=
                        shift
                        # Handle multiple no-argument parameters combined as
                        # -xyz instead of -x -y -z. If we have just shifted
                        # parameter -xyz, we now replace it with -yz (which
                        # will be processed in the next iteration).
                        if [ -n "${suffix}" ]; then
                            eval "set -- $(save "-${suffix}")$(save "$@")"
                        fi
                    };;
                    ( * ) exiterr 1 "Unknown option $1";;
                esac
            };;
            # process long option
            ( --?* ) {            # --xarg[=foo]
                suffix=${1#*=}    # foo (unless there was no =)
                if [ "${suffix}" = "$1" ]; then
                    suffix=
                fi
                opt=${1%=$suffix} # --xarg
                optword=${opt#--} # xarg
                case ",${longopts}," in
                    ( *,${optword}::,* ) { # optional argument
                        argtype=optional
                        arg="${suffix}"
                        shift
                    };;
                    ( *,${optword}:,* ) { # required argument
                        argtype=required
                        if [ -n "${suffix}" ]; then
                            arg="${suffix}"
                            shift
                        else
                            case "$2" in
                                ( -* ) exiterr 1 \
                                       "--${optword} requires an argument";;
                                ( ?* ) arg="$2"; shift 2;;
                                (  * ) exiterr 1 \
                                       "--${optword} requires an argument";;
                            esac
                        fi
                    };;
                    ( *,${optword},* ) { # no argument
                        if [ -n "${suffix}" ]; then
                            exiterr 1 "--${optword} does not take an argument"
                        fi
                        argtype=none
                        arg=
                        shift
                    };;
                    ( * ) exiterr 1 "Unknown option $1";;
                esac
            };;
            # any other parameters starting with -
            ( -* ) exiterr 1 "Unknown option $1";;
            # remember non-option parameters
            ( * ) nonopt="${nonopt}$(save "$1")"; shift;;
        esac

        if [ -n "${opt}" ]; then
            getopt="${getopt}$(save "$opt")"
            case "${argtype}" in
                ( optional|required ) {
                    getopt="${getopt}$(save "$arg")"
                };;
            esac
        fi
    done

    # Generate function output, suitable for:
    # eval "set -- $(posix_getopt ...)"
    printf %s "${getopt}"
    if [ -n "${nonopt}" ]; then
        printf %s "$(save "--")${nonopt}"
    fi
}

ตัวอย่างการใช้งาน:

# Process command line options
shortopts="hvd:c::s::L:D"
longopts="help,version,directory:,client::,server::,load:,delete"
#opts=$(getopt -o "$shortopts" -l "$longopts" -n "$(basename $0)" -- "$@")
opts=$(posix_getopt "$shortopts" "$longopts" "$@")
if [ $? -eq 0 ]; then
    #eval set -- ${opts}
    eval "set -- ${opts}"
    while [ $# -gt 0 ]; do
        case "$1" in
            ( --                ) shift; break;;
            ( -h|--help         ) help=1; shift; break;;
            ( -v|--version      ) version_help=1; shift; break;;
            ( -d|--directory    ) dir=$2; shift 2;;
            ( -c|--client       ) useclient=1; client=$2; shift 2;;
            ( -s|--server       ) startserver=1; server_name=$2; shift 2;;
            ( -L|--load         ) load=$2; shift 2;;
            ( -D|--delete       ) delete=1; shift;;
        esac
    done
else
    shorthelp=1 # getopt returned (and reported) an error.
fi

4

คำตอบที่ยอมรับนั้นเป็นงานที่ดีมากในการชี้ให้เห็นข้อบกพร่องทั้งหมดของ bash ในgetoptsตัว คำตอบลงท้ายด้วย:

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

และแม้ว่าฉันจะเห็นด้วยในหลักการกับข้อความนั้นฉันรู้สึกว่าจำนวนครั้งที่เราใช้คุณลักษณะนี้ในสคริปต์ต่าง ๆ แสดงให้เห็นถึงการใช้ความพยายามเล็กน้อยในการสร้างโซลูชันที่ได้มาตรฐานและผ่านการทดสอบแล้ว

ด้วยเหตุนี้ฉันจึงได้ "อัปเกรด" bash ในตัวgetoptsโดยการใช้งานgetopts_longใน bash บริสุทธิ์โดยไม่มีการพึ่งพาจากภายนอก การใช้งานของฟังก์ชั่นเป็น 100% getoptsเข้ากันได้กับในตัว

โดยการรวมgetopts_long(ซึ่งโฮสต์อยู่บน GitHub ) ในสคริปต์คำตอบของคำถามเดิมสามารถนำไปปฏิบัติได้ง่าย ๆ เช่นเดียวกับ:

source "${PATH_TO}/getopts_long.bash"

while getopts_long ':c: copyfile:' OPTKEY; do
    case ${OPTKEY} in
        'c'|'copyfile')
            echo 'file supplied -- ${OPTARG}'
            ;;
        '?')
            echo "INVALID OPTION -- ${OPTARG}" >&2
            exit 1
            ;;
        ':')
            echo "MISSING ARGUMENT for option -- ${OPTARG}" >&2
            exit 1
            ;;
        *)
            echo "Misconfigured OPTSPEC or uncaught option -- ${OPTKEY}" >&2
            exit 1
            ;;
    esac
done

shift $(( OPTIND - 1 ))
[[ "${1}" == "--" ]] && shift

3

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

ฉันยังได้เพิ่มการใช้งานตัวอย่างและข้อความช่วยเหลืออีกด้วย ฉันจะรวมรุ่นขยายของฉันเล็กน้อยที่นี่:

#!/bin/bash

# getopt example
# from: /programming/402377/using-getopts-in-bash-shell-script-to-get-long-and-short-command-line-options
HELP_TEXT=\
"   USAGE:\n
    Accepts - and -- flags, can specify options that require a value, and can be in any order. A double-hyphen (--) will stop processing options.\n\n

    Accepts the following forms:\n\n

    getopt-example.sh -a -b -c value-for-c some-arg\n
    getopt-example.sh -c value-for-c -a -b some-arg\n
    getopt-example.sh -abc some-arg\n
    getopt-example.sh --along --blong --clong value-for-c -a -b -c some-arg\n
    getopt-example.sh some-arg --clong value-for-c\n
    getopt-example.sh
"

aflag=false
bflag=false
cargument=""

# options may be followed by one colon to indicate they have a required argument
if ! options=$(getopt -o abc:h\? -l along,blong,help,clong: -- "$@")
then
    # something went wrong, getopt will put out an error message for us
    exit 1
fi

set -- $options

while [ $# -gt 0 ]
do
    case $1 in
    -a|--along) aflag=true ;;
    -b|--blong) bflag=true ;;
    # for options with required arguments, an additional shift is required
    -c|--clong) cargument="$2" ; shift;;
    -h|--help|-\?) echo -e $HELP_TEXT; exit;;
    (--) shift; break;;
    (-*) echo "$0: error - unrecognized option $1" 1>&2; exit 1;;
    (*) break;;
    esac
    shift
done

# to remove the single quotes around arguments, pipe the output into:
# | sed -e "s/^'\\|'$//g"  (just leading/trailing) or | sed -e "s/'//g"  (all)

echo aflag=${aflag}
echo bflag=${bflag}
echo cargument=${cargument}

while [ $# -gt 0 ]
do
    echo arg=$1
    shift

    if [[ $aflag == true ]]; then
        echo a is true
    fi

done

3

ที่นี่คุณสามารถค้นหาวิธีที่แตกต่างกันไม่กี่อย่างสำหรับการแยกวิเคราะห์ตัวเลือกที่ซับซ้อนใน bash http://mywiki.wooledge.org/ComplexOptionParsing

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

#!/bin/bash
# Uses bash extensions.  Not portable as written.

declare -A longoptspec
longoptspec=( [loglevel]=1 ) #use associative array to declare how many arguments a long option expects, in this case we declare that loglevel expects/has one argument, long options that aren't listed i n this way will have zero arguments by default
optspec=":h-:"
while getopts "$optspec" opt; do
while true; do
    case "${opt}" in
        -) #OPTARG is name-of-long-option or name-of-long-option=value
            if [[ "${OPTARG}" =~ .*=.* ]] #with this --key=value format only one argument is possible
            then
                opt=${OPTARG/=*/}
                OPTARG=${OPTARG#*=}
                ((OPTIND--))    
            else #with this --key value1 value2 format multiple arguments are possible
                opt="$OPTARG"
                OPTARG=(${@:OPTIND:$((longoptspec[$opt]))})
            fi
            ((OPTIND+=longoptspec[$opt]))
            continue #now that opt/OPTARG are set we can process them as if getopts would've given us long options
            ;;
        loglevel)
          loglevel=$OPTARG
            ;;
        h|help)
            echo "usage: $0 [--loglevel[=]<value>]" >&2
            exit 2
            ;;
    esac
break; done
done

# End of file

2

ฉันทำงานเกี่ยวกับเรื่องนี้มานานแล้วและสร้างห้องสมุดของตัวเองซึ่งคุณจะต้องใช้แหล่งข้อมูลในสคริปต์หลักของคุณ ดูที่libopt4shellและcd2mpcสำหรับตัวอย่าง หวังว่ามันจะช่วย!


2

โซลูชันที่ได้รับการปรับปรุง:

# translate long options to short
# Note: This enable long options but disable "--?*" in $OPTARG, or disable long options after  "--" in option fields.
for ((i=1;$#;i++)) ; do
    case "$1" in
        --)
            # [ ${args[$((i-1))]} == ... ] || EndOpt=1 ;;& # DIRTY: we still can handle some execptions...
            EndOpt=1 ;;&
        --version) ((EndOpt)) && args[$i]="$1"  || args[$i]="-V";;
        # default case : short option use the first char of the long option:
        --?*) ((EndOpt)) && args[$i]="$1"  || args[$i]="-${1:2:1}";;
        # pass through anything else:
        *) args[$i]="$1" ;;
    esac
    shift
done
# reset the translated args
set -- "${args[@]}"

function usage {
echo "Usage: $0 [options] files" >&2
    exit $1
}

# now we can process with getopt
while getopts ":hvVc:" opt; do
    case $opt in
        h)  usage ;;
        v)  VERBOSE=true ;;
        V)  echo $Version ; exit ;;
        c)  source $OPTARG ;;
        \?) echo "unrecognized option: -$opt" ; usage -1 ;;
        :)
        echo "option -$OPTARG requires an argument"
        usage -1
        ;;
    esac
done

shift $((OPTIND-1))
[[ "$1" == "--" ]] && shift

2

บางทีการใช้ ksh อาจจะง่ายกว่าสำหรับส่วน getopts หากต้องการตัวเลือกบรรทัดคำสั่งที่ยาวเนื่องจากสามารถทำได้ง่ายกว่า

# Working Getopts Long => KSH

#! /bin/ksh
# Getopts Long
USAGE="s(showconfig)"
USAGE+="c:(createdb)"
USAGE+="l:(createlistener)"
USAGE+="g:(generatescripts)"
USAGE+="r:(removedb)"
USAGE+="x:(removelistener)"
USAGE+="t:(createtemplate)"
USAGE+="h(help)"

while getopts "$USAGE" optchar ; do
    case $optchar in
    s)  echo "Displaying Configuration" ;;
        c)  echo "Creating Database $OPTARG" ;;
    l)  echo "Creating Listener LISTENER_$OPTARG" ;;
    g)  echo "Generating Scripts for Database $OPTARG" ;;
    r)  echo "Removing Database $OPTARG" ;;
    x)  echo "Removing Listener LISTENER_$OPTARG" ;;
    t)  echo "Creating Database Template" ;;
    h)  echo "Help" ;;
    esac
done

+1 - โปรดทราบว่าสิ่งนี้ จำกัด อยู่ที่ ksh93 - จากโครงการ AST แบบโอเพนซอร์ส (การวิจัย AT&T)
Henk Langeveld

2

ฉันต้องการบางสิ่งที่ไม่มีการพึ่งพาจากภายนอกด้วยการสนับสนุนการทุบตีที่เข้มงวด (-u) และฉันต้องการมันในการทำงานแม้กระทั่งเวอร์ชั่นทุบตีที่เก่ากว่า สิ่งนี้จัดการกับ params ประเภทต่างๆ:

  • บูลสั้น (-h)
  • ตัวเลือกสั้น ๆ (-i "image.jpg")
  • บูลยาว (- ช่วย)
  • ตัวเลือกเท่ากับ (--file = "filename.ext")
  • ตัวเลือกพื้นที่ (- ไฟล์ "filename.ext")
  • concatinated bools (-hvm)

เพียงแทรกสิ่งต่อไปนี้ที่ด้านบนสุดของสคริปต์ของคุณ:

# Check if a list of params contains a specific param
# usage: if _param_variant "h|?|help p|path f|file long-thing t|test-thing" "file" ; then ...
# the global variable $key is updated to the long notation (last entry in the pipe delineated list, if applicable)
_param_variant() {
  for param in $1 ; do
    local variants=${param//\|/ }
    for variant in $variants ; do
      if [[ "$variant" = "$2" ]] ; then
        # Update the key to match the long version
        local arr=(${param//\|/ })
        let last=${#arr[@]}-1
        key="${arr[$last]}"
        return 0
      fi
    done
  done
  return 1
}

# Get input parameters in short or long notation, with no dependencies beyond bash
# usage:
#     # First, set your defaults
#     param_help=false
#     param_path="."
#     param_file=false
#     param_image=false
#     param_image_lossy=true
#     # Define allowed parameters
#     allowed_params="h|?|help p|path f|file i|image image-lossy"
#     # Get parameters from the arguments provided
#     _get_params $*
#
# Parameters will be converted into safe variable names like:
#     param_help,
#     param_path,
#     param_file,
#     param_image,
#     param_image_lossy
#
# Parameters without a value like "-h" or "--help" will be treated as
# boolean, and will be set as param_help=true
#
# Parameters can accept values in the various typical ways:
#     -i "path/goes/here"
#     --image "path/goes/here"
#     --image="path/goes/here"
#     --image=path/goes/here
# These would all result in effectively the same thing:
#     param_image="path/goes/here"
#
# Concatinated short parameters (boolean) are also supported
#     -vhm is the same as -v -h -m
_get_params(){

  local param_pair
  local key
  local value
  local shift_count

  while : ; do
    # Ensure we have a valid param. Allows this to work even in -u mode.
    if [[ $# == 0 || -z $1 ]] ; then
      break
    fi

    # Split the argument if it contains "="
    param_pair=(${1//=/ })
    # Remove preceeding dashes
    key="${param_pair[0]#--}"

    # Check for concatinated boolean short parameters.
    local nodash="${key#-}"
    local breakout=false
    if [[ "$nodash" != "$key" && ${#nodash} -gt 1 ]]; then
      # Extrapolate multiple boolean keys in single dash notation. ie. "-vmh" should translate to: "-v -m -h"
      local short_param_count=${#nodash}
      let new_arg_count=$#+$short_param_count-1
      local new_args=""
      # $str_pos is the current position in the short param string $nodash
      for (( str_pos=0; str_pos<new_arg_count; str_pos++ )); do
        # The first character becomes the current key
        if [ $str_pos -eq 0 ] ; then
          key="${nodash:$str_pos:1}"
          breakout=true
        fi
        # $arg_pos is the current position in the constructed arguments list
        let arg_pos=$str_pos+1
        if [ $arg_pos -gt $short_param_count ] ; then
          # handle other arguments
          let orignal_arg_number=$arg_pos-$short_param_count+1
          local new_arg="${!orignal_arg_number}"
        else
          # break out our one argument into new ones
          local new_arg="-${nodash:$str_pos:1}"
        fi
        new_args="$new_args \"$new_arg\""
      done
      # remove the preceding space and set the new arguments
      eval set -- "${new_args# }"
    fi
    if ! $breakout ; then
      key="$nodash"
    fi

    # By default we expect to shift one argument at a time
    shift_count=1
    if [ "${#param_pair[@]}" -gt "1" ] ; then
      # This is a param with equals notation
      value="${param_pair[1]}"
    else
      # This is either a boolean param and there is no value,
      # or the value is the next command line argument
      # Assume the value is a boolean true, unless the next argument is found to be a value.
      value=true
      if [[ $# -gt 1 && -n "$2" ]]; then
        local nodash="${2#-}"
        if [ "$nodash" = "$2" ]; then
          # The next argument has NO preceding dash so it is a value
          value="$2"
          shift_count=2
        fi
      fi
    fi

    # Check that the param being passed is one of the allowed params
    if _param_variant "$allowed_params" "$key" ; then
      # --key-name will now become param_key_name
      eval param_${key//-/_}="$value"
    else
      printf 'WARNING: Unknown option (ignored): %s\n' "$1" >&2
    fi
    shift $shift_count
  done
}

และใช้มันอย่างนั้น:

# Assign defaults for parameters
param_help=false
param_path=$(pwd)
param_file=false
param_image=true
param_image_lossy=true
param_image_lossy_quality=85

# Define the params we will allow
allowed_params="h|?|help p|path f|file i|image image-lossy image-lossy-quality"

# Get the params from arguments provided
_get_params $*

1

เพื่อให้สามารถใช้งานข้ามแพลตฟอร์มได้และหลีกเลี่ยงการพึ่งพาโปรแกรมปฏิบัติการภายนอกฉันจึงย้ายรหัสจากภาษาอื่น

ฉันคิดว่ามันใช้งานง่ายมากนี่คือตัวอย่าง:

ArgParser::addArg "[h]elp"    false    "This list"
ArgParser::addArg "[q]uiet"   false    "Supress output"
ArgParser::addArg "[s]leep"   1        "Seconds to sleep"
ArgParser::addArg "v"         1        "Verbose mode"

ArgParser::parse "$@"

ArgParser::isset help && ArgParser::showArgs

ArgParser::isset "quiet" \
   && echo "Quiet!" \
   || echo "Noisy!"

local __sleep
ArgParser::tryAndGetArg sleep into __sleep \
   && echo "Sleep for $__sleep seconds" \
   || echo "No value passed for sleep"

# This way is often more convienient, but is a little slower
echo "Sleep set to: $( ArgParser::getArg sleep )"

BASH ที่ต้องการนั้นใช้เวลานานกว่านี้เล็กน้อย แต่ฉันต้องการหลีกเลี่ยงการพึ่งพาอาเรย์เชื่อมโยงของ BASH 4 นอกจากนี้คุณยังสามารถดาวน์โหลดได้โดยตรงจากhttp://nt4.com/bash/argparser.inc.sh

#!/usr/bin/env bash

# Updates to this script may be found at
# http://nt4.com/bash/argparser.inc.sh

# Example of runtime usage:
# mnc.sh --nc -q Caprica.S0*mkv *.avi *.mp3 --more-options here --host centos8.host.com

# Example of use in script (see bottom)
# Just include this file in yours, or use
# source argparser.inc.sh

unset EXPLODED
declare -a EXPLODED
function explode 
{
    local c=$# 
    (( c < 2 )) && 
    {
        echo function "$0" is missing parameters 
        return 1
    }

    local delimiter="$1"
    local string="$2"
    local limit=${3-99}

    local tmp_delim=$'\x07'
    local delin=${string//$delimiter/$tmp_delim}
    local oldifs="$IFS"

    IFS="$tmp_delim"
    EXPLODED=($delin)
    IFS="$oldifs"
}


# See: http://fvue.nl/wiki/Bash:_Passing_variables_by_reference
# Usage: local "$1" && upvar $1 "value(s)"
upvar() {
    if unset -v "$1"; then           # Unset & validate varname
        if (( $# == 2 )); then
            eval $1=\"\$2\"          # Return single value
        else
            eval $1=\(\"\${@:2}\"\)  # Return array
        fi
    fi
}

function decho
{
    :
}

function ArgParser::check
{
    __args=${#__argparser__arglist[@]}
    for (( i=0; i<__args; i++ ))
    do
        matched=0
        explode "|" "${__argparser__arglist[$i]}"
        if [ "${#1}" -eq 1 ]
        then
            if [ "${1}" == "${EXPLODED[0]}" ]
            then
                decho "Matched $1 with ${EXPLODED[0]}"
                matched=1

                break
            fi
        else
            if [ "${1}" == "${EXPLODED[1]}" ]
            then
                decho "Matched $1 with ${EXPLODED[1]}"
                matched=1

                break
            fi
        fi
    done
    (( matched == 0 )) && return 2
    # decho "Key $key has default argument of ${EXPLODED[3]}"
    if [ "${EXPLODED[3]}" == "false" ]
    then
        return 0
    else
        return 1
    fi
}

function ArgParser::set
{
    key=$3
    value="${1:-true}"
    declare -g __argpassed__$key="$value"
}

function ArgParser::parse
{

    unset __argparser__argv
    __argparser__argv=()
    # echo parsing: "$@"

    while [ -n "$1" ]
    do
        # echo "Processing $1"
        if [ "${1:0:2}" == '--' ]
        then
            key=${1:2}
            value=$2
        elif [ "${1:0:1}" == '-' ]
        then
            key=${1:1}               # Strip off leading -
            value=$2
        else
            decho "Not argument or option: '$1'" >& 2
            __argparser__argv+=( "$1" )
            shift
            continue
        fi
        # parameter=${tmp%%=*}     # Extract name.
        # value=${tmp##*=}         # Extract value.
        decho "Key: '$key', value: '$value'"
        # eval $parameter=$value
        ArgParser::check $key
        el=$?
        # echo "Check returned $el for $key"
        [ $el -eq  2 ] && decho "No match for option '$1'" >&2 # && __argparser__argv+=( "$1" )
        [ $el -eq  0 ] && decho "Matched option '${EXPLODED[2]}' with no arguments"        >&2 && ArgParser::set true "${EXPLODED[@]}"
        [ $el -eq  1 ] && decho "Matched option '${EXPLODED[2]}' with an argument of '$2'"   >&2 && ArgParser::set "$2" "${EXPLODED[@]}" && shift
        shift
    done
}

function ArgParser::isset
{
    declare -p "__argpassed__$1" > /dev/null 2>&1 && return 0
    return 1
}

function ArgParser::getArg
{
    # This one would be a bit silly, since we can only return non-integer arguments ineffeciently
    varname="__argpassed__$1"
    echo "${!varname}"
}

##
# usage: tryAndGetArg <argname> into <varname>
# returns: 0 on success, 1 on failure
function ArgParser::tryAndGetArg
{
    local __varname="__argpassed__$1"
    local __value="${!__varname}"
    test -z "$__value" && return 1
    local "$3" && upvar $3 "$__value"
    return 0
}

function ArgParser::__construct
{
    unset __argparser__arglist
    # declare -a __argparser__arglist
}

##
# @brief add command line argument
# @param 1 short and/or long, eg: [s]hort
# @param 2 default value
# @param 3 description
##
function ArgParser::addArg
{
    # check for short arg within long arg
    if [[ "$1" =~ \[(.)\] ]]
    then
        short=${BASH_REMATCH[1]}
        long=${1/\[$short\]/$short}
    else
        long=$1
    fi
    if [ "${#long}" -eq 1 ]
    then
        short=$long
        long=''
    fi
    decho short: "$short"
    decho long: "$long"
    __argparser__arglist+=("$short|$long|$1|$2|$3")
}

## 
# @brief show available command line arguments
##
function ArgParser::showArgs
{
    # declare -p | grep argparser
    printf "Usage: %s [OPTION...]\n\n" "$( basename "${BASH_SOURCE[0]}" )"
    printf "Defaults for the options are specified in brackets.\n\n";

    __args=${#__argparser__arglist[@]}
    for (( i=0; i<__args; i++ ))
    do
        local shortname=
        local fullname=
        local default=
        local description=
        local comma=

        explode "|" "${__argparser__arglist[$i]}"

        shortname="${EXPLODED[0]:+-${EXPLODED[0]}}" # String Substitution Guide: 
        fullname="${EXPLODED[1]:+--${EXPLODED[1]}}" # http://tldp.org/LDP/abs/html/parameter-substitution.html
        test -n "$shortname" \
            && test -n "$fullname" \
            && comma=","

        default="${EXPLODED[3]}"
        case $default in
            false )
                default=
                ;;
            "" )
                default=
                ;;
            * )
                default="[$default]"
        esac

        description="${EXPLODED[4]}"

        printf "  %2s%1s %-19s %s %s\n" "$shortname" "$comma" "$fullname" "$description" "$default"
    done
}

function ArgParser::test
{
    # Arguments with a default of 'false' do not take paramaters (note: default
    # values are not applied in this release)

    ArgParser::addArg "[h]elp"      false       "This list"
    ArgParser::addArg "[q]uiet" false       "Supress output"
    ArgParser::addArg "[s]leep" 1           "Seconds to sleep"
    ArgParser::addArg "v"           1           "Verbose mode"

    ArgParser::parse "$@"

    ArgParser::isset help && ArgParser::showArgs

    ArgParser::isset "quiet" \
        && echo "Quiet!" \
        || echo "Noisy!"

    local __sleep
    ArgParser::tryAndGetArg sleep into __sleep \
        && echo "Sleep for $__sleep seconds" \
        || echo "No value passed for sleep"

    # This way is often more convienient, but is a little slower
    echo "Sleep set to: $( ArgParser::getArg sleep )"

    echo "Remaining command line: ${__argparser__argv[@]}"

}

if [ "$( basename "$0" )" == "argparser.inc.sh" ]
then
    ArgParser::test "$@"
fi

1

หากตัวเลือกแบบยาวทั้งหมดของคุณมีตัวอักษรและการจับคู่ที่ไม่ซ้ำกันตัวอักษรตัวแรกจะเป็นตัวเลือกสั้น ๆ ตัวอย่างเช่น

./slamm --chaos 23 --plenty test -quiet

เป็นเช่นเดียวกับ

./slamm -c 23 -p test -q

คุณสามารถใช้สิ่งนี้ก่อนที่ getopts เพื่อเขียน $ args:

# change long options to short options

for arg; do 
    [[ "${arg:0:1}" == "-" ]] && delim="" || delim="\""
    if [ "${arg:0:2}" == "--" ]; 
       then args="${args} -${arg:2:1}" 
       else args="${args} ${delim}${arg}${delim}"
    fi
done

# reset the incoming args
eval set -- $args

# proceed as usual
while getopts ":b:la:h" OPTION; do
    .....

ขอบคุณสำหรับ mtvee สำหรับแรงบันดาลใจ ;-)


ฉันไม่ได้รับความสำคัญของการประเมินที่นี่
มิตร

1

หากนี่เป็นวิธีที่คุณต้องการเรียกใช้สคริปต์

myscript.sh --input1 "ABC" --input2 "PQR" --input2 "XYZ"

จากนั้นคุณสามารถทำตามวิธีที่ง่ายที่สุดนี้เพื่อให้บรรลุมันด้วยความช่วยเหลือของ getopt และ - longoptions

ลองนี้หวังว่าจะเป็นประโยชน์

# Read command line options
ARGUMENT_LIST=(
    "input1"
    "input2"
    "input3"
)



# read arguments
opts=$(getopt \
    --longoptions "$(printf "%s:," "${ARGUMENT_LIST[@]}")" \
    --name "$(basename "$0")" \
    --options "" \
    -- "$@"
)


echo $opts

eval set --$opts

while true; do
    case "$1" in
    --input1)  
        shift
        empId=$1
        ;;
    --input2)  
        shift
        fromDate=$1
        ;;
    --input3)  
        shift
        toDate=$1
        ;;
      --)
        shift
        break
        ;;
    esac
    shift
done

0

getopts "สามารถใช้งานได้" สำหรับการแยกวิเคราะห์ตัวเลือกยาวตราบใดที่คุณไม่คาดว่ามันจะมีอาร์กิวเมนต์ ...

นี่คือวิธี:

$ cat > longopt
while getopts 'e:-:' OPT; do
  case $OPT in
    e) echo echo: $OPTARG;;
    -) #long option
       case $OPTARG in
         long-option) echo long option;;
         *) echo long option: $OPTARG;;
       esac;;
  esac
done

$ bash longopt -e asd --long-option --long1 --long2 -e test
echo: asd
long option
long option: long1
long option: long2
echo: test

หากคุณพยายามใช้ OPTIND เพื่อรับพารามิเตอร์สำหรับตัวเลือกแบบยาว getopts จะถือว่าเป็นพารามิเตอร์ตัวเลือกแรกที่ไม่มีทางเลือกและจะหยุดการแยกวิเคราะห์พารามิเตอร์อื่น ๆ ในกรณีเช่นนี้คุณจะดีกว่าจัดการมันด้วยตนเองด้วยคำสั่งกรณีง่าย

สิ่งนี้จะทำงาน "เสมอ":

$ cat >longopt2
while (($#)); do
    OPT=$1
    shift
    case $OPT in
        --*) case ${OPT:2} in
            long1) echo long1 option;;
            complex) echo comples with argument $1; shift;;
        esac;;

        -*) case ${OPT:1} in
            a) echo short option a;;
            b) echo short option b with parameter $1; shift;;
        esac;;
    esac
done


$ bash longopt2 --complex abc -a --long -b test
comples with argument abc
short option a
short option b with parameter test

แม้ว่าจะไม่ยืดหยุ่นเท่ากับ getopts และคุณต้องทำข้อผิดพลาดในการตรวจสอบรหัสด้วยตัวคุณเองภายในเคส ...

แต่มันเป็นตัวเลือก


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

ใช่นี่คือ poc สำหรับชื่ออาร์กิวเมนต์แบบยาวโดยไม่มีข้อโต้แย้งเพื่อแยกความแตกต่างระหว่างสองสิ่งที่คุณต้องการตั้งค่าบางอย่างเช่น getops และเกี่ยวกับการเปลี่ยนคุณสามารถ "นำกลับ" พร้อมชุด ไม่ว่าในกรณีใดจะต้องสามารถกำหนดค่าได้หากคาดว่าจะมีพารามิเตอร์หรือไม่ คุณสามารถใช้เวทมนต์กับมันได้ แต่จากนั้นคุณจะบังคับให้ผู้ใช้ใช้ - เพื่อบอกว่าการหยุดเวทมนตร์และพารามิเตอร์ตำแหน่งเริ่มต้นขึ้น
estani

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

0

บิวด์อินgetoptsเท่านั้นแยกวิเคราะห์ตัวเลือกสั้น ๆ (ยกเว้นใน ksh93) แต่คุณยังสามารถเพิ่มสคริปต์ไม่กี่บรรทัดเพื่อให้ getopts จัดการกับตัวเลือกแบบยาว

นี่เป็นส่วนหนึ่งของรหัสที่พบในhttp://www.uxora.com/unix/shell-script/22-handle-long-options-with-getopts

  #== set short options ==#
SCRIPT_OPTS=':fbF:B:-:h'
  #== set long options associated with short one ==#
typeset -A ARRAY_OPTS
ARRAY_OPTS=(
    [foo]=f
    [bar]=b
    [foobar]=F
    [barfoo]=B
    [help]=h
    [man]=h
)

  #== parse options ==#
while getopts ${SCRIPT_OPTS} OPTION ; do
    #== translate long options to short ==#
    if [[ "x$OPTION" == "x-" ]]; then
        LONG_OPTION=$OPTARG
        LONG_OPTARG=$(echo $LONG_OPTION | grep "=" | cut -d'=' -f2)
        LONG_OPTIND=-1
        [[ "x$LONG_OPTARG" = "x" ]] && LONG_OPTIND=$OPTIND || LONG_OPTION=$(echo $OPTARG | cut -d'=' -f1)
        [[ $LONG_OPTIND -ne -1 ]] && eval LONG_OPTARG="\$$LONG_OPTIND"
        OPTION=${ARRAY_OPTS[$LONG_OPTION]}
        [[ "x$OPTION" = "x" ]] &&  OPTION="?" OPTARG="-$LONG_OPTION"

        if [[ $( echo "${SCRIPT_OPTS}" | grep -c "${OPTION}:" ) -eq 1 ]]; then
            if [[ "x${LONG_OPTARG}" = "x" ]] || [[ "${LONG_OPTARG}" = -* ]]; then 
                OPTION=":" OPTARG="-$LONG_OPTION"
            else
                OPTARG="$LONG_OPTARG";
                if [[ $LONG_OPTIND -ne -1 ]]; then
                    [[ $OPTIND -le $Optnum ]] && OPTIND=$(( $OPTIND+1 ))
                    shift $OPTIND
                    OPTIND=1
                fi
            fi
        fi
    fi

    #== options follow by another option instead of argument ==#
    if [[ "x${OPTION}" != "x:" ]] && [[ "x${OPTION}" != "x?" ]] && [[ "${OPTARG}" = -* ]]; then 
        OPTARG="$OPTION" OPTION=":"
    fi

    #== manage options ==#
    case "$OPTION" in
        f  ) foo=1 bar=0                    ;;
        b  ) foo=0 bar=1                    ;;
        B  ) barfoo=${OPTARG}               ;;
        F  ) foobar=1 && foobar_name=${OPTARG} ;;
        h ) usagefull && exit 0 ;;
        : ) echo "${SCRIPT_NAME}: -$OPTARG: option requires an argument" >&2 && usage >&2 && exit 99 ;;
        ? ) echo "${SCRIPT_NAME}: -$OPTARG: unknown option" >&2 && usage >&2 && exit 99 ;;
    esac
done
shift $((${OPTIND} - 1))

นี่คือการทดสอบ:

# Short options test
$ ./foobar_any_getopts.sh -bF "Hello world" -B 6 file1 file2
foo=0 bar=1
barfoo=6
foobar=1 foobar_name=Hello world
files=file1 file2

# Long and short options test
$ ./foobar_any_getopts.sh --bar -F Hello --barfoo 6 file1 file2
foo=0 bar=1
barfoo=6
foobar=1 foobar_name=Hello
files=file1 file2

ไม่อย่างนั้นใน Korn Shell ล่าสุด ksh93 getoptsสามารถแยกตัวเลือกแบบยาวตามธรรมชาติและแม้แต่แสดงหน้าคนเหมือนกัน (ดูhttp://www.uxora.com/unix/shell-script/20-getopts-with-man-page-and-long-options )


0

Th ในตัว OS X (BSD) getopt ไม่สนับสนุนตัวเลือกนาน แต่รุ่น GNU brew install gnu-getoptไม่: จากนั้นบางสิ่งที่คล้ายกับ: cp /usr/local/Cellar/gnu-getopt/1.1.6/bin/getopt /usr/local/bin/gnu-getopt.


0

EasyOptionsจัดการกับตัวเลือกระยะสั้นและระยะยาว:

## Options:
##   --verbose, -v   Verbose mode
##   --logfile=NAME  Log filename

source easyoptions || exit

if test -n "${verbose}"; then
    echo "log file: ${logfile}"
    echo "arguments: ${arguments[@]}"
fi
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.