ข้อผิดพลาดในการทดสอบวงเล็บของเชลล์เมื่อสตริงเป็นวงเล็บซ้าย


27

ฉันเคยมีความมั่นใจเกี่ยวกับความจริงที่ว่าการอ้างถึงสตริงนั้นเป็นวิธีปฏิบัติที่ดีเสมอเพื่อหลีกเลี่ยงการแยกเชลล์

จากนั้นฉันก็เจอสิ่งนี้:

$ x='('
$ [ "$x" = '1' -a "$y" = '1' ]
bash: [: `)' expected, found 1

พยายามที่จะแยกปัญหาได้รับข้อผิดพลาดเดียวกัน:

$ [ '(' = '1' -a '1' = '1' ]
bash: [: `)' expected, found 1

ฉันแก้ไขปัญหาเช่นนี้:

[ "$x" = '1' ] && [ "$y" = '1' ]

ยังฉันต้องรู้ว่าเกิดอะไรขึ้นที่นี่


2
คุณสามารถใช้งานได้[[ "$x" = '1' && "$y" = '1' ]]
terdon

3
ข้อมูลจำเพาะ POSIX สำหรับการทดสอบอธิบายอย่างชัดเจน-aและ-oล้าสมัยด้วยเหตุผลนี้ (ซึ่งเป็นสิ่งที่[OB]ยกมาถัดจากข้อมูลจำเพาะของพวกเขาหมายถึง) หากคุณเขียน[ "$x" = 1 ] && [ "$y" = 1 ]แทนคุณจะสบายดีและจะอยู่ในขอบเขตของพฤติกรรมที่กำหนดไว้อย่างดี / เป็นมาตรฐาน
Charles Duffy

6
นี่คือเหตุผลที่ผู้คนเคยใช้[ "x$x" = "x1" ]เพื่อป้องกันการขัดแย้งที่ถูกตีความผิดในฐานะผู้ประกอบการ
Jonathan Leffler

@JanathanLeffler: เฮ้คุณย่อคำตอบของฉันไปเป็นประโยคเดียวไม่ยุติธรรม! :) ถ้ามีใครใช้ POSIX เชลล์dashแทน Bash มันก็ยังเป็นประโยชน์อยู่
สัตว์ที่กำหนด

ฉันอยากจะขอบคุณทุกคนที่สละเวลาตอบคำถามของฉันพวกเขาซาบซึ้งจริงๆ :)! ฉันต้องการขอบคุณสำหรับการแก้ไขที่สำคัญสำหรับคำถามของฉัน การเข้าสู่ฟอรั่มนี้บางครั้งทำให้ฉันมีความตื่นเต้นในการหลบหนีจากอัลคาทราซการย้ายผิดหมายถึงชีวิตของคุณ อย่างไรก็ตามฉันหวังว่าคำขอบคุณของฉันจะมาหาคุณก่อนที่ความคิดเห็นนี้จะถูกลบ
Claudio

คำตอบ:


25

