ความแตกต่างระหว่างวงเล็บเหลี่ยมเดี่ยวและคู่ใน Bash


161

ฉันกำลังอ่านตัวอย่างทุบตีเกี่ยวกับifแต่บางตัวอย่างเขียนด้วยวงเล็บเหลี่ยมเดียว:

if [ -f $param ]
then
  #...
fi

อื่น ๆ ด้วยวงเล็บเหลี่ยมสองชั้น:

if [[ $? -ne 0 ]]
then
    start looking for errors in yourlog
fi

อะไรคือความแตกต่าง?


3
คุณสามารถรับคำตอบของคุณโดยดูที่คำตอบของคำถามนี้: unix.stackexchange.com/questions/3831/ …
Amir Naghizadeh

ดูเพิ่มเติมserverfault.com/questions/52034/…
pevik

คำตอบ:


188

Single []คือการทดสอบเงื่อนไขที่สอดคล้องกับ posix shell

Double [[]]เป็นส่วนขยายของมาตรฐาน[]และรองรับโดย bash และ shell อื่น ๆ (เช่น zsh, ksh) พวกเขาสนับสนุนการดำเนินงานพิเศษ (เช่นเดียวกับการดำเนินงาน posix มาตรฐาน) ตัวอย่างเช่น: ||แทน-oและการจับคู่ regex =~กับ รายการฟูลเลอร์ของความแตกต่างที่สามารถพบได้ในส่วนคู่มือการทุบตีในการสร้างเงื่อนไข

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


6
ฉันจะเพิ่มว่าถ้าสคริปต์ของคุณไม่ได้เริ่มต้นด้วย shebang ที่ร้องขอเชลล์ที่สนับสนุนอย่างชัดเจน[[ ]](เช่นทุบตีด้วย#!/bin/bashหรือ#!/usr/bin/env bash) คุณควรใช้ตัวเลือกแบบพกพา สคริปต์ที่สมมติว่า / bin / sh รองรับส่วนขยายเช่นนี้จะทำให้ระบบปฏิบัติการหยุดชะงักเช่น Debian และ Ubuntu รุ่นล่าสุดซึ่งไม่ได้เป็นอย่างนั้น
Gordon Davisson

87

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

ทดสอบใน 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 เทียบเท่า5
  • การแยกคำและการสร้างชื่อไฟล์ตามการขยาย (แยก + 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 4 , สูญเสียเวทย์ด้วย''
    • [[ 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เหมือนหรือ!(

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

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


3
" มี POSIX ที่เทียบเท่ากับการสร้าง [[]] ทุกอย่างที่ฉันเคยเห็น " สิ่งเดียวกันนี้สามารถพูดได้ในภาษาทัวริงที่สมบูรณ์บนใบหน้าของดาวเคราะห์
A. Rick

3
@ A.Rick ที่จะเป็นคำตอบที่ถูกต้องสำหรับทุกคน "วิธีการทำ X ในภาษา Y" ดังนั้นคำถาม :-) แน่นอนมี "ความสะดวก" โดยนัยในคำสั่งนั้น
Ciro Santilli 郝海东冠状病六四事件法轮功

6
สรุปที่ยอดเยี่ยม ขอบคุณสำหรับความพยายาม อย่างไรก็ตามฉันไม่เห็นด้วยกับข้อเสนอแนะ ความสะดวกในการพกพากับไวยากรณ์ที่สอดคล้องและทรงพลังยิ่งขึ้น หากคุณต้องการ bash> 4 ในสภาพแวดล้อมของคุณแนะนำให้ใช้ [[]] ไวยากรณ์
เบอร์นาร์ด

@Downvoters โปรดอธิบายเพื่อให้ฉันสามารถเรียนรู้และปรับปรุงข้อมูล :-)
Ciro Santilli 郝海东冠状病病六四事件法轮功

8
คำตอบที่ดี แต่ผมคิดว่าคำแนะนำ : เสมอใช้[]ควรอ่านเป็นตั้งค่าของฉันใช้งาน: ใช้[]ถ้าคุณไม่ต้องการที่จะสูญเสียการพกพา ตามที่ระบุไว้ที่นี่ : หากการพกพา / ความสอดคล้องกับ POSIX หรือ BourneShell เป็นเรื่องที่น่ากังวลควรใช้ไวยากรณ์เก่า หากในอีกทางหนึ่งสคริปต์ต้องการ BASH, Zsh หรือ KornShell ไวยากรณ์ใหม่มักจะมีความยืดหยุ่นมากขึ้น แต่ไม่จำเป็นต้องเข้ากันได้ย้อนหลัง ฉันควรจะไปด้วย[[ ab =~ ab? ]]ถ้าฉันสามารถและไม่ต้องกังวลเกี่ยวกับความเข้ากันได้ย้อนหลังกว่าprintf 'ab' | grep -Eq 'ab?'
MauricioRobayo

14

ภายในวงเล็บเดียวสำหรับการทดสอบเงื่อนไข (เช่น [... ]) ผู้ประกอบการบางรายเช่นเดียว=ได้รับการสนับสนุนโดยเชลล์ทั้งหมดในขณะที่การใช้ตัวดำเนินการ==ไม่ได้รับการสนับสนุนโดยเชลล์รุ่นเก่าบางตัว

ภายในวงเล็บสองชั้นสำหรับการทดสอบเงื่อนไข (เช่น [[... ]]) ไม่มีความแตกต่างระหว่างการใช้=หรือ==ในกระสุนเก่าหรือใหม่

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

if [ $var == "hello" ]; then

ถ้า $ var ว่างเปล่า / ว่างนี่คือสิ่งที่สคริปต์เห็น:

if [ == "hello" ]; then

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


1
ใส่คำพูดรอบทั้งหมดอ่านของตัวแปรจนกว่าคุณจะมีเหตุผลที่ดีมากที่จะไม่เป็นมากการปฏิบัติที่ดีกว่าการป้องกันการเข้ารหัสเพราะมันใช้กับทุกคนอ่านของตัวแปรที่ไม่เพียง แต่ผู้ที่อยู่ในเงื่อนไข ข้อผิดพลาดของตัวติดตั้ง iTunes ลบไฟล์ของผู้คนเมื่อชื่อฮาร์ดไดรฟ์มีช่องว่าง (หรืออะไรทำนองนั้น) นอกจากนี้ยังแก้ปัญหาที่คุณพูดถึง
Chai T. Rex


1

คุณสามารถใช้วงเล็บเหลี่ยมคู่สำหรับการจับคู่ regex แบบเบาเช่น:

if [[ $1 =~ "foo.*bar" ]] ; then

(ตราบใดที่เวอร์ชันของ bash ที่คุณใช้รองรับไวยากรณ์นี้)


6
ยกเว้นว่าคุณได้ยกรูปแบบดังนั้นตอนนี้จึงถือว่าเป็นสตริงตัวอักษร
ormaaj

จริงแท้แน่นอน. บางครั้งสิ่งนี้ทำให้ฉันรำคาญ :)
asf107

1

คู่มือทุบตีพูดว่า:

เมื่อใช้กับ [[ตัวดำเนินการ '<' และ '>' จะเรียงลำดับพจนานุกรมโดยใช้ภาษาปัจจุบัน คำสั่งทดสอบใช้การสั่งซื้อ ASCII

(คำสั่งทดสอบเหมือนกับ [])

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