ฉันจะแยกอาร์กิวเมนต์ตัวเลือกในสคริปต์ทุบตีหากไม่มีการสั่งซื้อได้อย่างไร


13

ฉันสับสนวิธีรวมอาร์กิวเมนต์ / แฟล็กเผื่อเลือกเมื่อเขียนสคริปต์ทุบตีสำหรับโปรแกรมต่อไปนี้:

โปรแกรมต้องการสองอาร์กิวเมนต์:

run_program --flag1 <value> --flag2 <value>

อย่างไรก็ตามมีแฟล็กเผื่อเลือกหลายรายการ:

run_program --flag1 <value> --flag2 <value> --optflag1 <value> --optflag2 <value> --optflag3 <value> --optflag4 <value> --optflag5 <value> 

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

#!/bin/sh

run_program --flag1 $1 --flag2 $2

แต่จะเกิดอะไรขึ้นถ้ามีอาร์กิวเมนต์ตัวเลือกใด ๆ รวมอยู่ด้วย? ฉันคิดว่ามันจะเป็น

if [ --optflag1 "$3" ]; then
    run_program --flag1 $1 --flag2 $2 --optflag1 $3
fi

แต่ถ้าหากได้รับ $ 4 แต่ไม่ใช่ $ 3 ล่ะ


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


@ orion ด้วยgetoptsฉันจะต้องระบุชุดค่าผสมแต่ละอาร์กิวเมนต์หรือไม่ 3 & 4, 3 & 5, 3 & 4 & 5 ฯลฯ ?
ShanZhengYang

ไม่คุณเพียงแค่ตั้งค่าหากคุณได้รับมิฉะนั้นจะไม่พบรายงานดังนั้นโดยทั่วไปคุณเพียงแค่ "รับ" แต่ละตัวเลือกไม่ว่าจะเรียงตามลำดับใดก็ตามและระบุไว้ในลำดับใดก็ได้ แต่เพียงแค่อ่านหน้าคนทุบตีมันมีทั้งหมด
orion

@orion ฉันขอโทษ getoptsแต่ฉันยังคงไม่เข้าใจ สมมติว่าผมบังคับให้ผู้ใช้เรียกใช้สคริปต์ที่มีการขัดแย้งทั้งหมด: ซึ่งไหลโปรแกรมเป็นrun_program.sh VAL VAL FALSE FALSE FALSE FALSE FALSE program --flag1 VAL --flag2 VALถ้าคุณวิ่งโปรแกรมจะทำงานเป็นrun_program.sh VAL VAL FALSE 10 FALSE FALSE FALSE program --flag1 VAL --flag2 VAL --optflag2 10คุณจะมีพฤติกรรมเช่นนี้ได้getoptsอย่างไร
ShanZhengYang

คำตอบ:


25

บทความนี้แสดงวิธีที่แตกต่างกันสองวิธี - shiftและgetopts(และอธิบายถึงข้อดีและข้อเสียของวิธีการสองวิธี)

ด้วยshiftรูปลักษณ์ที่สคริปต์ของคุณที่$1ตัดสินใจสิ่งที่จะดำเนินการแล้วรันshiftย้าย$2ไป$1, $3ไป$2ฯลฯ

ตัวอย่างเช่น:

while :; do
    case $1 in
        -a|--flag1) flag1="SET"            
        ;;
        -b|--flag2) flag2="SET"            
        ;;
        -c|--optflag1) optflag1="SET"            
        ;;
        -d|--optflag2) optflag2="SET"            
        ;;
        -e|--optflag3) optflag3="SET"            
        ;;
        *) break
    esac
    shift
done

เมื่อgetoptsคุณกำหนดตัวเลือก (สั้น) ในwhileนิพจน์:

while getopts abcde opt; do
    case $opt in
        a) flag1="SET"
        ;;
        b) flag2="SET"
        ;;
        c) optflag1="SET"
        ;;
        d) optflag2="SET"
        ;;
        e) optflag3="SET"
        ;;
    esac
done

