ฉันจะแยกอาร์กิวเมนต์บรรทัดคำสั่งใน Bash ได้อย่างไร


1921

บอกฉันว่าฉันมีสคริปต์ที่ถูกเรียกด้วยบรรทัดนี้:

./myscript -vfd ./foo/bar/someFile -o /fizz/someOtherFile

หรืออันนี้:

./myscript -v -f -d -o /fizz/someOtherFile ./foo/bar/someFile 

เป็นวิธีที่ได้รับการยอมรับของการแยกเช่นนี้ว่าในแต่ละกรณี (หรือการรวมกันของทั้งสองบางคน) $v, $fและ $dทั้งหมดจะถูกตั้งค่าtrueและ$outFileจะเท่ากับ/fizz/someOtherFile?


1
สำหรับ zsh ผู้ใช้มีในตัวเรียกว่า zparseopts ที่ดีซึ่งสามารถทำได้: zparseopts -D -E -M -- d=debug -debug=dและมีทั้ง-dและ--debugใน$debugอาร์เรย์echo $+debug[1]จะกลับ 0 หรือ 1 ถ้าหนึ่งในผู้ที่ถูกนำมาใช้ Ref: zsh.org/mla/users/2011/msg00350.html
dezza

1
กวดวิชาที่ดีจริงๆ: linuxcommand.org/lc3_wss0120.php ฉันชอบตัวอย่าง "ตัวเลือกบรรทัดคำสั่ง" เป็นพิเศษ
Gabriel Staples

คำตอบ:


2675

วิธีที่ # 1: การใช้ bash โดยไม่มี getopt [s]

วิธีทั่วไปสองวิธีในการส่งผ่านข้อโต้แย้งคู่คีย์ - ค่า - คือ:

Bash Space-Separated (เช่น, --option argument) (ไม่มี getopt [s])

การใช้ demo-space-separated.sh -e conf -s /etc -l /usr/lib /etc/hosts

cat >/tmp/demo-space-separated.sh <<'EOF'
#!/bin/bash

POSITIONAL=()
while [[ $# -gt 0 ]]
do
key="$1"

case $key in
    -e|--extension)
    EXTENSION="$2"
    shift # past argument
    shift # past value
    ;;
    -s|--searchpath)
    SEARCHPATH="$2"
    shift # past argument
    shift # past value
    ;;
    -l|--lib)
    LIBPATH="$2"
    shift # past argument
    shift # past value
    ;;
    --default)
    DEFAULT=YES
    shift # past argument
    ;;
    *)    # unknown option
    POSITIONAL+=("$1") # save it in an array for later
    shift # past argument
    ;;
esac
done
set -- "${POSITIONAL[@]}" # restore positional parameters