นี่เป็นกรณีมุมที่คลุมเครือมากซึ่งอาจพิจารณาข้อผิดพลาดในวิธีการทดสอบ[ในตัว อย่างไรก็ตามมันตรงกับพฤติกรรมของ[ไบนารีจริงที่มีอยู่ในหลาย ๆ ระบบ เท่าที่ผมสามารถบอกได้ว่ามันมีผลเฉพาะบางกรณีและตัวแปรที่มีค่าที่ตรงกับที่[ผู้ประกอบการเช่น(, !, =, -eและอื่น ๆ

ให้ฉันอธิบายว่าทำไมและวิธีแก้ไขในเชลล์ Bash และ POSIX


คำอธิบาย:

พิจารณาสิ่งต่อไปนี้:

x="("
[ "$x" = "(" ] && echo yes || echo no

ไม่มีปัญหา; yesอัตราผลตอบแทนดังกล่าวข้างต้นไม่มีข้อผิดพลาดและเอาท์พุท นี่คือวิธีที่เราคาดหวังสิ่งที่ทำงาน คุณสามารถเปลี่ยนสตริงเปรียบเทียบเป็น'1'หากคุณต้องการและคุณค่าของxมันและมันจะทำงานตามที่คาดไว้

โปรดทราบว่า/usr/bin/[ไบนารีจริงจะทำงานในลักษณะเดียวกัน หากคุณเรียกใช้เช่น'/usr/bin/[' '(' = '(' ']'ไม่มีข้อผิดพลาดเพราะโปรแกรมสามารถตรวจพบว่าข้อโต้แย้งประกอบด้วยการดำเนินการเปรียบเทียบสตริงเดียว

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

[ '1' = '1' ] && echo yes || echo no

ผลลัพธ์yesและเห็นได้ชัดว่าเป็นการแสดงออกที่ถูกต้อง; แต่ถ้าเรารวมสอง

[ "$x" = "(" -a '1' = '1' ] && echo yes || echo no

ทุบตีปฏิเสธการแสดงออกและถ้าหากxเป็นหรือ(!

ถ้าเราจะเรียกใช้ข้างต้นโดยใช้[โปรแกรมจริงเช่น

'/usr/bin/[' "$x" = "(" -a '1' = '1' ] && echo yes || echo no

ข้อผิดพลาดจะสามารถเข้าใจได้: เนื่องจากเชลล์ทำการแทนที่ตัวแปร/usr/bin/[ไบนารีจะรับพารามิเตอร์( = ( -a 1 = 1และการยกเลิก]เท่านั้นจึงไม่สามารถแยกวิเคราะห์ได้ว่าวงเล็บเปิดจะเริ่มการแสดงออกหรือไม่มีการ ดำเนินการและเกี่ยวข้อง แน่นอนว่าการแยกวิเคราะห์เป็นสองการเปรียบเทียบสตริงเป็นไปได้ แต่การทำอย่างโลภอย่างที่อาจทำให้เกิดปัญหาเมื่อนำไปใช้กับการแสดงออกที่เหมาะสมกับการแสดงออกย่อยย่อยวงเล็บ

ปัญหาที่แท้จริงคือเชลล์[ในตัวทำงานในลักษณะเดียวกันราวกับว่ามันขยายค่าxก่อนที่จะตรวจสอบนิพจน์

(ความกำกวมเหล่านี้และอื่น ๆ ที่เกี่ยวข้องกับการขยายตัวของตัวแปรเป็นสาเหตุใหญ่ที่ทำให้ Bash ดำเนินการและตอนนี้แนะนำให้ใช้[[ ... ]]นิพจน์ทดสอบแทน)


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

[ "x$x" = "x(" -a "x$y" = "x1" ]

1
ฉันจะไม่เรียกพฤติกรรมของ[ข้อบกพร่องในตัว ถ้ามีอะไรมันเป็นข้อบกพร่องการออกแบบโดยธรรมชาติ [[เป็นคีย์เวิร์ดของเชลล์ไม่ใช่แค่คำสั่งในตัวเท่านั้นดังนั้นมันจะได้ดูสิ่งต่าง ๆ ก่อนที่จะทำการลบคำพูดและจะแทนที่การแบ่งคำตามปกติ เช่น[[ $x == 1 ]]ไม่จำเป็นต้องใช้"$x"เพราะ[[บริบทแตกต่างจากปกติ อย่างไรก็ตามนี้เป็นวิธีการที่สามารถที่จะหลีกเลี่ยงข้อผิดพลาดของ[[ [POSIX ต้อง[ทำงานในแบบที่มันทำและทุบตีเป็นไปตาม POSIX ส่วนใหญ่แม้จะไม่มี--posixดังนั้นการเปลี่ยน[เป็นคำหลักนั้นไม่น่าสนใจ
Peter Cordes

วิธีแก้ปัญหาของคุณไม่แนะนำโดย POSIX [เพียงแค่ใช้สองสายไป
สัญลักษณ์แทน

@PeterCordes: ค่อนข้างถูกต้อง; จุดดี. (บางทีฉันควรจะใช้การออกแบบแทนการกำหนดไว้ในย่อหน้าแรกของฉัน แต่[พฤติกรรมที่เป็นภาระจากประวัติศาสตร์ก่อนหน้า POSIX ฉันเลือกคำหลังแทน) ฉันหลีกเลี่ยงการใช้ส่วนตัว[[ในสคริปต์ตัวอย่างของฉัน แต่เพราะมันเป็น ข้อยกเว้นของการอ้างอิงที่อ้างถึงฉันมักจะพิถีพิถันเกี่ยวกับ (เนื่องจากการละเว้นการเสนอราคาเป็นสาเหตุที่พบบ่อยที่สุดสำหรับข้อบกพร่องสคริปต์ที่ฉันเห็น) และฉันไม่ได้คิดถึงย่อหน้าง่าย ๆ ที่จะอธิบายว่าทำไม[[เป็นข้อยกเว้นของกฎ สงสัย.
สัตว์ที่กำหนด

@ Wildcard: ไม่ POSIX ไม่แนะนำการปฏิบัตินี้และนั่นคือสิ่งที่สำคัญ เพียงเพราะผู้มีอำนาจไม่ได้เกิดขึ้นเพื่อแนะนำการปฏิบัตินี้ไม่ได้ทำให้เรื่องนี้แย่ อันที่จริงแล้วเมื่อฉันชี้ให้เห็นว่านี่เป็นวิธีปฏิบัติทางประวัติศาสตร์ที่ใช้ในshสคริปต์ ใช้ subexpressions วงเล็บและ-aและ-oดำเนินการทางตรรกะในการทดสอบ / [มีประสิทธิภาพมากขึ้นที่อาศัยการแสดงออกผูกมัด (ผ่าน&&และ||); เป็นเพียงแค่ในเครื่องปัจจุบันความแตกต่างนั้นไม่เกี่ยวข้อง
สัตว์ที่กำหนด

@ Wildcard: อย่างไรก็ตามฉันเองชอบที่จะโยงการแสดงออกของการทดสอบโดยใช้&&และ||เหตุผลก็คือมันทำให้มนุษย์เราง่ายต่อการรักษา (อ่านทำความเข้าใจและแก้ไขหากจำเป็น) โดยไม่ต้องมีข้อบกพร่องในการแนะนำ ดังนั้นฉันไม่ได้วิจารณ์ข้อเสนอแนะของคุณ แต่เพียงเหตุผลที่อยู่เบื้องหลังข้อเสนอแนะ (สำหรับเหตุผลที่คล้ายกันมาก.LT., .GE.ฯลฯ ดำเนินการเปรียบเทียบใน FORTRAN 77 มีรุ่นอื่น ๆ อีกมากมายเป็นมิตรกับมนุษย์<, >=ฯลฯ รุ่นที่ใหม่กว่า.)
Nominal สัตว์

11

[อาคาtestเห็น:

 argc: 1 2 3 4  5 6 7 8
 argv: ( = 1 -a 1 = 1 ]

testยอมรับ subexpressions ในวงเล็บ; ดังนั้นจึงคิดว่าเครื่องหมายวงเล็บด้านซ้ายเปิดนิพจน์ย่อยและพยายามแยกวิเคราะห์ parser มองว่า=เป็นสิ่งแรกใน subexpression และคิดว่ามันเป็นการทดสอบความยาวของสตริงโดยปริยายดังนั้นจึงมีความสุข subexpression แล้วควรจะตามด้วยวงเล็บขวาและแทนที่จะแยกวิเคราะห์พบแทน1 )และมันก็บ่น

เมื่อtestมีสามอาร์กิวเมนต์ที่แน่นอนและอาร์กิวเมนต์กลางเป็นหนึ่งในตัวดำเนินการที่รู้จักจะใช้ตัวดำเนินการนั้นกับอาร์กิวเมนต์ที่ 1 และ 3 โดยไม่ต้องค้นหา subexpressions ในวงเล็บ

ดูรายละเอียดman bashทั้งหมดtest exprได้ที่

สรุป: อัลกอริทึมการแยกวิเคราะห์ที่ใช้testมีความซับซ้อน ใช้นิพจน์ง่ายเท่านั้นและใช้เปลือกผู้ประกอบการ!, &&และ||จะรวมพวกเขา

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