เห็นได้ชัดว่านี่เป็นเพียงโค้ด - โค้ดและฉันไม่ได้ทำการตรวจสอบ - ตรวจสอบว่าชุดคำสั่ง args flag1 และ flag2 ถูกตั้งค่าไว้ ฯลฯ

วิธีการที่คุณใช้คือการเพิ่มรสนิยมให้คุณ - แบบพกพาที่คุณต้องการให้สคริปต์ของคุณไม่ว่าคุณจะอยู่กับตัวเลือกสั้น ๆ (POSIX) เท่านั้นหรือคุณต้องการตัวเลือกแบบยาว (GNU) ฯลฯ


ฉันมักจะทำwhile (( $# ))แทนwhile :;และมักเลิกกับข้อผิดพลาดใน*กรณี
Jasen

คำตอบนี้ชื่อ แต่ไม่ใช่คำถาม
Jasen

@ Jasen ฉันดูเมื่อคืนนี้และทำไม่ได้สำหรับชีวิตของฉันดูว่าทำไม เช้านี้มันชัดเจนขึ้น (และฉันก็ได้เห็นคำตอบของ orion แล้วด้วย) ฉันจะลบคำตอบนี้ในวันนี้ (ฉันต้องการรับทราบความคิดเห็นของคุณก่อนและนี่เป็นวิธีที่ง่ายที่สุดในการทำ)
John N

ไม่มีปัญหามันยังคงเป็นคำตอบที่ดีเพียงคำถามที่หลบมัน
Jasen

1
หมายเหตุ: ในกรณีที่set -o nounsetการแก้ปัญหาครั้งแรกจะเกิดข้อผิดพลาดหากไม่ได้รับพารามิเตอร์ แก้ไข:case ${1:-} in
Raphael

3

ใช้อาร์เรย์

#!/bin/bash

args=( --flag1 "$1" --flag2 "$2" )
[  "x$3" = xFALSE ] ||  args+=( --optflag1 "$3" )
[  "x$4" = xFALSE ] ||  args+=( --optflag2 "$4" )
[  "x$5" = xFALSE ] ||  args+=( --optflag3 "$5" )
[  "x$6" = xFALSE ] ||  args+=( --optflag4 "$6" )
[  "x$7" = xFALSE ] ||  args+=( --optflag5 "$7" )

program_name "${args[@]}"

นี้จะจัดการกับข้อโต้แย้งด้วยช่องว่างในพวกเขาอย่างถูกต้อง

[แก้ไข] ฉันใช้ไวยากรณ์ eqivalent คร่าวๆargs=( "${args[@]}" --optflag1 "$3" )แต่ G-Man แนะนำวิธีที่ดีกว่า


1
คุณสามารถปรับปรุงเล็ก ๆ น้อย ๆ args+=( --optflag1 "$3" )นี้โดยกล่าวว่า คุณอาจต้องการเห็นคำตอบของฉันสำหรับงานอ้างอิงของเราความหมายของการรักษาความปลอดภัยของความล้มเหลวในการอ้างอิงตัวแปรในเชลล์ bash / POSIXที่ฉันพูดถึงเทคนิคนี้ (ของการสร้างบรรทัดคำสั่งในอาร์เรย์
G-Man กล่าวว่า 'Reinstate Monica'

@ G-Man ขอบคุณฉันไม่เคยเห็นว่าในเอกสารประกอบการทำความเข้าใจ[@]ฉันมีเครื่องมือเพียงพอที่จะแก้ปัญหาของฉัน
Jasen

1

ในเชลล์สคริปต์อาร์กิวเมนต์คือ "$ 1", "$ 2", "$ 3" ฯลฯ จำนวนอาร์กิวเมนต์คือ $ #
หากสคริปต์ของคุณไม่รู้จักตัวเลือกคุณสามารถออกจากการตรวจสอบตัวเลือกและถือว่าข้อโต้แย้งทั้งหมดเป็นตัวถูกดำเนินการ
เมื่อต้องการรู้จักตัวเลือกใช้ getopts builtin


0

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

FLAG1="--flag1 $1"
FLAG2="--flag2 $2"
OPTFLAG1=""
OPTFLAG2=""
OPTFLAG3=""
if [ xFALSE != x"$3" ]; then
   OPTFLAG1="--optflag1 $3"
fi
if [ xFALSE != x"$4" ]; then
   OPTFLAG2="--optflag2 $4"
fi
#the same for other flags

#now just run the program
runprogram $FLAG1 $FLAG2 $OPTFLAG1 $OPTFLAG2 $OPTFLAG3

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

xคำนำหน้าใน[]การทดสอบที่มีในกรณี$3หรือ$4เป็นที่ว่างเปล่า ในกรณีดังกล่าว bash จะขยาย[ FALSE != $3 ]ออกไป[ FALSE != ]ซึ่งเป็นข้อผิดพลาดทางไวยากรณ์ดังนั้นอักขระอื่นใดที่มีอยู่เพื่อป้องกันสิ่งนี้ นี่เป็นวิธีทั่วไปคุณจะเห็นมันในหลาย ๆ สคริปต์

ฉันตั้งค่าOPTFLAG1และส่วนที่เหลือของพวกเขาไป""ที่จุดเริ่มต้นเพียงเพื่อให้แน่ใจว่า (ในกรณีที่พวกเขาถูกตั้งค่าให้บางสิ่งบางอย่างก่อน) แต่ถ้าพวกเขาไม่ได้ประกาศในสภาพแวดล้อมจริงแล้วคุณไม่จำเป็นต้องทำอย่างนั้น

ข้อสังเกตเพิ่มเติมสองสามข้อ:

  • คุณสามารถรับพารามิเตอร์ได้ในแบบเดียวrunprogramกับที่มี: นั่นคือสิ่งที่John Nพูดถึง นั่นคือสิ่งที่getoptsจะเป็นประโยชน์
  • การใช้ FALSE เพื่อจุดประสงค์นั้นค่อนข้างไม่ได้มาตรฐานและค่อนข้างยาว โดยปกติแล้วหนึ่งจะใช้อักขระเดียว (อาจ-) เพื่อส่งสัญญาณค่าว่างเปล่าหรือเพียงแค่ผ่านสตริงที่ว่างเปล่าเช่น""ถ้าไม่ได้เป็นอย่างอื่นค่าที่ถูกต้องของพารามิเตอร์ if [ -n "$3" ]สตริงที่ว่างเปล่ายังทำให้การทดสอบสั้นเพียงแค่ใช้
  • หากมีเพียงธงเดียวที่เป็นทางเลือกหรือหากพวกมันขึ้นอยู่กับว่าคุณจะไม่มี OPTFLAG2 แต่ไม่ใช่ OPTFLAG1 คุณก็สามารถข้ามสิ่งที่คุณไม่ต้องการตั้งค่าได้ หากคุณใช้""สำหรับพารามิเตอร์ที่ว่างเปล่าเช่นที่แนะนำไว้ข้างต้นคุณสามารถข้ามสิ่งที่ต่อท้ายได้ทั้งหมด

วิธีนี้ค่อนข้างจะมีการส่งตัวเลือกไปยังคอมไพเลอร์ใน Makefiles

และอีกครั้ง - ถ้าอินพุทอาจมีช่องว่างมันก็น่าเกลียด


ไม่คุณไม่ต้องการให้เชลล์แยกพารามิเตอร์เป็นคำ คุณควรอ้างอิงการอ้างอิงถึงตัวแปรเชลล์ทุกครั้งยกเว้นว่าคุณไม่มีเหตุผลที่ดีและคุณมั่นใจว่าคุณรู้ว่าคุณกำลังทำอะไรอยู่ ดูผลกระทบการรักษาความปลอดภัยของการล้มเหลวในการพูดตัวแปรในทุบตี / เปลือกหอย POSIXในกรณีที่คุณไม่คุ้นเคยกับมันและเลื่อนลงไปที่คำตอบของฉันที่ฉันอยู่ได้อย่างแม่นยำปัญหานี้ (กับเทคนิคเดียวกันใช้ Jasen )
G-Man กล่าวว่า 'Reinstate Monica'
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.