echo "FILE EXTENSION  = ${EXTENSION}"
echo "SEARCH PATH     = ${SEARCHPATH}"
echo "LIBRARY PATH    = ${LIBPATH}"
echo "DEFAULT         = ${DEFAULT}"
echo "Number files in SEARCH PATH with EXTENSION:" $(ls -1 "${SEARCHPATH}"/*."${EXTENSION}" | wc -l)
if [[ -n $1 ]]; then
    echo "Last line of file specified as non-opt/last argument:"
    tail -1 "$1"
fi
EOF

chmod +x /tmp/demo-space-separated.sh

/tmp/demo-space-separated.sh -e conf -s /etc -l /usr/lib /etc/hosts

เอาต์พุตจากการคัดลอกวางบล็อกด้านบน:

FILE EXTENSION  = conf
SEARCH PATH     = /etc
LIBRARY PATH    = /usr/lib
DEFAULT         =
Number files in SEARCH PATH with EXTENSION: 14
Last line of file specified as non-opt/last argument:
#93.184.216.34    example.com

Bash เท่ากับ - แยกออก (เช่น, --option=argument) (ไม่มี getopt [s])

การใช้ demo-equals-separated.sh -e=conf -s=/etc -l=/usr/lib /etc/hosts

cat >/tmp/demo-equals-separated.sh <<'EOF'
#!/bin/bash

for i in "$@"
do
case $i in
    -e=*|--extension=*)
    EXTENSION="${i#*=}"
    shift # past argument=value
    ;;
    -s=*|--searchpath=*)
    SEARCHPATH="${i#*=}"
    shift # past argument=value
    ;;
    -l=*|--lib=*)
    LIBPATH="${i#*=}"
    shift # past argument=value
    ;;
    --default)
    DEFAULT=YES
    shift # past argument with no value
    ;;
    *)
          # unknown option
    ;;
esac
done
echo "FILE EXTENSION  = ${EXTENSION}"
echo "SEARCH PATH     = ${SEARCHPATH}"
echo "LIBRARY PATH    = ${LIBPATH}"
echo "DEFAULT         = ${DEFAULT}"
echo "Number files in SEARCH PATH with EXTENSION:" $(ls -1 "${SEARCHPATH}"/*."${EXTENSION}" | wc -l)
if [[ -n $1 ]]; then
    echo "Last line of file specified as non-opt/last argument:"
    tail -1 $1
fi
EOF

chmod +x /tmp/demo-equals-separated.sh

/tmp/demo-equals-separated.sh -e=conf -s=/etc -l=/usr/lib /etc/hosts

เอาต์พุตจากการคัดลอกวางบล็อกด้านบน:

FILE EXTENSION  = conf
SEARCH PATH     = /etc
LIBRARY PATH    = /usr/lib
DEFAULT         =
Number files in SEARCH PATH with EXTENSION: 14
Last line of file specified as non-opt/last argument:
#93.184.216.34    example.com

เพื่อให้เข้าใจการ${i#*=}ค้นหา "กำจัดซับสตริง" ในคู่มือนี้ได้ดีขึ้น มันเทียบเท่ากับการใช้งาน`sed 's/[^=]*=//' <<< "$i"`ที่เรียก subprocess ที่ไม่จำเป็นหรือ`echo "$i" | sed 's/[^=]*=//'`เรียกsubprocesses ที่ไม่ต้องการสองตัว

วิธีที่ # 2: การใช้ bash กับ getopt [s]

จาก: http://mywiki.wooledge.org/BashFAQ/035#getopts

ข้อ จำกัด getopt (1) (รุ่นเก่ากว่าค่อนข้างใกล้เคียงgetopt):

  • ไม่สามารถจัดการกับอาร์กิวเมนต์ที่เป็นสตริงว่าง
  • ไม่สามารถจัดการกับข้อโต้แย้งด้วยช่องว่างที่ฝังตัว

เมื่อเร็ว ๆ นี้getoptรุ่นที่ไม่ได้มีข้อ จำกัด เหล่านี้

นอกจากนี้ข้อเสนอ POSIX เชลล์ (และอื่น ๆ ) getoptsซึ่งไม่มีข้อ จำกัด เหล่านี้ ฉันได้รวมgetoptsตัวอย่างง่ายๆ

การใช้ demo-getopts.sh -vf /etc/hosts foo bar

cat >/tmp/demo-getopts.sh <<'EOF'
#!/bin/sh

# A POSIX variable
OPTIND=1         # Reset in case getopts has been used previously in the shell.

# Initialize our own variables:
output_file=""
verbose=0

while getopts "h?vf:" opt; do
    case "$opt" in
    h|\?)
        show_help
        exit 0
        ;;
    v)  verbose=1
        ;;
    f)  output_file=$OPTARG
        ;;
    esac
done

shift $((OPTIND-1))

[ "${1:-}" = "--" ] && shift

echo "verbose=$verbose, output_file='$output_file', Leftovers: $@"
EOF

chmod +x /tmp/demo-getopts.sh

/tmp/demo-getopts.sh -vf /etc/hosts foo bar

เอาต์พุตจากการคัดลอกวางบล็อกด้านบน:

verbose=1, output_file='/etc/hosts', Leftovers: foo bar

ข้อดีของgetoptsคือ:

  1. มันเป็นแบบพกพามากขึ้นและจะทำงานในเปลือกหอยอื่น ๆ dashเช่น
  2. มันสามารถจัดการตัวเลือกเดียวหลายตัวเช่นเดียวกับ-vf filenameใน Unix โดยทั่วไปโดยอัตโนมัติ

ข้อเสียของgetoptsมันคือมันสามารถจัดการตัวเลือกสั้น ๆ ( -hไม่ใช่--help) โดยไม่มีรหัสเพิ่มเติม

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


44
นี่เป็นเรื่องจริงหรือ ตามWikipediaมี GNU ฉบับปรับปรุงที่ใหม่กว่าgetoptซึ่งรวมถึงการทำงานทั้งหมดgetoptsแล้วบางส่วน man getoptบน Ubuntu 13.04 outputs getopt - parse command options (enhanced)เป็นชื่อดังนั้นฉันจึงคิดว่ารุ่นที่ปรับปรุงนี้เป็นมาตรฐานในขณะนี้
Livven

47
บางสิ่งนั้นเป็นวิธีที่แน่นอนในระบบของคุณเป็นหลักฐานที่อ่อนแอมากในการตั้งสมมติฐานว่า "เป็นมาตรฐาน"
szablica

13
@Livven ที่getoptไม่ได้เป็นยูทิลิตี้ GNU util-linuxเป็นส่วนหนึ่งของมันของ
Stephane Chazelas

4
หากคุณใช้-gt 0ให้ลบของคุณshiftหลังesac, เติมทั้งหมดshiftด้วย 1 และเพิ่มกรณีนี้: *) break;;คุณสามารถจัดการอาร์กิวเมนต์ที่ไม่ใช่ตัวเลือกได้ เช่นpastebin.com/6DJ57HTc
Nicolas Lacombe

2
–defaultคุณทำไม่ได้สะท้อน ในตัวอย่างแรกฉันสังเกตเห็นว่าหาก–defaultเป็นอาร์กิวเมนต์สุดท้ายจะไม่ถูกประมวลผล (ถือว่าไม่ใช่การเลือก) ยกเว้นว่าwhile [[ $# -gt 1 ]]ตั้งเป็น while [[ $# -gt 0 ]]
kolydart

562

ไม่มีคำตอบกล่าวgetopt ที่เพิ่มขึ้น และคำตอบที่ได้รับการโหวตมากที่สุดคือทำให้เข้าใจผิด:มันไม่สนใจ-⁠vfdตัวเลือกแบบสั้น ๆ สไตล์ (ร้องขอโดย OP) หรือตัวเลือกหลังจากข้อโต้แย้งตำแหน่ง (หรือร้องขอโดย OP); และจะละเว้นการแยกวิเคราะห์ข้อผิดพลาด แทน:

  • ใช้ที่เพิ่มขึ้นgetoptจาก util ลินุกซ์หรือ GNU glibc 1
  • มันทำงานร่วมกับgetopt_long()ฟังก์ชั่น C ของ GNU glibc
  • มีคุณสมบัติการแยกความแตกต่างที่มีประโยชน์ทั้งหมด (คุณสมบัติอื่น ๆ ไม่มี:)
    • จัดการช่องว่างการอ้างอิงตัวอักษรและเลขฐานสองในอาร์กิวเมนต์2 (ไม่ได้รับการปรับปรุงgetoptไม่สามารถทำได้)
    • มันสามารถจัดการกับตัวเลือกได้ในตอนท้าย: script.sh -o outFile file1 file2 -v( getoptsไม่ได้ทำสิ่งนี้)
    • อนุญาตให้=ใช้ตัวเลือกแบบยาว: script.sh --outfile=fileOut --infile fileIn(การอนุญาตให้ทั้งคู่ยาวถ้าแยกวิเคราะห์ตัวเอง)
    • อนุญาตให้รวมตัวเลือกสั้น ๆ เช่น-vfd(ทำงานจริงถ้าแยกวิเคราะห์ด้วยตนเอง)
    • ช่วยให้ตัวเลือกสัมผัสข้อโต้แย้งเช่น-oOutfileหรือ-vfdoOutfile
  • เก่ามากแล้ว3ซึ่งไม่มีระบบ GNU ที่ขาดหายไป (เช่น Linux ใด ๆ มี)
  • คุณสามารถทดสอบการมีอยู่ของมันด้วย: getopt --test→ค่าส่งคืน 4
  • อื่น ๆgetoptหรือ shell-builtin getoptsมีการใช้งาน จำกัด

การโทรดังต่อไปนี้

myscript -vfd ./foo/bar/someFile -o /fizz/someOtherFile
myscript -v -f -d -o/fizz/someOtherFile -- ./foo/bar/someFile
myscript --verbose --force --debug ./foo/bar/someFile -o/fizz/someOtherFile
myscript --output=/fizz/someOtherFile ./foo/bar/someFile -vfd
myscript ./foo/bar/someFile -df -v --output /fizz/someOtherFile

ผลตอบแทนทั้งหมด

verbose: y, force: y, debug: y, in: ./foo/bar/someFile, out: /fizz/someOtherFile

ด้วยดังต่อไปนี้ myscript

#!/bin/bash
# saner programming env: these switches turn some bugs into errors
set -o errexit -o pipefail -o noclobber -o nounset

# -allow a command to fail with !’s side effect on errexit
# -use return value from ${PIPESTATUS[0]}, because ! hosed $?
! getopt --test > /dev/null 
if [[ ${PIPESTATUS[0]} -ne 4 ]]; then
    echo 'I’m sorry, `getopt --test` failed in this environment.'
    exit 1
fi

OPTIONS=dfo:v
LONGOPTS=debug,force,output:,verbose

# -regarding ! and PIPESTATUS see above
# -temporarily store output to be able to check for errors
# -activate quoting/enhanced mode (e.g. by writing out “--options”)
# -pass arguments only via   -- "$@"   to separate them correctly
! PARSED=$(getopt --options=$OPTIONS --longoptions=$LONGOPTS --name "$0" -- "$@")
if [[ ${PIPESTATUS[0]} -ne 0 ]]; then
    # e.g. return value is 1
    #  then getopt has complained about wrong arguments to stdout
    exit 2
fi
# read getopt’s output this way to handle the quoting right:
eval set -- "$PARSED"

d=n f=n v=n outFile=-
# now enjoy the options in order and nicely split until we see --
while true; do
    case "$1" in
        -d|--debug)
            d=y
            shift
            ;;
        -f|--force)
            f=y
            shift
            ;;
        -v|--verbose)
            v=y
            shift
            ;;
        -o|--output)
            outFile="$2"
            shift 2
            ;;
        --)
            shift
            break
            ;;
        *)
            echo "Programming error"
            exit 3
            ;;
    esac
done

# handle non-option arguments
if [[ $# -ne 1 ]]; then
    echo "$0: A single input file is required."
    exit 4
fi

echo "verbose: $v, force: $f, debug: $d, in: $1, out: $outFile"

1 getopt ที่ปรับปรุงแล้วมีอยู่ใน "ระบบทุบตี" ส่วนใหญ่รวมถึง Cygwin; บน OS X ลองชงติดตั้ง gnu-getoptหรือsudo port install getopt
2exec()อนุสัญญาPOSIXไม่มีวิธีที่เชื่อถือได้ในการผ่านไบนารี NULL ในอาร์กิวเมนต์บรรทัดคำสั่ง จำนวนไบต์เหล่านั้นสิ้นสุดก่อนกำหนดอาร์กิวเมนต์
3รุ่นแรกที่วางจำหน่ายในปี 1997 หรือก่อนหน้า (ฉันติดตามเฉพาะตอนนี้จนถึงปี 1997)


4
ขอบคุณสำหรับสิ่งนี้. เพิ่งได้รับการยืนยันจากตารางคุณลักษณะที่en.wikipedia.org/wiki/Getoptsหากคุณต้องการการสนับสนุนสำหรับตัวเลือกที่ยาวนานและคุณไม่ได้อยู่บน Solaris getoptเป็นหนทางที่จะไป
johncip

4
ฉันเชื่อว่าข้อแม้เดียวที่มีgetoptคือมันไม่สามารถใช้งานได้อย่างสะดวกในสคริปต์ตัวคลุมซึ่งบางตัวอาจมีตัวเลือกไม่กี่ตัวสำหรับสคริปต์ตัวตัดแล้วส่งผ่านตัวเลือกที่ไม่ใช้ตัวห่อหุ้มสคริปต์ สมมติว่าผมมีgrepเสื้อคลุมที่เรียกว่าmygrepและผมมีตัวเลือก--fooที่เฉพาะเจาะจงเพื่อmygrepแล้วฉันไม่สามารถทำmygrep --foo -A 2และมี-A 2การส่งผ่านโดยอัตโนมัติgrep; ผมต้องการmygrep --foo -- -A 2ที่จะทำ นี่คือการดำเนินการของฉันบนโซลูชันของคุณ
Kaushal Modi

2
@ บ๊อบพอลคำสั่งของคุณเกี่ยวกับ util-linux นั้นผิดและทำให้เข้าใจผิดเช่นกัน: แพคเกจถูกทำเครื่องหมายว่า "จำเป็น" บน Ubuntu / Debian เช่นนั้นจะติดตั้งอยู่เสมอ - distros ใดที่คุณกำลังพูดถึง
Robert Siemer

3
หมายเหตุสิ่งนี้ใช้ไม่ได้กับ Mac อย่างน้อยจนถึงปัจจุบัน 10.14.3 getopt ที่จัดส่งคือ BSD getopt ตั้งแต่ปี 1999 ...
jjj

2
@transang Boolean การปฏิเสธค่าส่งคืน และผลข้างเคียงของมัน: อนุญาตให้คำสั่งล้มเหลว (มิฉะนั้น errexit จะยกเลิกโปรแกรมเมื่อเกิดข้อผิดพลาด) - ความคิดเห็นในสคริปต์บอกคุณมากขึ้น มิฉะนั้น:man bash
Robert Siemer

144

วิธีรวบรัดมากขึ้น

script.sh

#!/bin/bash

while [[ "$#" -gt 0 ]]; do
    case $1 in
        -d|--deploy) deploy="$2"; shift ;;
        -u|--uglify) uglify=1 ;;
        *) echo "Unknown parameter passed: $1"; exit 1 ;;
    esac
    shift
done

echo "Should deploy? $deploy"
echo "Should uglify? $uglify"

การใช้งาน:

./script.sh -d dev -u

# OR:

./script.sh --deploy dev --uglify

3
นี่คือสิ่งที่ฉันทำ ต้องถ้าผมต้องการที่จะสนับสนุนสิ้นสุดบรรทัดที่มีธงบูลีนwhile [[ "$#" > 1 ]] ./script.sh --debug dev --uglify fast --verboseตัวอย่าง: gist.github.com/hfossli/4368aa5a577742c3c9f9266ed214aa58
hfossli

12
ว้าว! เรียบง่ายและสะอาด! นี่คือวิธีที่ฉันใช้: gist.github.com/hfossli/4368aa5a577742c3c9f9266ed214aa58
hfossli

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

คำเตือน: สิ่งนี้จะยอมรับข้อโต้แย้งที่ซ้ำกันอาร์กิวเมนต์ล่าสุดมีผล เช่นจะส่งผลให้./script.sh -d dev -d prod deploy == 'prod'ฉันใช้มันต่อไป: P :): +1:
yair

ฉันใช้มัน (ขอบคุณ!) แต่ทราบว่ามันช่วยให้ค่าอาร์กิวเมนต์ที่ว่างเปล่าเช่น./script.sh -dจะไม่สร้างข้อผิดพลาด แต่เพิ่งตั้ง$deployเป็นสตริงที่ว่างเปล่า
EM0

137

จาก: digitalpeer.comพร้อมการแก้ไขเล็กน้อย

การใช้ myscript.sh -p=my_prefix -s=dirname -l=libname

#!/bin/bash
for i in "$@"
do
case $i in
    -p=*|--prefix=*)
    PREFIX="${i#*=}"

    ;;
    -s=*|--searchpath=*)
    SEARCHPATH="${i#*=}"
    ;;
    -l=*|--lib=*)
    DIR="${i#*=}"
    ;;
    --default)
    DEFAULT=YES
    ;;
    *)
            # unknown option
    ;;
esac
done
echo PREFIX = ${PREFIX}
echo SEARCH PATH = ${SEARCHPATH}
echo DIRS = ${DIR}
echo DEFAULT = ${DEFAULT}

เพื่อให้เข้าใจการ${i#*=}ค้นหา "กำจัดซับสตริง" ในคู่มือนี้ได้ดีขึ้น มันเทียบเท่ากับการใช้งาน`sed 's/[^=]*=//' <<< "$i"`ที่เรียก subprocess ที่ไม่จำเป็นหรือ`echo "$i" | sed 's/[^=]*=//'`เรียกsubprocesses ที่ไม่ต้องการสองตัว


4
เรียบร้อย! mount -t tempfs ...แต่นี้จะไม่ทำงานสำหรับข้อโต้แย้งพื้นที่แยกàลา หนึ่งอาจจะสามารถแก้ไขได้ผ่านบางสิ่งบางอย่างเช่นwhile [ $# -ge 1 ]; do param=$1; shift; case $param in; -p) prefix=$1; shift;;อื่น ๆ
Tobias Kienzler

3
วิธีนี้ไม่สามารถจัดการกับ-vfdตัวเลือกสั้น ๆ ที่ผสมผสานสไตล์ได้
Robert Siemer

105

getopt()/ getopts()เป็นตัวเลือกที่ดี ขโมยมาจากที่นี่ :

การใช้งานที่ง่ายของ "getopt" จะปรากฏในมินิสคริปต์นี้:

#!/bin/bash
echo "Before getopt"
for i
do
  echo $i
done
args=`getopt abc:d $*`
set -- $args
echo "After getopt"
for i
do
  echo "-->$i"
done

สิ่งที่เราพูดไปคือ -a, -b, -c หรือ -d ใด ๆ ที่จะได้รับอนุญาต แต่ -c นั้นจะตามมาด้วยการโต้แย้ง ("c:" กล่าวว่า)

หากเราเรียกสิ่งนี้ว่า "g" และลองทำดู:

bash-2.05a$ ./g -abc foo
Before getopt
-abc
foo
After getopt
-->-a
-->-b
-->-c
-->foo
-->--

เราเริ่มต้นด้วยการโต้แย้งสองครั้งและ "getopt" แยกตัวเลือกออกและทำให้แต่ละข้อขัดแย้งกัน นอกจากนี้ยังเพิ่ม "-"


4
การใช้งาน$*ถูกใช้งานgetoptไม่ได้ (มันเป็นการขัดแย้งกับช่องว่าง) ดูคำตอบของฉันสำหรับการใช้งานที่เหมาะสม
Robert Siemer

ทำไมคุณต้องการทำให้ซับซ้อนขึ้น
SDsolar

@ Matt J ส่วนแรกของสคริปต์ (สำหรับ i) จะสามารถจัดการกับอาร์กิวเมนต์ที่มีช่องว่างหากคุณใช้ "$ i" แทน $ i getopts ดูเหมือนจะไม่สามารถจัดการกับข้อโต้แย้งด้วยช่องว่าง อะไรคือข้อดีของการใช้ getopt บน for for loop
thebunnyrules

99

มีความเสี่ยงที่จะเพิ่มอีกตัวอย่างหนึ่งที่จะเพิกเฉยต่อไปนี่คือแบบแผนของฉัน

  • จับ-n argและ--name=arg
  • อนุญาตให้มีการขัดแย้งในตอนท้าย
  • แสดงข้อผิดพลาดที่มีเหตุผลหากมีสิ่งใดสะกดผิด
  • เข้ากันได้ไม่ได้ใช้ bashisms
  • สามารถอ่านได้ไม่ต้องการการดูแลรักษาสถานะในลูป

หวังว่ามันจะเป็นประโยชน์กับใครบางคน

while [ "$#" -gt 0 ]; do
  case "$1" in
    -n) name="$2"; shift 2;;
    -p) pidfile="$2"; shift 2;;
    -l) logfile="$2"; shift 2;;

    --name=*) name="${1#*=}"; shift 1;;
    --pidfile=*) pidfile="${1#*=}"; shift 1;;
    --logfile=*) logfile="${1#*=}"; shift 1;;
    --name|--pidfile|--logfile) echo "$1 requires an argument" >&2; exit 1;;

    -*) echo "unknown option: $1" >&2; exit 1;;
    *) handle_argument "$1"; shift 1;;
  esac
done

4
ขออภัยในความล่าช้า. ในสคริปต์ของฉันฟังก์ชัน handle_argument ได้รับอาร์กิวเมนต์ที่ไม่ใช่ตัวเลือกทั้งหมด คุณสามารถเปลี่ยนสายได้ว่ามีสิ่งที่คุณต้องการอาจจะ*) die "unrecognized argument: $1"หรือเก็บ args *) args+="$1"; shift 1;;ลงในตัวแปร
bronson

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

2
ดีรหัสรวบรัด แต่ใช้ -n และไม่มีอื่น ๆ หาเรื่องทำให้เกิดวง จำกัด เนื่องจากข้อผิดพลาดในshift 2การออกครั้งที่สองแทนshift shift 2แนะนำการแก้ไข
lauksas

42

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

#!/usr/bin/env bash

# NOTICE: Uncomment if your script depends on bashisms.
#if [ -z "$BASH_VERSION" ]; then bash $0 $@ ; exit $? ; fi

echo "Before"
for i ; do echo - $i ; done


# Code template for parsing command line parameters using only portable shell
# code, while handling both long and short params, handling '-f file' and
# '-f=file' style param data and also capturing non-parameters to be inserted
# back into the shell positional parameters.

while [ -n "$1" ]; do
        # Copy so we can modify it (can't modify $1)
        OPT="$1"
        # Detect argument termination
        if [ x"$OPT" = x"--" ]; then
                shift
                for OPT ; do
                        REMAINS="$REMAINS \"$OPT\""
                done
                break
        fi
        # Parse current opt
        while [ x"$OPT" != x"-" ] ; do
                case "$OPT" in
                        # Handle --flag=value opts like this
                        -c=* | --config=* )
                                CONFIGFILE="${OPT#*=}"
                                shift
                                ;;
                        # and --flag value opts like this
                        -c* | --config )
                                CONFIGFILE="$2"
                                shift
                                ;;
                        -f* | --force )
                                FORCE=true
                                ;;
                        -r* | --retry )
                                RETRY=true
                                ;;
                        # Anything unknown is recorded for later
                        * )
                                REMAINS="$REMAINS \"$OPT\""
                                break
                                ;;
                esac
                # Check for multiple short options
                # NOTICE: be sure to update this pattern to match valid options
                NEXTOPT="${OPT#-[cfr]}" # try removing single short opt
                if [ x"$OPT" != x"$NEXTOPT" ] ; then
                        OPT="-$NEXTOPT"  # multiple short opts, keep going
                else
                        break  # long form, exit inner loop
                fi
        done
        # Done with that param. move to next
        shift
done
# Set the non-parameters back into the positional parameters ($1 $2 ..)
eval set -- $REMAINS


echo -e "After: \n configfile='$CONFIGFILE' \n force='$FORCE' \n retry='$RETRY' \n remains='$REMAINS'"
for i ; do echo - $i ; done

-c1รหัสนี้ไม่สามารถจัดการตัวเลือกที่มีการขัดแย้งเช่นนี้ และการใช้=ที่จะแยกตัวเลือกระยะสั้น ๆ จากขัดแย้งของพวกเขาเป็นเรื่องปกติ ...
โรเบิร์ต Siemer

2
ฉันพบปัญหาสองประการกับโค้ดอันมีประโยชน์นี้: 1) "shift" ในกรณีของ "-c = foo" จบลงด้วยการกินพารามิเตอร์ถัดไป และ 2) 'c' ไม่ควรรวมอยู่ในรูปแบบ "[cfr]" สำหรับตัวเลือกสั้น ๆ ที่ใช้ร่วมกันได้
sfnd

36

ฉันได้พบปัญหาในการเขียนการแยกวิเคราะห์แบบพกพาในสคริปต์เพื่อทำลายที่ฉันได้เขียนArgbash - ตัวสร้างรหัส FOSS ที่สามารถสร้างรหัสการแยกวิเคราะห์อาร์กิวเมนต์สำหรับสคริปต์ของคุณรวมทั้งมีคุณสมบัติที่ดีบางอย่าง:

https://argbash.io


ขอบคุณสำหรับการเขียน argbash ฉันใช้มันและพบว่าใช้งานได้ดี ฉันส่วนใหญ่ไปหา argbash เพราะมันเป็นตัวสร้างรหัสที่รองรับ bash 3.x รุ่นเก่าที่พบใน OS X 10.11 El Capitan ข้อเสียเพียงอย่างเดียวคือวิธีการสร้างรหัสหมายถึงรหัสค่อนข้างมากในสคริปต์หลักของคุณเมื่อเทียบกับการเรียกโมดูล
RichVel

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

ดีแล้วที่รู้. ตัวอย่างนั้นน่าสนใจ แต่ก็ยังไม่ชัดเจน - คุณอาจเปลี่ยนชื่อของสคริปต์ที่สร้างขึ้นเป็น 'parse_lib.sh' หรือคล้ายกันและแสดงตำแหน่งที่สคริปต์หลักเรียกใช้ (เช่นในส่วนของสคริปต์การตัดคำซึ่งซับซ้อนกว่ากรณีใช้งาน)
RichVel

ปัญหาได้รับการแก้ไขในเวอร์ชันล่าสุดของ argbash: มีการปรับปรุงเอกสารมีการแนะนำสคริปต์เริ่มต้นอย่างรวดเร็วของ argbash และคุณยังสามารถใช้ argbash ออนไลน์ได้ที่argbash.io/generate
bubla

29

คำตอบของฉันขึ้นอยู่กับคำตอบของ Bruno Bronoskyเป็นส่วนใหญ่แต่ฉันได้บดขยี้การใช้งานทุบตีบริสุทธิ์ของเขาทั้งสองให้เป็นแบบที่ฉันใช้บ่อยๆ

# As long as there is at least one more argument, keep looping
while [[ $# -gt 0 ]]; do
    key="$1"
    case "$key" in
        # This is a flag type option. Will catch either -f or --foo
        -f|--foo)
        FOO=1
        ;;
        # Also a flag type option. Will catch either -b or --bar
        -b|--bar)
        BAR=1
        ;;
        # This is an arg value type option. Will catch -o value or --output-file value
        -o|--output-file)
        shift # past the key and to the value
        OUTPUTFILE="$1"
        ;;
        # This is an arg=value type option. Will catch -o=value or --output-file=value
        -o=*|--output-file=*)
        # No need to shift here since the value is part of the same string
        OUTPUTFILE="${key#*=}"
        ;;
        *)
        # Do whatever you want with extra options
        echo "Unknown option '$key'"
        ;;
    esac
    # Shift after checking all the cases to get the next option
    shift
done

สิ่งนี้ช่วยให้คุณมีตัวเลือก / ค่าที่คั่นด้วยช่องว่างทั้งสองรวมถึงค่าที่กำหนดเท่ากัน

ดังนั้นคุณสามารถเรียกใช้สคริปต์ของคุณโดยใช้:

./myscript --foo -b -o /fizz/file.txt

เช่นเดียวกับ:

./myscript -f --bar -o=/fizz/file.txt

และทั้งคู่ควรมีผลลัพธ์ที่เหมือนกัน

ข้อดี:

  • อนุญาตสำหรับทั้ง -arg = value และ -arg value

  • ทำงานกับชื่อ arg ใด ๆ ที่คุณสามารถใช้ในการทุบตี

    • ความหมาย -a หรือ -arg หรือ --arg หรือ -arg หรืออะไรก็ตาม
  • ทุบตีบริสุทธิ์ ไม่จำเป็นต้องเรียนรู้ / ใช้ getopt หรือ getopts

ข้อเสีย:

  • ไม่สามารถรวม args

    • ไม่มีความหมาย -abc คุณต้องทำ -a -b -c

เหล่านี้เป็นข้อดี / ข้อเสียเดียวที่ฉันสามารถนึกได้จากส่วนหัวของฉัน


15

ฉันคิดว่าอันนี้ง่ายพอที่จะใช้:

#!/bin/bash
#

readopt='getopts $opts opt;rc=$?;[ $rc$opt == 0? ]&&exit 1;[ $rc == 0 ]||{ shift $[OPTIND-1];false; }'

opts=vfdo:

# Enumerating options
while eval $readopt
do
    echo OPT:$opt ${OPTARG+OPTARG:$OPTARG}
done

# Enumerating arguments
for arg
do
    echo ARG:$arg
done

ตัวอย่างการเรียกร้อง:

./myscript -v -do /fizz/someOtherFile -f ./foo/bar/someFile
OPT:v 
OPT:d 
OPT:o OPTARG:/fizz/someOtherFile
OPT:f 
ARG:./foo/bar/someFile

1
ฉันอ่านทั้งหมดและอันนี้เป็นอันที่ฉันชอบ ฉันไม่ชอบใช้-a=1สไตล์ argc ฉันชอบที่จะใส่ครั้งแรก -options -o optionตัวเลือกหลักและต่อมาคนพิเศษที่มีระยะห่างเพียงครั้งเดียว ฉันกำลังมองหาวิธีที่ง่ายที่สุดในการอ่าน argvs
m3nda

มันใช้งานได้ดีจริงๆ แต่ถ้าคุณส่งอาร์กิวเมนต์ไปที่ไม่ใช่: ตัวเลือกตัวเลือกทั้งหมดต่อไปนี้จะถูกนำมาใช้เป็นอาร์กิวเมนต์ คุณสามารถตรวจสอบบรรทัดนี้./myscript -v -d fail -o /fizz/someOtherFile -f ./foo/bar/someFileด้วยสคริปต์ของคุณเอง ไม่ได้ตั้งค่าตัวเลือก -d เป็น d:
m3nda

15

การขยายคำตอบที่ยอดเยี่ยมโดย @guneysus นี่คือ tweak ที่ให้ผู้ใช้ใช้ไวยากรณ์ที่พวกเขาต้องการเช่น

command -x=myfilename.ext --another_switch 

VS

command -x myfilename.ext --another_switch

นั่นคือการพูดว่าสามารถถูกแทนที่ด้วยช่องว่างเท่ากับ

"การตีความฟัซซี่" นี้อาจไม่ตรงกับความต้องการของคุณ แต่ถ้าคุณกำลังสร้างสคริปต์ที่สามารถใช้แทนกันกับยูทิลิตี้อื่น ๆ (เช่นกรณีของฉันซึ่งต้องทำงานกับ ffmpeg) ความยืดหยุ่นมีประโยชน์

STD_IN=0

prefix=""
key=""
value=""
for keyValue in "$@"
do
  case "${prefix}${keyValue}" in
    -i=*|--input_filename=*)  key="-i";     value="${keyValue#*=}";; 
    -ss=*|--seek_from=*)      key="-ss";    value="${keyValue#*=}";;
    -t=*|--play_seconds=*)    key="-t";     value="${keyValue#*=}";;
    -|--stdin)                key="-";      value=1;;
    *)                                      value=$keyValue;;
  esac
  case $key in
    -i) MOVIE=$(resolveMovie "${value}");  prefix=""; key="";;
    -ss) SEEK_FROM="${value}";          prefix=""; key="";;
    -t)  PLAY_SECONDS="${value}";           prefix=""; key="";;
    -)   STD_IN=${value};                   prefix=""; key="";; 
    *)   prefix="${keyValue}=";;
  esac
done

13

ตัวอย่างนี้แสดงวิธีใช้getoptและevalและHEREDOCและshiftเพื่อจัดการพารามิเตอร์แบบสั้นและยาวโดยมีและไม่มีค่าที่จำเป็นซึ่งตามมา คำสั่ง switch / case กระชับและง่ายต่อการติดตาม

#!/usr/bin/env bash

# usage function
function usage()
{
   cat << HEREDOC

   Usage: $progname [--num NUM] [--time TIME_STR] [--verbose] [--dry-run]

   optional arguments:
     -h, --help           show this help message and exit
     -n, --num NUM        pass in a number
     -t, --time TIME_STR  pass in a time string
     -v, --verbose        increase the verbosity of the bash script
     --dry-run            do a dry run, dont change any files

HEREDOC
}  

# initialize variables
progname=$(basename $0)
verbose=0
dryrun=0
num_str=
time_str=

# use getopt and store the output into $OPTS
# note the use of -o for the short options, --long for the long name options
# and a : for any option that takes a parameter
OPTS=$(getopt -o "hn:t:v" --long "help,num:,time:,verbose,dry-run" -n "$progname" -- "$@")
if [ $? != 0 ] ; then echo "Error in command line arguments." >&2 ; usage; exit 1 ; fi
eval set -- "$OPTS"

while true; do
  # uncomment the next line to see how shift is working
  # echo "\$1:\"$1\" \$2:\"$2\""
  case "$1" in
    -h | --help ) usage; exit; ;;
    -n | --num ) num_str="$2"; shift 2 ;;
    -t | --time ) time_str="$2"; shift 2 ;;
    --dry-run ) dryrun=1; shift ;;
    -v | --verbose ) verbose=$((verbose + 1)); shift ;;
    -- ) shift; break ;;
    * ) break ;;
  esac
done

if (( $verbose > 0 )); then

   # print out all the parameters we read in
   cat <<-EOM
   num=$num_str
   time=$time_str
   verbose=$verbose
   dryrun=$dryrun
EOM
fi

# The rest of your script below

บรรทัดที่สำคัญที่สุดของสคริปต์ด้านบนคือ:

OPTS=$(getopt -o "hn:t:v" --long "help,num:,time:,verbose,dry-run" -n "$progname" -- "$@")
if [ $? != 0 ] ; then echo "Error in command line arguments." >&2 ; exit 1 ; fi
eval set -- "$OPTS"

while true; do
  case "$1" in
    -h | --help ) usage; exit; ;;
    -n | --num ) num_str="$2"; shift 2 ;;
    -t | --time ) time_str="$2"; shift 2 ;;
    --dry-run ) dryrun=1; shift ;;
    -v | --verbose ) verbose=$((verbose + 1)); shift ;;
    -- ) shift; break ;;
    * ) break ;;
  esac
done

สั้นไปยังจุดอ่านและจัดการได้ทุกอย่าง (IMHO)

หวังว่าจะช่วยใครซักคน


1
นี่คือหนึ่งในคำตอบที่ดีที่สุด
Mr. Polywhirl

11

ฉันให้คุณฟังก์ชั่นparse_paramsที่จะแยกพารามิเตอร์จากบรรทัดคำสั่ง

  1. มันเป็นวิธีการแก้ปัญหา Bash บริสุทธิ์ไม่มีสาธารณูปโภคเพิ่มเติม
  2. ไม่สร้างมลภาวะระดับโลก
  3. ให้ผลตอบแทนที่ง่ายดายแก่คุณในการใช้ตัวแปรอย่างง่ายดายซึ่งคุณสามารถสร้างตรรกะเพิ่มเติมได้
  4. ปริมาณของขีดกลางก่อน params ไม่สำคัญ ( --allเท่ากับ-allเท่ากับall=all)

สคริปต์ด้านล่างเป็นการสาธิตการทำงานของการคัดลอกวาง ดูฟังก์ชั่นจะเข้าใจวิธีการใช้งานshow_useparse_params

ข้อ จำกัด :

  1. ไม่รองรับ params คั่นด้วยช่องว่าง ( -d 1)
  2. ชื่อพารามิเตอร์จะสูญเสียเครื่องหมายขีดคั่นดังนั้น--any-paramและ-anyparamเทียบเท่า
  3. eval $(parse_params "$@")จะต้องใช้ภายในฟังก์ชั่นทุบตี(มันจะไม่ทำงานในขอบเขตทั่วโลก)

#!/bin/bash

# Universal Bash parameter parsing
# Parse equal sign separated params into named local variables
# Standalone named parameter value will equal its param name (--force creates variable $force=="force")
# Parses multi-valued named params into an array (--path=path1 --path=path2 creates ${path[*]} array)
# Puts un-named params as-is into ${ARGV[*]} array
# Additionally puts all named params as-is into ${ARGN[*]} array
# Additionally puts all standalone "option" params as-is into ${ARGO[*]} array
# @author Oleksii Chekulaiev
# @version v1.4.1 (Jul-27-2018)
parse_params ()
{
    local existing_named
    local ARGV=() # un-named params
    local ARGN=() # named params
    local ARGO=() # options (--params)
    echo "local ARGV=(); local ARGN=(); local ARGO=();"
    while [[ "$1" != "" ]]; do
        # Escape asterisk to prevent bash asterisk expansion, and quotes to prevent string breakage
        _escaped=${1/\*/\'\"*\"\'}
        _escaped=${_escaped//\'/\\\'}
        _escaped=${_escaped//\"/\\\"}
        # If equals delimited named parameter
        nonspace="[^[:space:]]"
        if [[ "$1" =~ ^${nonspace}${nonspace}*=..* ]]; then
            # Add to named parameters array
            echo "ARGN+=('$_escaped');"
            # key is part before first =
            local _key=$(echo "$1" | cut -d = -f 1)
            # Just add as non-named when key is empty or contains space
            if [[ "$_key" == "" || "$_key" =~ " " ]]; then
                echo "ARGV+=('$_escaped');"
                shift
                continue
            fi
            # val is everything after key and = (protect from param==value error)
            local _val="${1/$_key=}"
            # remove dashes from key name
            _key=${_key//\-}
            # skip when key is empty
            # search for existing parameter name
            if (echo "$existing_named" | grep "\b$_key\b" >/dev/null); then
                # if name already exists then it's a multi-value named parameter
                # re-declare it as an array if needed
                if ! (declare -p _key 2> /dev/null | grep -q 'declare \-a'); then
                    echo "$_key=(\"\$$_key\");"
                fi
                # append new value
                echo "$_key+=('$_val');"
            else
                # single-value named parameter
                echo "local $_key='$_val';"
                existing_named=" $_key"
            fi
        # If standalone named parameter
        elif [[ "$1" =~ ^\-${nonspace}+ ]]; then
            # remove dashes
            local _key=${1//\-}
            # Just add as non-named when key is empty or contains space
            if [[ "$_key" == "" || "$_key" =~ " " ]]; then
                echo "ARGV+=('$_escaped');"
                shift
                continue
            fi
            # Add to options array
            echo "ARGO+=('$_escaped');"
            echo "local $_key=\"$_key\";"
        # non-named parameter
        else
            # Escape asterisk to prevent bash asterisk expansion
            _escaped=${1/\*/\'\"*\"\'}
            echo "ARGV+=('$_escaped');"
        fi
        shift
    done
}

#--------------------------- DEMO OF THE USAGE -------------------------------

show_use ()
{
    eval $(parse_params "$@")
    # --
    echo "${ARGV[0]}" # print first unnamed param
    echo "${ARGV[1]}" # print second unnamed param
    echo "${ARGN[0]}" # print first named param
    echo "${ARG0[0]}" # print first option param (--force)
    echo "$anyparam"  # print --anyparam value
    echo "$k"         # print k=5 value
    echo "${multivalue[0]}" # print first value of multi-value
    echo "${multivalue[1]}" # print second value of multi-value
    [[ "$force" == "force" ]] && echo "\$force is set so let the force be with you"
}

show_use "param 1" --anyparam="my value" param2 k=5 --force --multi-value=test1 --multi-value=test2

หากต้องการใช้การสาธิตเพื่อแยกวิเคราะห์ params ที่เข้ามาในสคริปต์ทุบตีของคุณคุณต้องทำshow_use "$@"
Oleksii Chekulaiev

โดยทั่วไปฉันพบว่าgithub.com/renatosilva/easyoptionsทำแบบเดียวกัน แต่มีขนาดใหญ่กว่าฟังก์ชั่นนี้เล็กน้อย
Oleksii Chekulaiev

10

EasyOptionsไม่จำเป็นต้องแยกวิเคราะห์:

## Options:
##   --verbose, -v  Verbose mode
##   --output=FILE  Output filename

source easyoptions || exit

if test -n "${verbose}"; then
    echo "output file is ${output}"
    echo "${arguments[@]}"
fi

ฉันใช้เวลาสักครู่ในการตระหนักว่าความคิดเห็นที่ด้านบนสุดของสคริปต์ตัวอย่างของคุณกำลังถูกวิเคราะห์เพื่อให้สตริงความช่วยเหลือในการใช้งานเริ่มต้นเช่นเดียวกับข้อกำหนดคุณสมบัติของอาร์กิวเมนต์ นี่เป็นวิธีแก้ปัญหาที่ยอดเยี่ยมและฉันขอโทษด้วยที่มีแค่ 6 โหวตใน 2 ปี บางทีคำถามนี้ล้นเกินกว่าที่ผู้คนจะสังเกตเห็น
Metamorphic

ในแง่หนึ่งโซลูชันของคุณดีที่สุด (นอกเหนือจาก @ OleksiiChekulaiev ซึ่งไม่รองรับไวยากรณ์ตัวเลือก "มาตรฐาน") เพราะนี่คือวิธีการแก้ปัญหาของคุณเพียง แต่ต้องเขียนสคริปต์ในการระบุชื่อของแต่ละตัวเลือกในครั้งเดียว ความจริงที่ว่าโซลูชันอื่น ๆ จำเป็นต้องมีการระบุ 3 ครั้ง - ในการใช้งานในรูปแบบ 'case' และในการตั้งค่าของตัวแปร - ทำให้ฉันรำคาญอย่างต่อเนื่อง แม้ getopt มีปัญหานี้ อย่างไรก็ตามรหัสของคุณช้าในเครื่องของฉัน - 0.11 สำหรับการใช้ Bash, 0.28 สำหรับ Ruby กับ 0.02s สำหรับการแยกวิเคราะห์ "ในขณะที่" ชัดเจน
Metamorphic

ฉันต้องการรุ่นที่เร็วกว่าอาจเขียนด้วยภาษาซีและรุ่นที่เข้ากันได้กับ zsh บางทีสิ่งนี้สมควรได้รับคำถามที่แยกต่างหาก ("มีวิธีการแยกวิเคราะห์บรรทัดคำสั่งในเชลล์เหมือน Bash ซึ่งยอมรับไวยากรณ์ตัวเลือกแบบยาวมาตรฐานและไม่ต้องการพิมพ์ชื่อตัวเลือกมากกว่าหนึ่งครั้งหรือไม่")
Metamorphic

10

getopts ใช้งานได้ดีถ้า # 1 คุณติดตั้งแล้วและ # 2 คุณตั้งใจจะรันบนแพลตฟอร์มเดียวกัน OSX และ Linux (ตัวอย่าง) ประพฤติตัวแตกต่างกันในส่วนนี้

นี่คือโซลูชัน (ไม่ใช่ getopts) ที่สนับสนุนเท่ากับ, ไม่เท่ากับและแฟล็กบูลีน ตัวอย่างเช่นคุณสามารถเรียกใช้สคริปต์ด้วยวิธีนี้:

./script --arg1=value1 --arg2 value2 --shouldClean

# parse the arguments.
COUNTER=0
ARGS=("$@")
while [ $COUNTER -lt $# ]
do
    arg=${ARGS[$COUNTER]}
    let COUNTER=COUNTER+1
    nextArg=${ARGS[$COUNTER]}

    if [[ $skipNext -eq 1 ]]; then
        echo "Skipping"
        skipNext=0
        continue
    fi

    argKey=""
    argVal=""
    if [[ "$arg" =~ ^\- ]]; then
        # if the format is: -key=value
        if [[ "$arg" =~ \= ]]; then
            argVal=$(echo "$arg" | cut -d'=' -f2)
            argKey=$(echo "$arg" | cut -d'=' -f1)
            skipNext=0

        # if the format is: -key value
        elif [[ ! "$nextArg" =~ ^\- ]]; then
            argKey="$arg"
            argVal="$nextArg"
            skipNext=1

        # if the format is: -key (a boolean flag)
        elif [[ "$nextArg" =~ ^\- ]] || [[ -z "$nextArg" ]]; then
            argKey="$arg"
            argVal=""
            skipNext=0
        fi
    # if the format has not flag, just a value.
    else
        argKey=""
        argVal="$arg"
        skipNext=0
    fi

    case "$argKey" in 
        --source-scmurl)
            SOURCE_URL="$argVal"
        ;;
        --dest-scmurl)
            DEST_URL="$argVal"
        ;;
        --version-num)
            VERSION_NUM="$argVal"
        ;;
        -c|--clean)
            CLEAN_BEFORE_START="1"
        ;;
        -h|--help|-help|--h)
            showUsage
            exit
        ;;
    esac
done

8

นี่คือวิธีที่ฉันทำในฟังก์ชั่นเพื่อหลีกเลี่ยงการทำลาย getopts ที่ทำงานในเวลาเดียวกันที่ไหนสักแห่งที่สูงกว่าใน stack:

function waitForWeb () {
   local OPTIND=1 OPTARG OPTION
   local host=localhost port=8080 proto=http
   while getopts "h:p:r:" OPTION; do
      case "$OPTION" in
      h)
         host="$OPTARG"
         ;;
      p)
         port="$OPTARG"
         ;;
      r)
         proto="$OPTARG"
         ;;
      esac
   done
