วงเล็บสองสี่เหลี่ยม [[]] ดีกว่าวงเล็บเหลี่ยมเดี่ยว [] ใน Bash หรือไม่?


574

เพื่อนร่วมงานอ้างสิทธิ์เมื่อเร็ว ๆ นี้ในการตรวจสอบโค้ดว่าสิ่ง[[ ]]ก่อสร้างนั้นเป็นที่ต้องการมากกว่า[ ]ในงานสร้างเช่น

if [ "`id -nu`" = "$someuser" ] ; then 
     echo "I love you madly, $someuser"
fi

เขาไม่สามารถให้เหตุผลได้ มีไหม


19
มีความยืดหยุ่นบางครั้งให้ตัวเองให้ฟังคำแนะนำโดยไม่ต้องมีคำอธิบายลึก :) สำหรับ[[กับมันรหัสเป็นสิ่งที่ดีและชัดเจน แต่จำวันนั้นเมื่อคุณจะพอร์ต scriptworks ของคุณในระบบที่มีเปลือกเริ่มต้นซึ่งไม่ได้bashหรือksh, ฯลฯ[เป็นสิ่งที่น่าเกลียดน่ารำคาญ แต่ทำงานได้AK-47ในทุกสถานการณ์
rook

178
@rook คุณสามารถฟังคำแนะนำโดยไม่ต้องอธิบายอย่างแน่นอน แต่เมื่อคุณขอคำอธิบายและไม่เข้าใจมันมักจะเป็นธงสีแดง "เชื่อ แต่ยืนยัน" และทุกอย่าง
Josip Rodin


1
@rook ในคำอื่น ๆ "ทำสิ่งที่คุณกำลังบอกและไม่ได้ถามคำถาม"
สัญลักษณ์

@rook นั่นทำให้รู้สึกไม่
Rakib

คำตอบ:


607

[[มีความประหลาดใจน้อยลงและโดยทั่วไปจะปลอดภัยกว่าการใช้งาน แต่มันไม่สามารถพกพาได้ - POSIX ไม่ได้ระบุสิ่งที่มันทำและมีเพียงหอยบางตัวเท่านั้นที่สนับสนุน (นอกเหนือจาก bash ฉันได้ยิน ksh ก็สนับสนุนเช่นกัน) ตัวอย่างเช่นคุณสามารถทำได้

[[ -e $b ]]

เพื่อทดสอบว่ามีไฟล์อยู่หรือไม่ แต่ด้วยความที่[คุณต้องอ้าง$bเพราะมันแยกอาร์กิวเมนต์และขยายสิ่งที่ชอบ"a*"(ที่[[ใช้มันตัวอักษร) สิ่งนั้นเกี่ยวข้องกับวิธีที่[จะเป็นโปรแกรมภายนอกและรับอาร์กิวเมนต์โดยปกติเหมือนกับโปรแกรมอื่น ๆ ทุกตัว (แม้ว่ามันจะเป็น builtin แต่ก็ยังไม่มีการจัดการพิเศษนี้)

[[นอกจากนี้ยังมีคุณสมบัติที่ดีอื่น ๆ เช่นการจับคู่นิพจน์ทั่วไป=~พร้อมกับตัวดำเนินการเหมือนกับที่รู้จักในภาษา C-like นี่คือหน้าดีเกี่ยวกับมัน: อะไรคือความแตกต่างระหว่างการทดสอบ[และ[[? และการทดสอบทุบตี


21
เมื่อพิจารณาว่าทุบตีอยู่ทุกที่ในวันนี้ฉันมักจะคิดว่ามันพกพาสวยไอ้บ้า ข้อยกเว้นทั่วไปเพียงอย่างเดียวสำหรับฉันคือบนแพลตฟอร์ม busybox - แต่คุณอาจต้องการใช้ความพยายามเป็นพิเศษสำหรับเรื่องนี้เนื่องจากฮาร์ดแวร์ที่ทำงานอยู่
ปืน

14
@ guns: แน่นอน ฉันขอแนะนำว่าประโยคที่สองของคุณหักล้างประโยคแรก หากคุณพิจารณาว่าการพัฒนาซอฟต์แวร์โดยรวมแล้ว "bash อยู่ทุกหนทุกแห่ง" และ "ข้อยกเว้นคือแพลตฟอร์ม busybox" เข้ากันไม่ได้โดยสิ้นเชิง busybox เป็นที่แพร่หลายสำหรับการพัฒนาแบบฝัง
Lightness Races ในวงโคจร

11
@ guns: แม้ว่าจะมีการติดตั้ง bash ไว้ในกล่องของฉัน แต่มันอาจไม่ได้รันสคริปต์ มีความแปลกประหลาดเพิ่มเติมเป็นกับ Busybox เกินไป: มีตัวเลือกการรวบรวมมาตรฐานในปัจจุบันก็จะแยกวิเคราะห์แต่ตีความว่าเป็นความหมายเดียวกับ[[ ]] [ ]
dubiousjim

59
@dubiousjim: หากคุณใช้การสร้าง bash-only (เช่นนี้) คุณควรมี #! / bin / bash ไม่ใช่ #! / bin / sh
Nick Matteo

16
นั่นคือเหตุผลที่ฉันทำให้สคริปต์ของฉันใช้#!/bin/shแต่จากนั้นสลับไปใช้#!/bin/bashทันทีที่ฉันใช้คุณลักษณะเฉพาะของ BASH เพื่อแสดงว่าไม่มี Bourne shell แบบพกพาอีกต่อไป
แอนโธนี

151

ความแตกต่างของพฤติกรรม

ความแตกต่างบางประการใน Bash 4.3.11:

  • POSIX vs Bash extension:

  • คำสั่งปกติกับเวทมนตร์

    • [ เป็นเพียงคำสั่งปกติที่มีชื่อแปลก ๆ

      ]เป็นเพียงข้อโต้แย้ง[ที่ป้องกันไม่ให้มีการใช้ข้อโต้แย้งเพิ่มเติม

      จริง ๆ แล้ว Ubuntu 16.04 นั้นสามารถใช้งานได้จริง/usr/bin/[โดยcoreutilsแต่ bash ในตัวจะมีความสำคัญมากกว่า

      ไม่มีการเปลี่ยนแปลงในวิธีที่ Bash วิเคราะห์คำสั่ง

      โดยเฉพาะอย่างยิ่ง<คือการเปลี่ยนเส้นทาง&&และ||เชื่อมคำสั่งหลาย ๆ คำสั่ง( )สร้าง subshells เว้นแต่จะหลบหนีไป\และการขยายคำจะเกิดขึ้นตามปกติ

    • [[ X ]]เป็นโครงสร้างเดียวที่ทำให้การXแยกวิเคราะห์อย่างน่าอัศจรรย์ <, &&, ||และ()ได้รับการปฏิบัติเป็นพิเศษและกฎการแยกคำที่แตกต่างกัน

      นอกจากนี้ยังมีความแตกต่างต่อไปเหมือนและ==~

      ใน Bashese: [เป็นคำสั่งในตัวและ[[เป็นคำหลัก: /ubuntu/445749/whats-the-difference-between-shell-builtin-and-shell-keyword

  • <

  • && และ ||

    • [[ a = a && b = b ]]: จริงตรรกะและ
    • [ a = a && b = b ]: ข้อผิดพลาดทางไวยากรณ์&&แยกวิเคราะห์เป็นตัวคั่นคำสั่ง ANDcmd1 && cmd2
    • [ a = a -a b = b ]: เทียบเท่า แต่เลิกใช้แล้วโดยPOSIX³
    • [ a = a ] && [ b = b ]: POSIX และเทียบเท่าที่เชื่อถือได้
  • (

    • [[ (a = a || a = b) && a = b ]]: false
    • [ ( a = a ) ]: ข้อผิดพลาดทางไวยากรณ์()ถูกตีความว่าเป็น subshell
    • [ \( a = a -o a = b \) -a a = b ]: เทียบเท่า แต่()เลิกใช้แล้วโดย POSIX
    • { [ a = a ] || [ a = b ]; } && [ a = b ] POSIX ที่เทียบเท่า⁵
  • การแยกคำและการสร้างชื่อไฟล์ตามการขยาย (แยก + glob)

    • x='a b'; [[ $x = 'a b' ]]: จริงไม่จำเป็นต้องใส่เครื่องหมายคำพูด
    • x='a b'; [ $x = 'a b' ]: ข้อผิดพลาดทางไวยากรณ์ขยายเป็น [ a b = 'a b' ]
    • x='*'; [ $x = 'a b' ]: ข้อผิดพลาดทางไวยากรณ์หากมีมากกว่าหนึ่งไฟล์ในไดเรกทอรีปัจจุบัน
    • x='a b'; [ "$x" = 'a b' ]: เทียบเท่า POSIX
  • =

    • [[ ab = a? ]]: จริงเพราะมันจับคู่รูปแบบ ( * ? [เป็นเวทมนตร์) ห้าม glob ขยายไฟล์ในไดเรกทอรีปัจจุบัน
    • [ ab = a? ]: a?glob ขยายตัว ดังนั้นอาจเป็นจริงหรือเท็จขึ้นอยู่กับไฟล์ในไดเรกทอรีปัจจุบัน
    • [ ab = a\? ]: เท็จไม่ใช่การขยาย glob
    • =และ==เหมือนกันทั้งใน[และ[[, แต่==เป็นส่วนขยายของ Bash
    • case ab in (a?) echo match; esac: เทียบเท่า POSIX
    • [[ ab =~ 'ab?' ]]: false⁴, สูญเสียเวทย์ด้วย ''
    • [[ ab? =~ 'ab?' ]]: จริง
  • =~

    • [[ ab =~ ab? ]]: จริง POSIX ขยายการจับคู่การแสดงออกปกติ?ไม่ขยาย
    • [ a =~ a ]: ข้อผิดพลาดทางไวยากรณ์ ไม่เทียบเท่าทุบตี
    • printf 'ab\n' | grep -Eq 'ab?': เทียบเท่า POSIX (ข้อมูลบรรทัดเดียวเท่านั้น)
    • awk 'BEGIN{exit !(ARGV[1] ~ ARGV[2])}' ab 'ab?': เทียบเท่า POSIX

คำแนะนำ : []มักจะใช้

มี POSIX ที่เทียบเท่าสำหรับการ[[ ]]สร้างทุกครั้งที่ฉันเห็น

ถ้าคุณใช้[[ ]]คุณ:

  • สูญเสียการพกพา
  • บังคับให้ผู้อ่านเรียนรู้ความซับซ้อนของส่วนขยาย bash อื่น [เป็นเพียงคำสั่งปกติที่มีชื่อแปลก ๆ ไม่มีความหมายพิเศษเกี่ยวข้อง

¹แรงบันดาลใจจากโครงสร้างที่เทียบเท่า[[...]]ใน Korn เชลล์

² แต่ล้มเหลวสำหรับบางค่าของaหรือb(เช่น+หรือindex) และทำการเปรียบเทียบตัวเลขถ้าaและbมีลักษณะเป็นจำนวนเต็มทศนิยม expr "x$a" '<' "x$b"ทำงานได้ทั้งสองอย่าง

³และยังล้มเหลวสำหรับค่าของบางส่วนaหรือbเหมือนหรือ!(

⁴ใน bash 3.2 ขึ้นไปและไม่สามารถเปิดใช้งานความเข้ากันได้กับ bash 3.1 (เช่นเดียวกับBASH_COMPAT=3.1)

⁵ถึงแม้ว่าการจัดกลุ่ม (ที่นี่พร้อมกับ{...;}กลุ่มคำสั่งแทนที่จะ(...)เป็นการเรียกใช้ subshell ที่ไม่จำเป็น) ไม่จำเป็นเนื่องจากตัวดำเนินการ||และ&&ตัวดำเนินการเชลล์ (ตรงข้ามกับตัวดำเนินการ||และ&& [[...]]ตัวดำเนินการหรือตัวดำเนินการ-o/ / -a [) ดังนั้น[ a = a ] || [ a = b ] && [ a = b ]จะเทียบเท่า


4
คำอธิบายที่ดี ฉันใช้[เหตุผลเดียวกัน
Gordon

2
ใน Bashese :) ฮ่า ความชัดเจนที่ดีของความแตกต่างและเหตุผลที่ต้องยึดติดกับ POSIX
Jonathan Komar

56

[[ ]]มีคุณสมบัติมากขึ้น - ผมขอแนะนำให้คุณลองดูที่เป็นขั้นสูงทุบตี Scripting คู่มือสำหรับข้อมูลเพิ่มเติมโดยเฉพาะคำสั่งทดสอบการขยายส่วนในบทที่ 7 การทดสอบ

อนึ่งในฐานะบันทึก[[ ]]แนะนำถูกนำมาใช้ใน ksh88 (เวอร์ชัน 1988 ของเชลล์ Korn)


5
นี่อยู่ไกลจากการทุบตีมันเป็นครั้งแรกในเปลือก Korn
Henk Langeveld

6
@ โทมัส, ABS ถือว่าจริงอ้างอิงที่น่าสงสารมากในหลาย ๆ วงการ; ในขณะที่มันมีข้อมูลที่ถูกต้องอย่างมาก แต่ก็มีแนวโน้มที่จะระมัดระวังเล็กน้อยเพื่อหลีกเลี่ยงการแสดงวิธีปฏิบัติที่ไม่ดีในตัวอย่างของมันและใช้เวลาส่วนใหญ่ในชีวิตของมันไป
Charles Duffy

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

9
@Thomas, the Wooledge BashFAQ และ wiki ที่เกี่ยวข้องเป็นสิ่งที่ฉันใช้ พวกเขาได้รับการบำรุงรักษาอย่างแข็งขันโดยพลเมืองของช่อง Freenode #bash (ซึ่งบางครั้งมีหนามมักจะสนใจอย่างมากเกี่ยวกับความถูกต้อง) BashFAQ # 31, mywiki.wooledge.org/BashFAQ/031เป็นหน้าเว็บที่เกี่ยวข้องโดยตรงกับคำถามนี้
Charles Duffy

13

จากเครื่องมือเปรียบเทียบการทดสอบตัวยึดหรือตัวยึดคู่ใดเร็วที่สุด? ( http://bashcurescancer.com )

วงเล็บคู่เป็น“ คำสั่งผสม” โดยที่การทดสอบและวงเล็บเดี่ยวเป็นเชลล์ในตัว (และในความเป็นจริงคือคำสั่งเดียวกัน) ดังนั้นวงเล็บเดี่ยวและวงเล็บคู่รันรหัสที่แตกต่างกัน

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


7
อะไรคือความหลงใหลในสคริปต์เชลล์ที่เร็วที่สุด ? ฉันต้องการพกพามากที่สุดและไม่สนใจการปรับปรุงที่[[จะนำมาซึ่ง แต่แล้วฉันเป็นโรงเรียนเก่าผายลมเก่า :-)
Jens

1
ฉันคิดว่า shells จำนวนมากพิสูจน์รุ่นบิวด์อิน[และtestแม้ว่าเวอร์ชั่นภายนอกก็มีอยู่
dubiousjim

4
@Jens โดยทั่วไปฉันเห็นด้วย: จุดประสงค์ทั้งหมดของสคริปต์คือ (คือ?) พกพาได้ (ไม่เช่นนั้นเราจะเขียนโค้ดและคอมไพล์ไม่ใช่สคริปต์) ... ทั้งสองข้อยกเว้นที่ฉันคิดได้คือ: (1) แท็บเสร็จสมบูรณ์ สคริปต์ที่สมบูรณ์สามารถใช้เวลานานมากด้วยตรรกะตามเงื่อนไขจำนวนมาก); และ (2) super-prompts ( PS1=...crazy stuff...และ / หรือ$PROMPT_COMMAND) สำหรับสิ่งเหล่านี้ฉันไม่ต้องการให้เกิดความล่าช้าในการดำเนินการของสคริปต์
ไมเคิล

1
ความลุ่มหลงกับความรวดเร็วเป็นบางลักษณะ ทุกอย่างเท่าเทียมกันทำไมไม่รวมโครงสร้างโค้ดที่มีประสิทธิภาพมากขึ้นเข้ากับสไตล์เริ่มต้นของคุณโดยเฉพาะถ้าโครงสร้างนั้นมีการอ่านง่ายขึ้น? เท่าที่พกพาได้งานที่ดีหลายอย่างที่เหมาะกับการทุบตีนั้นไม่สามารถพกพาได้ เช่นฉันต้องวิ่งapt-get updateถ้ามันผ่านไปนานกว่า X ชั่วโมงนับตั้งแต่วิ่งครั้งสุดท้าย มันเป็นความโล่งใจที่ยอดเยี่ยมเมื่อหนึ่งสามารถออกจากการพกพาออกจากรายการข้อ จำกัด สำหรับรหัสยาวเกินไปแล้ว
Ron Burk

5

หากคุณกำลังทำตามคู่มือสไตล์ของ Google :

ทดสอบ[และ[[

[[ ... ]]ลดข้อผิดพลาดเนื่องจากไม่มีการขยายชื่อพา ธ หรือการแยกคำที่เกิดขึ้นระหว่าง[[และ]]และ[[ ... ]]ช่วยให้การจับคู่นิพจน์ปกติที่[ ... ]ไม่

# This ensures the string on the left is made up of characters in the
# alnum character class followed by the string name.
# Note that the RHS should not be quoted here.
# For the gory details, see
# E14 at https://tiswww.case.edu/php/chet/bash/FAQ
if [[ "filename" =~ ^[[:alnum:]]+name ]]; then
  echo "Match"
fi

# This matches the exact pattern "f*" (Does not match in this case)
if [[ "filename" == "f*" ]]; then
  echo "Match"
fi

# This gives a "too many arguments" error as f* is expanded to the
# contents of the current directory
if [ "filename" == f* ]; then
  echo "Match"
fi

1
Google เขียนว่า " ไม่มีการขยายชื่อพา ธ ... เกิดขึ้น " แต่[[ -d ~ ]]จะคืนค่าจริง (ซึ่งหมายถึง~การขยาย/home/user) ฉันคิดว่า Google ควรมีความแม่นยำมากกว่าในการเขียน
JamesThomasMoon1979

2

สถานการณ์ทั่วไปที่คุณไม่สามารถใช้งานได้[[คือสคริปต์ autotools configure.ac มีเครื่องหมายวงเล็บมีความหมายพิเศษและแตกต่างกันดังนั้นคุณจะต้องใช้testแทน[หรือ[[- โปรดสังเกตว่าการทดสอบ[นั้นเป็นโปรแกรมเดียวกัน


2
เครื่องมืออัตโนมัติที่ให้ไม่ใช่เปลือก POSIX ทำไมคุณ[ถึงคาดว่าจะถูกกำหนดให้เป็นฟังก์ชัน POSIX เชลล์
Gordon

เนื่องจากสคริปต์ autoconf ดูเหมือนเชลล์สคริปต์และสร้างเชลล์สคริปต์และคำสั่งเชลล์ส่วนใหญ่จะทำงานภายใน
vy32

-1

[[]] วงเล็บสองชั้นไม่ถูกคัดออกภายใต้รุ่น SunOS บางรุ่นและไม่มีการเรียงลำดับทั้งหมดภายในการประกาศฟังก์ชันโดย: GNU ทุบตีรุ่น 2.02.0 (1) - ปล่อย (sparc-sun-solaris2.6)


1
จริงมากและไม่ใช่เรื่องไม่สำคัญเลย ต้องพิจารณาการพกพา bash ในเวอร์ชันที่เก่ากว่า ผู้คนพูดว่า "bash เป็นที่แพร่หลายและพกพาได้ยกเว้น(อาจใส่ระบบปฏิบัติการลึกลับที่นี่) " - แต่จากประสบการณ์ของฉัน Solaris เป็นหนึ่งในแพลตฟอร์มเหล่านั้นที่ต้องให้ความสนใจเป็นพิเศษกับการพกพา: ไม่เพียงพิจารณารุ่นเก่าทุบตี ระบบปฏิบัติการรุ่นใหม่ปัญหา / ข้อบกพร่อง w / อาร์เรย์ฟังก์ชัน ฯลฯ แต่แม้แต่ยูทิลิตี้ (ใช้ในสคริปต์) เช่น tr, sed, awk, tar มีความแปลกและลักษณะเฉพาะบนโซลาริสที่คุณต้องทำงาน
ไมเคิล

2
คุณพูดถูก ... ยูทิลิตี้มากมายที่ไม่ใช่ POSIX บน Solaris เพียงแค่ดูที่เอาต์พุต "df" และการขัดแย้ง ... อัปยศบนซัน หวังว่ามันจะหายไปทีละน้อย (ยกเว้นในแคนาดา)
เก็บขยะ

7
Solaris 2.6 จริงจังหรือไม่ เปิดตัวในปี 1997 และสิ้นสุดการสนับสนุนในปี 2549 ฉันเดาว่าถ้าคุณยังใช้อยู่คุณก็มีปัญหาอื่นอีก! อนึ่งมันใช้ Bash v2.02 ซึ่งเป็นระบบที่แนะนำให้ใช้วงเล็บสองชั้นดังนั้นจึงควรใช้งานได้กับบางอย่างที่เก่าแก่ที่สุด Solaris 10 จากปี 2005 ใช้ Bash 3.2.51 และ Solaris 11 จาก 2011 ใช้ Bash 4.1.11
peterh

1
Re พกพาสคริปต์ บนระบบ Linux มีเครื่องมือแต่ละรุ่นโดยทั่วไปเท่านั้นและนั่นคือรุ่น GNU บน Solaris โดยทั่วไปคุณมีตัวเลือกระหว่าง Solaris-native edition หรือ GNU edition (พูด Solaris tar vs GNU tar) หากคุณขึ้นอยู่กับส่วนขยายเฉพาะของ GNU คุณต้องระบุว่าในสคริปต์ของคุณเพื่อให้สามารถพกพาได้ บนโซลาริสคุณทำได้โดยเติมคำนำหน้าด้วย "g" เช่น `ggrep` ถ้าคุณต้องการ grep GNU
peterh

-2

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


8
ทดสอบและ [เป็นชื่อสำหรับคำสั่ง builtin เดียวกันในทุบตี ลองใช้type [ดูสิ่งนี้
AB

1
@alberge นั้นเป็นจริง แต่[[แตกต่างจาก[เป็นไวยากรณ์ตีความโดยล่ามบรรทัดคำสั่ง type [[ในทุบตีลองพิมพ์ unix4linux ถูกต้องที่แม้ว่า[การทดสอบBourne-shell แบบคลาสสิกจะแยกกระบวนการใหม่เพื่อกำหนดค่าความจริง[[ไวยากรณ์ (ยืมจาก ksh โดย bash, zsh ฯลฯ ) ไม่ได้
Tim Gilbert

2
@ เวลาฉันไม่แน่ใจว่าเชลล์ Bourne ตัวใดที่คุณกำลังพูดถึง แต่[มีอยู่แล้วใน Bash และ Dash ( /bin/shในดิสทริบิวชั่นลีนุกซ์จาก Debian ทั้งหมด)
AB

1
โอ้ฉันเห็นสิ่งที่คุณหมายถึงมันเป็นเรื่องจริง ฉันกำลังคิดถึงสิ่งที่ต้องการพูด / bin / sh บนระบบ Solaris หรือ HP / UX ที่เก่ากว่า แต่แน่นอนถ้าคุณต้องการให้เข้ากันได้กับสิ่งที่คุณไม่ได้ใช้ [[เช่นกัน
Tim Gilbert

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