@Kusalananda ได้อธิบายปัญหาพื้นฐานและวิธีการแก้ไขแล้วและรายการBash FAQ ที่ลิงก์โดย @glenn jackmann ยังให้ข้อมูลที่มีประโยชน์มากมาย นี่คือคำอธิบายโดยละเอียดเกี่ยวกับสิ่งที่เกิดขึ้นในปัญหาของฉันตามทรัพยากรเหล่านี้
เราจะใช้สคริปต์ขนาดเล็กที่พิมพ์แต่ละข้อโต้แย้งของมันในบรรทัดแยกต่างหากเพื่อแสดงสิ่งต่าง ๆ ( argtest.bash
):
#!/bin/bash
for var in "$@"
do
echo "$var"
done
ตัวเลือกผ่าน "ด้วยตนเอง":
$ ./argtest.bash -rnv --exclude='.*'
-rnv
--exclude=.*
ตามที่คาดไว้ชิ้นส่วน-rnv
และ--exclude='.*'
ถูกแบ่งออกเป็นสองอาร์กิวเมนต์ตามที่ถูกคั่นด้วย whitespace ที่ไม่ได้กล่าวถึง (เรียกว่าการแยกคำ )
นอกจากนี้ทราบว่าคำพูดที่อยู่รอบ ๆ.*
ได้ถูกลบออก: ราคาเดียวบอกเปลือกจะผ่านเนื้อหาของพวกเขาโดยไม่ต้องตีความพิเศษ , แต่คำพูดตัวเองไม่ได้ส่งผ่านไปยังคำสั่ง
ถ้าตอนนี้เราเก็บตัวเลือกไว้ในตัวแปรเป็นสตริง (ซึ่งต่างจากการใช้อาร์เรย์) ดังนั้นเครื่องหมายคำพูดจะไม่ถูกลบ :
$ OPTS="--exclude='.*'"
$ ./argtest.bash $OPTS
--exclude='.*'
นี่เป็นเพราะเหตุผลสองประการคือ: อัญประกาศคู่ที่ใช้เมื่อกำหนด$OPTS
ป้องกันการดูแลพิเศษของราคาเดียวดังนั้นหลังเป็นส่วนหนึ่งของค่า:
$ echo $OPTS
--exclude='.*'
เมื่อตอนนี้เราใช้$OPTS
เป็นอาร์กิวเมนต์ให้กับคำสั่งแล้วอัญประกาศจะถูกประมวลผลก่อนที่จะขยายพารามิเตอร์ดังนั้นคำพูดในที่$OPTS
เกิดขึ้น "สายเกินไป"
ซึ่งหมายความว่า (ในปัญหาดั้งเดิมของฉัน) rsync
ใช้รูปแบบการแยก'.*'
(พร้อมอัญประกาศ!) แทนรูปแบบ.*
- ยกเว้นไฟล์ที่มีชื่อขึ้นต้นด้วยอัญประกาศเดี่ยวตามด้วยจุดและลงท้ายด้วยเครื่องหมายคำพูดเดี่ยว เห็นได้ชัดว่านั่นไม่ใช่สิ่งที่ตั้งใจไว้
วิธีแก้ปัญหาคือการละเว้นเครื่องหมายคำพูดคู่เมื่อกำหนด$OPTS
:
$ OPTS2=--exclude='.*'
$ ./argtest.bash $OPTS2
--exclude=.*
อย่างไรก็ตามมันเป็นแนวปฏิบัติที่ดีที่จะอ้างอิงการกำหนดตัวแปรเสมอเนื่องจากความแตกต่างที่ลึกซึ้งในกรณีที่ซับซ้อนมากขึ้น
ในฐานะที่เป็น @Kusalananda ตั้งข้อสังเกตไม่อ้าง.*
ยังจะได้ทำงาน ฉันได้เพิ่มเครื่องหมายคำพูดเพื่อป้องกันการขยายตัวของรูปแบบแต่นั่นไม่จำเป็นอย่างยิ่งในกรณีพิเศษนี้ :
$ ./argtest.bash --exclude=.*
--exclude=.*
แต่กลับกลายเป็นว่าทุบตีไม่ดำเนินการขยายรูปแบบ แต่รูปแบบ--exclude=.*
ไม่ตรงกับไฟล์ใด ๆ เพื่อให้รูปแบบจะถูกส่งผ่านไปยังคำสั่ง เปรียบเทียบ:
$ touch some_file
$ ./argtest.bash some_*
some_file
$ ./argtest.bash does_not_exit_*
does_not_exit_*
อย่างไรก็ตามการไม่อ้างอิงรูปแบบเป็นสิ่งที่อันตรายเพราะหาก (ด้วยเหตุผลใดก็ตาม) มีการจับคู่ไฟล์--exclude=.*
ดังนั้นรูปแบบจะขยายออก:
$ touch -- --exclude=.special-filenames-happen
$ ./argtest.bash --exclude=.*
--exclude=.special-filenames-happen
ในที่สุดเรามาดูกันว่าทำไมการใช้อาร์เรย์จึงป้องกันปัญหาการอ้างถึงของฉัน (นอกเหนือจากข้อดีอื่น ๆ ของการใช้อาร์เรย์เพื่อเก็บอาร์กิวเมนต์ของคำสั่ง)
เมื่อกำหนดอาร์เรย์การแบ่งคำและการจัดการคำพูดจะเกิดขึ้นตามที่คาดไว้:
$ ARRAY_OPTS=( -rnv --exclude='.*' )
$ echo length of the array: "${#ARRAY_OPTS[@]}"
length of the array: 2
$ echo first element: "${ARRAY_OPTS[0]}"
first element: -rnv
$ echo second element: "${ARRAY_OPTS[1]}"
second element: --exclude=.*
เมื่อส่งตัวเลือกไปยังคำสั่งเราจะใช้ไวยากรณ์"${ARRAY[@]}"
ซึ่งขยายแต่ละองค์ประกอบของอาร์เรย์เป็นคำแยก:
$ ./argtest.bash "${ARRAY_OPTS[@]}"
-rnv
--exclude=.*