...
}

8

เมื่อเพิ่มคำตอบของ @ bruno-bronosky ฉันได้เพิ่ม "preprocessor" เพื่อจัดการกับการจัดรูปแบบทั่วไป:

  • ขยาย--longopt=valสู่--longopt val
  • ขยาย-xyzสู่-x -y -z
  • รองรับ--การระบุจุดสิ้นสุดของธง
  • แสดงข้อผิดพลาดสำหรับตัวเลือกที่ไม่คาดคิด
  • ตัวเลือกขนาดกะทัดรัดและง่ายต่อการอ่านสลับ
#!/bin/bash

# Report usage
usage() {
  echo "Usage:"
  echo "$(basename $0) [options] [--] [file1, ...]"

  # Optionally exit with a status code
  if [ -n "$1" ]; then
    exit "$1"
  fi
}

invalid() {
  echo "ERROR: Unrecognized argument: $1" >&2
  usage 1
}

# Pre-process options to:
# - expand -xyz into -x -y -z
# - expand --longopt=arg into --longopt arg
ARGV=()
END_OF_OPT=
while [[ $# -gt 0 ]]; do
  arg="$1"; shift
  case "${END_OF_OPT}${arg}" in
    --) ARGV+=("$arg"); END_OF_OPT=1 ;;
    --*=*)ARGV+=("${arg%%=*}" "${arg#*=}") ;;
    --*) ARGV+=("$arg"); END_OF_OPT=1 ;;
    -*) for i in $(seq 2 ${#arg}); do ARGV+=("-${arg:i-1:1}"); done ;;
    *) ARGV+=("$arg") ;;
  esac
done

# Apply pre-processed options
set -- "${ARGV[@]}"

# Parse options
END_OF_OPT=
POSITIONAL=()
while [[ $# -gt 0 ]]; do
  case "${END_OF_OPT}${1}" in
    -h|--help)      usage 0 ;;
    -p|--password)  shift; PASSWORD="$1" ;;
    -u|--username)  shift; USERNAME="$1" ;;
    -n|--name)      shift; names+=("$1") ;;
    -q|--quiet)     QUIET=1 ;;
    -C|--copy)      COPY=1 ;;
    -N|--notify)    NOTIFY=1 ;;
    --stdin)        READ_STDIN=1 ;;
    --)             END_OF_OPT=1 ;;
    -*)             invalid "$1" ;;
    *)              POSITIONAL+=("$1") ;;
  esac
  shift
