การใช้วงเล็บ `() 'ในการกำหนดฟังก์ชันเชลล์คืออะไร


20

ฟังก์ชั่นถูกกำหนดเป็น:

do_something () {
    do it
}

ฉันสามารถเข้าใจชื่อ 'do_something' และวงเล็บปีกกาเพื่อใส่รหัสแอ็คชัน แต่ฉันไม่สามารถเข้าใจว่าจุดประสงค์ของ()ที่นี่คืออะไรเนื่องจากไม่มีพารามิเตอร์ที่มีชื่อในสคริปต์ Bash มันอาจจะดีกว่าและตรงไปตรงมาเพื่อกำหนดเป็น

do_something {
  do it
}

ซึ่งไม่ขัดแย้งกับไวยากรณ์ปัจจุบันและยิ่งประกาศว่าไม่มีพารามิเตอร์ที่มีชื่อ การใช้งานของ()ที่นี่คืออะไร?


คำตอบ:


44

หากไม่มี()ไวยากรณ์จะไม่ชัดเจน

จะต้องมีไวยากรณ์ที่ชัดเจนบางอย่างสำหรับการกำหนดฟังก์ชั่นและหากไม่มีการแก้ไขไวยากรณ์เชลล์อื่น ๆอย่างมีนัยสำคัญมันไม่สามารถเป็นเช่นนี้:

do_something {
    # one or more commands go here
}

คุณบอกว่า "ไม่ได้ทำผิดกับไวยากรณ์ปัจจุบัน" แต่มันไม่ได้! ขอให้สังเกตว่าคุณไม่ได้รับชนิดของไวยากรณ์ผิดพลาดใด ๆ เมื่อคุณพยายามที่จะเรียกบรรทัดแรกของที่ คุณได้รับข้อผิดพลาด แต่ไม่ใช่ข้อผิดพลาดเกี่ยวกับไวยากรณ์ บรรทัดที่สองด้วย}คือข้อผิดพลาดทางไวยากรณ์ แต่บรรทัดแรกไม่ใช่ ให้do_something {พยายามเรียกใช้คำสั่งที่เรียกdo_somethingและส่งผ่าน{เป็นอาร์กิวเมนต์ไปยังคำสั่งนั้นแทน:

$ do_something {
do_something: command not found

หากมีคำสั่งที่เรียกว่าdo_somethingคุณกำลังเรียกใช้ หากมีฟังก์ชั่นที่เรียกว่าdo_something, คุณเรียกมันว่า เป็นสิ่งสำคัญโดยทั่วไปว่าไวยากรณ์ไม่มีความกำกวม แต่ก็มีความสำคัญโดยเฉพาะอย่างยิ่งว่าเป็นไปได้ที่จะกำหนดฟังก์ชันใหม่โดยไม่ได้ตั้งใจเรียกมันแทน การกำหนดฟังก์ชั่นและการเรียกมันไม่ควรเหมือนกัน

วิธีถือว่าเปลือกและ{(

ตามที่type {จะบอกคุณ{คือคำหลักของเชลล์ [[นี้จะทำให้มันเหมือน หากใช้ในสถานการณ์ที่อาจเป็นคำสั่งให้{พกความหมายพิเศษ โดยเฉพาะมันทำการจัดกลุ่มคำสั่ง อย่างไรก็ตามในสถานการณ์อื่น ๆ อาจใช้ unescaped เพื่อแสดง{อักขระตามตัวอักษร ซึ่งรวมถึงสถานการณ์การส่งผ่านเป็นคำที่สองหรือต่อมาของคำสั่ง

แน่นอนว่า Bash นั้นได้รับการออกแบบมาเพื่อรักษาที่{แตกต่างไปจากปัจจุบัน อย่างไรก็ตามไวยากรณ์ของมันจะไม่เข้ากันได้กับ POSIX เชลล์อีกต่อไปและ Bash จะไม่ใช่เชลล์สไตล์แบบบอร์นจริงๆและจะไม่สามารถเรียกใช้เชลล์สคริปต์จำนวนมากได้

ในทางตรงกันข้าม(เป็นตัวบ่งชี้เชลล์ มันได้รับการปฏิบัติเสมอเป็นพิเศษถ้ามันปรากฏในคำสั่งและไม่ได้ยกมา (มี' ', " "หรือ\) ดังนั้นจึงไม่มีความกำกวมในไวยากรณ์:

do_something() {
    # one or more commands go here
}

นั่นไม่ได้หมายความว่าอย่างอื่น หาก Bash ไม่มีฟังก์ชั่นแสดงว่าเป็นข้อผิดพลาดทางไวยากรณ์เนื่องจากเหตุผลเดียวกันecho foo(bar)คือข้อผิดพลาดทางไวยากรณ์

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

สุดท้ายโปรดทราบว่าในขณะที่คำจำกัดความของฟังก์ชั่นส่วนใหญ่ใช้{ในทางปฏิบัติคำสั่งผสมใด ๆ ที่ได้รับอนุญาต หากคุณมีฟังก์ชั่นที่มีร่างกายที่คุณอยากที่จะทำงานใน subshell คุณสามารถใช้มากกว่า( ){ }


1
ในระดับที่สูงขึ้นมันเป็นเรื่องของความคาดหวังและความสามารถในการอ่านของมนุษย์ ในภาษาส่วนใหญ่โดยเฉพาะอย่างยิ่งใน C, fortran และอื่น ๆ ที่พบบ่อยในช่วงเวลาที่ภาษาสคริปต์ทุบตีได้รับการพัฒนาฟังก์ชั่น / รูทีนย่อยมักจะมีรายการอาร์กิวเมนต์อาจว่างเปล่าที่อยู่ในวงเล็บ เปลี่ยนกระบวนทัศน์นั้นและคุณทำให้ยากที่จะเข้าใจภาษา
jamesqf

15

()โทเค็นคือการบอกล่ามเปลือกที่คุณกำลังประกาศฟังก์ชั่น

$ do_something () { echo 'do it'; } ; do_something
do it

ทางเลือกในbashคือfunction

function do_something {
 echo 'do it'
}

หรือเป็นสายการบินเดียวที่คุณสามารถทดสอบ

$ bash -c "function do_something { echo 'do it'; } ; do_something"
do it

2
functionคำหลักคือ pre-POSIX ksh ลัทธิที่ทุบตีสนับสนุนสำหรับการทำงานร่วมกันหลัง; อย่างไรก็ตามทุบตีสนับสนุนมันไม่ดี (ไม่ทำให้ตัวแปรเริ่มต้นฟังก์ชั่นท้องถิ่นเป็น ksh เก่าในฟังก์ชั่นการประกาศในรูปแบบนั้น) ดังนั้นจึงไม่เหมาะสำหรับรหัสใหม่ ดูwiki.bash-hackers.org/scripting/obsolete
Charles Duffy

@CharlesDuffy ขอขอบคุณสำหรับคำอธิบายนี้ :-)
sudodus

1
@CharlesDuffy คุณกำลังบอกว่า ksh และ bash ยอมรับไวยากรณ์บางอย่างที่ทำให้ตัวแปรโลคัลเป็น ksh แต่เป็นโกลบอลใน bash หรือไม่? นั่นไม่ได้เสียงที่ถูกต้อง ความเข้าใจของฉันส่วนหนึ่งขึ้นอยู่กับการโพสต์และการตรวจสอบ (ใน ksh93) นั่นคือ ksh ทำให้ตัวแปรท้องถิ่นในสถานการณ์ที่น้อยกว่าทุบตีไม่มาก ใน bash typesetโดยไม่มี-gฟังก์ชั่นประกาศตัวแปรท้องถิ่นเสมอ ด้วยfunctionคีย์เวิร์ด, ตัวแปร bash และ ksh scope เหมือนกัน , พวกมันไม่?
Eliah Kagan

@EliahKagan ฉันคิดว่าสิ่งที่ Charles ได้รับการอ้างถึงได้รับการแสดงในคำตอบของ Gilleที่นี่
Sergiy Kolodyazhnyy

9

ช่างเป็นสไตล์ของคุณที่แท้จริงเพียงคนเดียว !

พิจารณารูปแบบการรั้งที่ดีอย่างสมบูรณ์แบบ:

foo
{
  ...
}
foo
  {
    ...
  }
foo
  while ...; do ...; done # Look, ma! No braces!
foo
( ... ) # Look, ma! No braces and a subshell to boot!

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


ในความคิดที่สองนั่นไม่ใช่ OTBS จริง ๆ เนื่องจากที่เดียวที่ OTBS อนุญาตให้ใช้เครื่องหมายปีกกาในบรรทัดใหม่สำหรับนิยามฟังก์ชัน
muru

3
ฉันคิดว่ามีข้อโต้แย้งที่จะทำให้คุณถูกต้องที่จะบอกว่ามันเป็น OTBS เพราะฟังก์ชั่นใน C และ C ++ นั้นแตกต่างจากฟังก์ชั่นใน C และ C ++ ซึ่งนิยามของฟังก์ชันในสคริปต์เชลล์สไตล์ Bourne สามารถซ้อนกันโดยตรงในเนื้อหาของนิยามฟังก์ชันอื่น ๆ ไม่สมควรที่จะประสบความสำเร็จ (หรืออาจเป็นสไตล์ที่นิยมในการเขียนโปรแกรม Java ที่วิธีการเปิดวงเล็บปีกกาของพวกเขาในบรรทัดเดียวกันมากกว่า OTBS) ทั้งสองวิธีคุณทำจุดที่ยอดเยี่ยมเกี่ยวกับว่าไวยากรณ์จะต้อง จำกัด ข้อ จำกัด ในการจัดตำแหน่งรั้งเพื่อทำงานอย่างไร ไม่มีเลย()หรืออะไรทำนองนั้น
Eliah Kagan
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.