done

# Restore positional parameters
set -- "${POSITIONAL[@]}"

6

มีหลายวิธีในการแยกวิเคราะห์ cmdline args (เช่น GNU getopt (ไม่ใช่แบบพกพา) เทียบกับ BSD (OSX) getopt vs getopts) - ทั้งหมดเป็นปัญหา วิธีนี้คือ

  • ! แบบพกพา
  • มีการอ้างอิงเป็นศูนย์ขึ้นอยู่กับ bash ในตัวเท่านั้น
  • อนุญาตสำหรับตัวเลือกทั้งระยะสั้นและระยะยาว
  • จัดการช่องว่างระหว่างตัวเลือกและอาร์กิวเมนต์ แต่ยังสามารถใช้=ตัวคั่น
  • รองรับรูปแบบตัวเลือกสั้นตัดแบ่ง -vxf
  • จัดการตัวเลือกด้วยอาร์กิวเมนต์ที่เป็นตัวเลือก (ดูตัวอย่าง) และ
  • ไม่ต้องใช้โค้ด Bloat เปรียบเทียบกับทางเลือกสำหรับชุดคุณลักษณะเดียวกัน คือรวบรัดและง่ายต่อการบำรุงรักษา

ตัวอย่าง: ข้อใดข้อหนึ่ง

# flag
-f
--foo

# option with required argument
-b"Hello World"
-b "Hello World"
--bar "Hello World"
--bar="Hello World"

# option with optional argument
--baz
--baz="Optional Hello"

#!/usr/bin/env bash

usage() {
  cat - >&2 <<EOF
NAME
    program-name.sh - Brief description

SYNOPSIS
    program-name.sh [-h|--help]
    program-name.sh [-f|--foo]
                    [-b|--bar <arg>]
                    [--baz[=<arg>]]
                    [--]
                    FILE ...

REQUIRED ARGUMENTS
  FILE ...
          input files

OPTIONS
  -h, --help
          Prints this and exits

  -f, --foo
          A flag option

  -b, --bar <arg>
          Option requiring an argument <arg>

  --baz[=<arg>]
          Option that has an optional argument <arg>. If <arg>
          is not specified, defaults to 'DEFAULT'
  --     
          Specify end of options; useful if the first non option
          argument starts with a hyphen

EOF
}

fatal() {
    for i; do
        echo -e "${i}" >&2
    done
    exit 1
}

# For long option processing
next_arg() {
    if [[ $OPTARG == *=* ]]; then
        # for cases like '--opt=arg'
        OPTARG="${OPTARG#*=}"
    else
        # for cases like '--opt arg'
        OPTARG="${args[$OPTIND]}"
        OPTIND=$((OPTIND + 1))
    fi
}

# ':' means preceding option character expects one argument, except
# first ':' which make getopts run in silent mode. We handle errors with
# wildcard case catch. Long options are considered as the '-' character
optspec=":hfb:-:"
args=("" "$@")  # dummy first element so $1 and $args[1] are aligned
while getopts "$optspec" optchar; do
    case "$optchar" in
        h) usage; exit 0 ;;
        f) foo=1 ;;
        b) bar="$OPTARG" ;;
        -) # long option processing
            case "$OPTARG" in
                help)
                    usage; exit 0 ;;
                foo)
                    foo=1 ;;
                bar|bar=*) next_arg
                    bar="$OPTARG" ;;
                baz)
                    baz=DEFAULT ;;
                baz=*) next_arg
                    baz="$OPTARG" ;;
                -) break ;;
                *) fatal "Unknown option '--${OPTARG}'" "see '${0} --help' for usage" ;;
            esac
            ;;
        *) fatal "Unknown option: '-${OPTARG}'" "See '${0} --help' for usage" ;;
    esac
done

shift $((OPTIND-1))

if [ "$#" -eq 0 ]; then
    fatal "Expected at least one required argument FILE" \
    "See '${0} --help' for usage"
fi

echo "foo=$foo, bar=$bar, baz=$baz, files=${@}"

5

ฉันต้องการเสนอการแยกวิเคราะห์ตัวเลือกเวอร์ชันของฉันซึ่งอนุญาตสำหรับสิ่งต่อไปนี้:

-s p1
--stage p1
-w somefolder
--workfolder somefolder
-sw p1 somefolder
-e=hello

อนุญาตสำหรับสิ่งนี้ (อาจไม่ต้องการ):

-s--workfolder p1 somefolder
-se=hello p1
-swe=hello p1 somefolder

คุณต้องตัดสินใจก่อนใช้หาก = จะใช้กับตัวเลือกหรือไม่ นี่คือการรักษารหัสที่สะอาด (ish)

while [[ $# > 0 ]]
do
    key="$1"
    while [[ ${key+x} ]]
    do
        case $key in
            -s*|--stage)
                STAGE="$2"
                shift # option has parameter
                ;;
            -w*|--workfolder)
                workfolder="$2"
                shift # option has parameter
                ;;
            -e=*)
                EXAMPLE="${key#*=}"
                break # option has been fully handled
                ;;
            *)
                # unknown option
                echo Unknown option: $key #1>&2
                exit 10 # either this: my preferred way to handle unknown options
                break # or this: do this to signal the option has been handled (if exit isn't used)
                ;;
        esac
        # prepare for next option in this key, if any
        [[ "$key" = -? || "$key" == --* ]] && unset key || key="${key/#-?/-}"
    done
    shift # option(s) fully processed, proceed to next input argument
done

1
ความหมายของ "+ x" บน $ {key + x} คืออะไร
Luca Davanzo

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

5

โซลูชันที่เก็บรักษาอาร์กิวเมนต์ที่ไม่สามารถจัดการได้ รวมการสาธิต

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

การใช้งานคือ: ./myscript -flag flagvariable -otherflag flagvar2

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

./myscript -flag flagvariable -otherflag flagvar2
echo $flag $otherflag
flagvariable flagvar2

รหัสหลัก (เวอร์ชั่นย่อ verbose พร้อมตัวอย่างเพิ่มเติมลงไปและรุ่นที่มีข้อผิดพลาด):

#!/usr/bin/env bash
#shebang.io
validflags="rate time number"
count=1
for arg in $@
do
    match=0
    argval=$1
    for flag in $validflags
    do
        sflag="-"$flag
        if [ "$argval" == "$sflag" ]
        then
            declare $flag=$2
            match=1
        fi
    done
        if [ "$match" == "1" ]
    then
        shift 2
    else
        leftovers=$(echo $leftovers $argval)
        shift
    fi
    count=$(($count+1))
done
#Cleanup then restore the leftovers
shift $#
set -- $leftovers

เวอร์ชัน verbose ที่มีการสาธิต echo:

#!/usr/bin/env bash
#shebang.io
rate=30
time=30
number=30
echo "all args
$@"
validflags="rate time number"
count=1
for arg in $@
do
    match=0
    argval=$1
#   argval=$(echo $@ | cut -d ' ' -f$count)
    for flag in $validflags
    do
            sflag="-"$flag
        if [ "$argval" == "$sflag" ]
        then
            declare $flag=$2
            match=1
        fi
    done
        if [ "$match" == "1" ]
    then
        shift 2
    else
        leftovers=$(echo $leftovers $argval)
        shift
    fi
    count=$(($count+1))
done

#Cleanup then restore the leftovers
echo "pre final clear args:
$@"
shift $#
echo "post final clear args:
$@"
set -- $leftovers
echo "all post set args:
$@"
echo arg1: $1 arg2: $2

echo leftovers: $leftovers
echo rate $rate time $time number $number

ในที่สุดอันนี้ข้อผิดพลาดนี้ออกหากมี - อาร์กิวเมนต์ไม่ถูกต้องจะถูกส่งผ่าน

#!/usr/bin/env bash
#shebang.io
rate=30
time=30
number=30
validflags="rate time number"
count=1
for arg in $@
do
    argval=$1
    match=0
        if [ "${argval:0:1}" == "-" ]
    then
        for flag in $validflags
        do
                sflag="-"$flag
            if [ "$argval" == "$sflag" ]
            then
                declare $flag=$2
                match=1
            fi
        done
        if [ "$match" == "0" ]
        then
            echo "Bad argument: $argval"
            exit 1
        fi
        shift 2
    else
        leftovers=$(echo $leftovers $argval)
        shift
    fi
    count=$(($count+1))
done
#Cleanup then restore the leftovers
shift $#
set -- $leftovers
echo rate $rate time $time number $number
echo leftovers: $leftovers

จุดเด่น: มันทำอะไรได้ดีมันจัดการได้ดีมาก มันรักษาข้อโต้แย้งที่ไม่ได้ใช้ซึ่งโซลูชันอื่น ๆ จำนวนมากที่นี่ทำไม่ได้ นอกจากนี้ยังอนุญาตให้ตัวแปรถูกเรียกโดยไม่ต้องกำหนดด้วยมือในสคริปต์ นอกจากนี้ยังอนุญาตให้มีการเติมพารามิเตอร์ล่วงหน้าหากไม่มีการกำหนดอาร์กิวเมนต์ที่สอดคล้องกัน (ดูตัวอย่างที่ชัดเจน)

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


5

ฉันต้องการส่งโครงการของฉัน: https://github.com/flyingangel/argparser

source argparser.sh
parse_args "$@"

เรียบง่ายเหมือนที่ สภาพแวดล้อมจะถูกเติมด้วยตัวแปรที่มีชื่อเหมือนกับอาร์กิวเมนต์


3

โปรดทราบว่าgetopt(1)นี่เป็นข้อผิดพลาดสั้น ๆ จาก AT&T

getopt ถูกสร้างขึ้นในปี 1984 แต่ถูกฝังอยู่ในปี 1986 เพราะมันไม่สามารถใช้งานได้จริง

หลักฐานสำหรับความจริงที่getoptล้าสมัยมากคือgetopt(1)หน้า man ยังคงกล่าวถึง"$*"แทนที่จะ"$@"ถูกเพิ่มเข้าไปใน Bourne Shell ในปี 1986 พร้อมกับgetopts(1)shell builtin เพื่อจัดการกับการขัดแย้งกับช่องว่างภายใน

BTW: หากคุณสนใจที่จะแยกวิเคราะห์ตัวเลือกแบบยาวในเชลล์สคริปต์อาจเป็นที่สนใจที่จะรู้ว่าการgetopt(3)ติดตั้งจาก libc (Solaris) และksh93ทั้งคู่เพิ่มการใช้งานตัวเลือกแบบยาวที่สนับสนุนตัวเลือกแบบยาวเป็นนามแฝงสำหรับตัวเลือกระยะสั้น นี่เป็นสาเหตุksh93และที่จะใช้อินเตอร์เฟซที่เหมือนกันสำหรับตัวเลือกยาวที่ผ่านBourne Shellgetopts

ตัวอย่างของตัวเลือกแบบยาวที่นำมาจากหน้า man ของ Bourne Shell:

getopts "f:(file)(input-file)o:(output-file)" OPTX "$@"

แสดงให้เห็นว่าสามารถใช้ชื่อแทนตัวเลือกได้นานเท่าใดในทั้ง Bourne Shell และ ksh93

ดู man page ของบอร์นเชลล์ล่าสุด:

http://schillix.sourceforge.net/man/man1/bosh.1.html

และ man page สำหรับ getopt (3) จาก OpenSolaris:

http://schillix.sourceforge.net/man/man3c/getopt.3c.html

และสุดท้ายหน้า getopt (1) man เพื่อตรวจสอบ $ ล้าสมัย:

http://schillix.sourceforge.net/man/man1/getopt.1.html


3

ฉันได้เขียน bash helper เพื่อเขียนเครื่องมือทุบตีที่ดี

โครงการบ้าน: https://gitlab.mbedsys.org/mbedsys/bashopts

ตัวอย่าง:

#!/bin/bash -ei

# load the library
. bashopts.sh

# Enable backtrace dusplay on error
trap 'bashopts_exit_handle' ERR

# Initialize the library
bashopts_setup -n "$0" -d "This is myapp tool description displayed on help message" -s "$HOME/.config/myapprc"

# Declare the options
bashopts_declare -n first_name -l first -o f -d "First name" -t string -i -s -r
bashopts_declare -n last_name -l last -o l -d "Last name" -t string -i -s -r
bashopts_declare -n display_name -l display-name -t string -d "Display name" -e "\$first_name \$last_name"
bashopts_declare -n age -l number -d "Age" -t number
bashopts_declare -n email_list -t string -m add -l email -d "Email adress"

# Parse arguments
bashopts_parse_args "$@"

# Process argument
bashopts_process_args

จะให้ความช่วยเหลือ:

NAME:
    ./example.sh - This is myapp tool description displayed on help message

USAGE:
    [options and commands] [-- [extra args]]

OPTIONS:
    -h,--help                          Display this help
    -n,--non-interactive true          Non interactive mode - [$bashopts_non_interactive] (type:boolean, default:false)
    -f,--first "John"                  First name - [$first_name] (type:string, default:"")
    -l,--last "Smith"                  Last name - [$last_name] (type:string, default:"")
    --display-name "John Smith"        Display name - [$display_name] (type:string, default:"$first_name $last_name")
    --number 0                         Age - [$age] (type:number, default:0)
    --email                            Email adress - [$email_list] (type:string, default:"")

สนุก :)


ฉันได้รับสิ่งนี้บน Mac OS X: `` `lib / bashopts.sh: บรรทัด 138: ประกาศ: -A: ตัวเลือกที่ไม่ถูกต้องประกาศ: การใช้งาน: ประกาศ [-afFirtx] [-p] [ชื่อ [= ค่า] ... ] ข้อผิดพลาดใน lib / bashopts.sh: 138 'ประกาศ -x -A bashopts_optprop_name' ออกจากสถานะ 2 เรียกต้นไม้: 1: lib / controller.sh: 4 แหล่ง (... ) กำลังออกจากสถานะ 1 `` `
Josh Wulf

คุณต้องใช้ Bash รุ่น 4 เพื่อใช้งาน สำหรับ Mac รุ่นเริ่มต้นคือ 3 คุณสามารถใช้ home brew เพื่อติดตั้ง bash 4 ได้
Josh Wulf

3

นี่คือวิธีของฉัน - ใช้ regexp

  • ไม่มี getopts
  • มันจัดการบล็อกของพารามิเตอร์สั้น ๆ -qwerty
  • มันจัดการพารามิเตอร์สั้น ๆ -q -w -e
  • มันจัดการตัวเลือกยาว --qwerty
  • คุณสามารถส่งแอททริบิวไปที่ออปชั่นแบบสั้นหรือแบบยาว (หากคุณใช้บล็อคของตัวเลือกแบบสั้นแอททริบิวต์จะถูกแนบกับตัวเลือกสุดท้าย)
  • คุณสามารถใช้ช่องว่างหรือ=เพื่อให้แอตทริบิวต์ แต่ตรงกับแอตทริบิวต์จนกว่าจะพบเครื่องหมายยัติภังค์ + ช่องว่าง "ตัวคั่น" ดังนั้นใน--q=qwe ty qwe tyหนึ่งคุณลักษณะ
  • มันจัดการการผสมผสานทั้งหมดข้างต้นจึง-o a -op attr ibute --option=att ribu te --op-tion attribute --option att-ributeถูกต้อง

สคริปต์:

#!/usr/bin/env sh

help_menu() {
  echo "Usage:

  ${0##*/} [-h][-l FILENAME][-d]

Options:

  -h, --help
    display this help and exit

  -l, --logfile=FILENAME
    filename

  -d, --debug
    enable debug
  "
}

parse_options() {
  case $opt in
    h|help)
      help_menu
      exit
     ;;
    l|logfile)
      logfile=${attr}
      ;;
    d|debug)
      debug=true
      ;;
    *)
      echo "Unknown option: ${opt}\nRun ${0##*/} -h for help.">&2
      exit 1
  esac
}
options=$@

until [ "$options" = "" ]; do
  if [[ $options =~ (^ *(--([a-zA-Z0-9-]+)|-([a-zA-Z0-9-]+))(( |=)(([\_\.\?\/\\a-zA-Z0-9]?[ -]?[\_\.\?a-zA-Z0-9]+)+))?(.*)|(.+)) ]]; then
    if [[ ${BASH_REMATCH[3]} ]]; then # for --option[=][attribute] or --option[=][attribute]
      opt=${BASH_REMATCH[3]}
      attr=${BASH_REMATCH[7]}
      options=${BASH_REMATCH[9]}
    elif [[ ${BASH_REMATCH[4]} ]]; then # for block options -qwert[=][attribute] or single short option -a[=][attribute]
      pile=${BASH_REMATCH[4]}
      while (( ${#pile} > 1 )); do
        opt=${pile:0:1}
        attr=""
        pile=${pile/${pile:0:1}/}
        parse_options
      done
      opt=$pile
      attr=${BASH_REMATCH[7]}
      options=${BASH_REMATCH[9]}
    else # leftovers that don't match
      opt=${BASH_REMATCH[10]}
      options=""
    fi
    parse_options
  fi
done

แบบนี้ อาจเพียงแค่เพิ่ม -e param เพื่อสะท้อนกับบรรทัดใหม่
mauron85

3

สมมติว่าเราสร้างเชลล์สคริปต์ชื่อtest_args.shดังต่อไปนี้

#!/bin/sh
until [ $# -eq 0 ]
do
  name=${1:1}; shift;
  if [[ -z "$1" || $1 == -* ]] ; then eval "export $name=true"; else eval "export $name=$1"; shift; fi  
done
echo "year=$year month=$month day=$day flag=$flag"

หลังจากที่เราเรียกใช้คำสั่งต่อไปนี้:

sh test_args.sh  -year 2017 -flag  -month 12 -day 22 

ผลลัพธ์จะเป็น:

year=2017 month=12 day=22 flag=true

5
วิธีนี้ใช้แนวทางเดียวกับคำตอบของ Noahแต่มีการตรวจสอบ / ป้องกันความปลอดภัยน้อยกว่า สิ่งนี้ช่วยให้เราสามารถเขียนข้อโต้แย้งโดยพลการลงในสภาพแวดล้อมของสคริปต์และฉันค่อนข้างมั่นใจว่าการใช้ eval ของคุณที่นี่อาจทำให้เกิดการฉีดคำสั่ง
Will Barnwell

2

ใช้โมดูล "อาร์กิวเมนต์" จากbash-modules

ตัวอย่าง:

#!/bin/bash
. import.sh log arguments

NAME="world"

parse_arguments "-n|--name)NAME;S" -- "$@" || {
  error "Cannot parse command line."
  exit 1
}

info "Hello, $NAME!"

2

การผสมอาร์กิวเมนต์ตำแหน่งและการยึดตามสถานะ

--param = arg (เท่ากับตัวคั่น)

การผสมธงระหว่างอาร์กิวเมนต์ตำแหน่งอย่างอิสระ:

./script.sh dumbo 127.0.0.1 --environment=production -q -d
./script.sh dumbo --environment=production 127.0.0.1 --quiet -d

สามารถทำได้ด้วยวิธีการที่ค่อนข้างกระชับ:

# process flags
pointer=1
while [[ $pointer -le $# ]]; do
   param=${!pointer}
   if [[ $param != "-"* ]]; then ((pointer++)) # not a parameter flag so advance pointer
   else
      case $param in
         # paramter-flags with arguments
         -e=*|--environment=*) environment="${param#*=}";;
                  --another=*) another="${param#*=}";;

         # binary flags
         -q|--quiet) quiet=true;;
                 -d) debug=true;;
      esac

      # splice out pointer frame from positional list
      [[ $pointer -gt 1 ]] \
         && set -- ${@:1:((pointer - 1))} ${@:((pointer + 1)):$#} \
         || set -- ${@:((pointer + 1)):$#};
   fi
done

# positional remain
node_name=$1
ip_address=$2

--par arg (คั่นด้วยช่องว่าง)

เป็นเรื่องปกติที่จะไม่มิกซ์--flag=valueแอนด์--flag valueสไตล์

./script.sh dumbo 127.0.0.1 --environment production -q -d

นี่เป็นการอ่านที่ค่อนข้างเล็ก แต่ก็ยังใช้ได้อยู่

./script.sh dumbo --environment production 127.0.0.1 --quiet -d

แหล่ง

# process flags
pointer=1
while [[ $pointer -le $# ]]; do
   if [[ ${!pointer} != "-"* ]]; then ((pointer++)) # not a parameter flag so advance pointer
   else
      param=${!pointer}
      ((pointer_plus = pointer + 1))
      slice_len=1

      case $param in
         # paramter-flags with arguments
         -e|--environment) environment=${!pointer_plus}; ((slice_len++));;
                --another) another=${!pointer_plus}; ((slice_len++));;

         # binary flags
         -q|--quiet) quiet=true;;
                 -d) debug=true;;
      esac

      # splice out pointer frame from positional list
      [[ $pointer -gt 1 ]] \
         && set -- ${@:1:((pointer - 1))} ${@:((pointer + $slice_len)):$#} \
         || set -- ${@:((pointer + $slice_len)):$#};
   fi
done

# positional remain
node_name=$1
ip_address=$2

2

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

เป็นพื้น eval "local key='val'"

function myrsync() {

        local backup=("${@}") args=(); while [[ $# -gt 0 ]]; do k="$1";
                case "$k" in
                    ---sourceuser|---sourceurl|---targetuser|---targeturl|---file|---exclude|---include)
                        eval "local ${k:3}='${2}'"; shift; shift    # Past two arguments
                    ;;
                    *)  # Unknown option  
                        args+=("$1"); shift;                        # Past argument only
                    ;;                                              
                esac                                                
        done; set -- "${backup[@]}"                                 # Restore $@


        echo "${sourceurl}"
}

ประกาศตัวแปรเป็นไอเดียแทนที่จะเป็นกลมเป็นคำตอบส่วนใหญ่ที่นี่

เรียกว่า:

myrsync ---sourceurl http://abc.def.g ---sourceuser myuser ... 

$ {k: 3} นั้นเป็นซับสตริงที่จะลบอันแรก---ออกจากกุญแจ


1

สิ่งนี้อาจมีประโยชน์ที่จะรู้คุณสามารถตั้งค่าและถ้ามีคนป้อนข้อมูลให้แทนที่ค่าเริ่มต้นด้วยค่านั้น

myscript.sh -f ./serverlist.txt หรือเพียงแค่. /myscript.sh (และต้องใช้ค่าเริ่มต้น)

    #!/bin/bash
    # --- set the value, if there is inputs, override the defaults.

    HOME_FOLDER="${HOME}/owned_id_checker"
    SERVER_FILE_LIST="${HOME_FOLDER}/server_list.txt"

    while [[ $# > 1 ]]
    do
    key="$1"
    shift

    case $key in
        -i|--inputlist)
        SERVER_FILE_LIST="$1"
        shift
        ;;
    esac
    done


    echo "SERVER LIST   = ${SERVER_FILE_LIST}"

1

โซลูชันอื่นที่ไม่มี getopt [s], POSIX, สไตล์ Unix แบบเก่า

คล้ายกับวิธีการแก้ Bruno Bronosky โพสต์getopt(s)ที่นี่เป็นหนึ่งโดยไม่ต้องใช้งานของ

หลักคุณลักษณะแตกต่างของการแก้ปัญหาของผมก็คือว่ามันจะช่วยให้มีตัวเลือกตัดแบ่งกันเช่นเดียวกับที่จะมีค่าเท่ากับtar -xzf foo.tar.gz tar -x -z -f foo.tar.gzและเช่นเดียวกับในtar, psฯลฯ ยัติภังค์ชั้นนำเป็นทางเลือกสำหรับบล็อกของตัวเลือกระยะสั้น ( แต่นี้สามารถเปลี่ยนแปลงได้ง่าย) ตัวเลือกแบบยาวได้รับการสนับสนุนเช่นกัน (แต่เมื่อบล็อกเริ่มต้นด้วยหนึ่งในนั้นก็จะต้องมียัติภังค์นำสองตัว)

รหัสพร้อมตัวเลือกตัวอย่าง

#!/bin/sh

echo
echo "POSIX-compliant getopt(s)-free old-style-supporting option parser from phk@[se.unix]"
echo

print_usage() {
  echo "Usage:

  $0 {a|b|c} [ARG...]

Options:

  --aaa-0-args
  -a
    Option without arguments.

  --bbb-1-args ARG
  -b ARG
    Option with one argument.

  --ccc-2-args ARG1 ARG2
  -c ARG1 ARG2
    Option with two arguments.

" >&2
}

if [ $# -le 0 ]; then
  print_usage
  exit 1
fi

opt=
while :; do

  if [ $# -le 0 ]; then

    # no parameters remaining -> end option parsing
    break

  elif [ ! "$opt" ]; then

    # we are at the beginning of a fresh block
    # remove optional leading hyphen and strip trailing whitespaces
    opt=$(echo "$1" | sed 's/^-\?\([a-zA-Z0-9\?-]*\)/\1/')

  fi

  # get the first character -> check whether long option
  first_chr=$(echo "$opt" | awk '{print substr($1, 1, 1)}')
  [ "$first_chr" = - ] && long_option=T || long_option=F

  # note to write the options here with a leading hyphen less
  # also do not forget to end short options with a star
  case $opt in

    -)

      # end of options
      shift
      break
      ;;

    a*|-aaa-0-args)

      echo "Option AAA activated!"
      ;;

    b*|-bbb-1-args)

      if [ "$2" ]; then
        echo "Option BBB with argument '$2' activated!"
        shift
      else
        echo "BBB parameters incomplete!" >&2
        print_usage
        exit 1
      fi
      ;;

    c*|-ccc-2-args)

      if [ "$2" ] && [ "$3" ]; then
        echo "Option CCC with arguments '$2' and '$3' activated!"
        shift 2
      else
        echo "CCC parameters incomplete!" >&2
        print_usage
        exit 1
      fi
      ;;

    h*|\?*|-help)

      print_usage
      exit 0
      ;;

    *)

      if [ "$long_option" = T ]; then
        opt=$(echo "$opt" | awk '{print substr($1, 2)}')
      else
        opt=$first_chr
      fi
      printf 'Error: Unknown option: "%s"\n' "$opt" >&2
      print_usage
      exit 1
      ;;

  esac

  if [ "$long_option" = T ]; then

    # if we had a long option then we are going to get a new block next
    shift
    opt=

  else

    # if we had a short option then just move to the next character
    opt=$(echo "$opt" | awk '{print substr($1, 2)}')

    # if block is now empty then shift to the next one
    [ "$opt" ] || shift

  fi

done

echo "Doing something..."

exit 0

สำหรับตัวอย่างการใช้งานโปรดดูตัวอย่างเพิ่มเติมด้านล่าง

ตำแหน่งของตัวเลือกที่มีข้อโต้แย้ง

สำหรับสิ่งที่มีค่าตัวเลือกที่มีอาร์กิวเมนต์ไม่ได้เป็นตัวเลือกสุดท้าย (ต้องใช้ตัวเลือกแบบยาวเท่านั้น) ดังนั้นในขณะที่เช่นในtar(อย่างน้อยในบางการใช้งาน) fตัวเลือกจะต้องมีอายุเพราะชื่อไฟล์ดังต่อไปนี้ (ใช้tar xzf bar.tar.gzงานได้ แต่tar xfz bar.tar.gzไม่ได้) นี่ไม่ใช่กรณีที่นี่ (ดูตัวอย่างในภายหลัง)

ตัวเลือกหลายตัวพร้อมข้อโต้แย้ง

ในฐานะโบนัสอื่นพารามิเตอร์ตัวเลือกจะถูกใช้ตามลำดับตัวเลือกโดยพารามิเตอร์พร้อมตัวเลือกที่จำเป็น เพียงแค่ดูผลลัพธ์ของสคริปต์ของฉันที่นี่ด้วยบรรทัดคำสั่งabc X Y Z(หรือ-abc X Y Z):

Option AAA activated!
Option BBB with argument 'X' activated!
Option CCC with arguments 'Y' and 'Z' activated!

ตัวเลือกยาวตัดแบ่งเช่นกัน

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

  • -cba Z Y X
  • cba Z Y X
  • -cb-aaa-0-args Z Y X
  • -c-bbb-1-args Z Y X -a
  • --ccc-2-args Z Y -ba X
  • c Z Y b X a
  • -c Z Y -b X -a
  • --ccc-2-args Z Y --bbb-1-args X --aaa-0-args

สิ่งเหล่านี้นำไปสู่:

Option CCC with arguments 'Z' and 'Y' activated!
Option BBB with argument 'X' activated!
Option AAA activated!
Doing something...

ไม่ได้อยู่ในโซลูชันนี้

อาร์กิวเมนต์ตัวเลือก

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

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

ฉันชอบตัวเลือกเพิ่มเติมแทนข้อโต้แย้งส่วนตัว

อาร์กิวเมนต์ตัวเลือกที่มีเครื่องหมายเท่ากับ

เช่นเดียวกับอาร์กิวเมนต์ที่เป็นตัวเลือกฉันไม่ใช่แฟนตัวยงของเรื่องนี้ (BTW มีเธรดสำหรับพูดคุยข้อดี / ข้อเสียของรูปแบบพารามิเตอร์ที่แตกต่างกันหรือไม่) แต่ถ้าคุณต้องการสิ่งนี้คุณอาจนำไปใช้เองได้เหมือนที่http: // mywiki.wooledge.org/BashFAQ/035#Manual_loopพร้อมด้วย--long-with-arg=?*คำสั่ง case และจากนั้นลอกเครื่องหมายเท่ากับ (นี่คือ BTW ไซต์ที่บอกว่าการทำให้การต่อพารามิเตอร์เป็นไปได้ด้วยความพยายาม แต่ "ปล่อย [มัน] เป็นแบบฝึกหัดสำหรับผู้อ่าน "ซึ่งทำให้ฉันใช้คำของพวกเขา แต่ฉันเริ่มจากศูนย์)

บันทึกอื่น ๆ

POSIX สอดคล้องทำงานได้แม้ในการตั้งค่า Busybox โบราณผมต้องจัดการกับ (มีเช่นcut, headและgetoptsหายไป)

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