Việc sử dụng dấu ngoặc đơn `()` trong định nghĩa hàm shell là gì?


20

Một hàm được định nghĩa là:

do_something () {
    do it
}

Tôi có thể hiểu tên của nó 'do_s Something' và dấu ngoặc nhọn để đóng gói mã hành động, nhưng không thể biết ý định của mục đích ()ở đây là gì, vì không có tham số được đặt tên trong các tập lệnh Bash. Có thể tốt hơn và đơn giản hơn để định nghĩa nó là

do_something {
  do it
}

Mà không xung đột với cú pháp hiện tại của nó, và thậm chí nhiều hơn tuyên bố rằng không có tham số được đặt tên. Cách sử dụng ()ở đây là gì?


Câu trả lời:


44

Nếu không có (), cú pháp thực sự sẽ mơ hồ.

Phải có một số cú pháp rõ ràng để xác định hàm và không thay đổi đáng kể cú pháp shell khác , nó không thể là:

do_something {
    # one or more commands go here
}

Bạn đã nói điều này "không kết luận với cú pháp hiện tại của nó", nhưng thực tế là vậy! Lưu ý rằng bạn không gặp phải bất kỳ loại lỗi cú pháp nào khi bạn cố chạy dòng đầu tiên . Bạn gặp lỗi, nhưng đó không phải là lỗi về cú pháp. Dòng thứ hai, với }, là một lỗi cú pháp, nhưng dòng đầu tiên thì không. Thay vào đó, do_something {cố gắng chạy một lệnh được gọi do_somethingvà chuyển {như một đối số cho lệnh đó:

$ do_something {
do_something: command not found

Nếu đã có một lệnh được gọi do_something, bạn đang chạy nó. Nếu đã có một hàm được gọi do_something, bạn đang gọi nó . Nói chung, điều quan trọng là cú pháp không rõ ràng, nhưng điều đặc biệt quan trọng là có thể xác định lại hàm mà không vô tình gọi nó thay thế. Xác định một hàm và gọi nó không giống nhau.

Làm thế nào vỏ xử lý {(.

Như type {sẽ cho bạn biết, {là một từ khóa shell. Điều này làm cho nó thích [[. Nếu được sử dụng trong một tình huống mà nếu không nó sẽ là một lệnh, {mang theo ngữ nghĩa đặc biệt. Cụ thể, nó thực hiện nhóm lệnh. Tuy nhiên, trong các tình huống khác, nó có thể được sử dụng không được giải thích để biểu thị một {ký tự theo nghĩa đen . Điều này bao gồm cả tình huống chuyển nó dưới dạng từ thứ hai hoặc tiếp theo của lệnh.

Tất nhiên, Bash có thể đã được thiết kế để đối xử {khác với hiện tại. Tuy nhiên, cú pháp của nó sau đó sẽ không còn tương thích với trình bao POSIX và Bash sẽ không thực sự là trình bao kiểu Bourne và sẽ không thể chạy nhiều tập lệnh shell.

Ngược lại, (là một metacharacter vỏ. Nó luôn luôn đối xử đặc biệt nếu nó xuất hiện trong một lệnh và không được trích dẫn (với ' ', " "hoặc \). Do đó, không có sự mơ hồ trong cú pháp:

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

Điều đó không có nghĩa gì khác. Nếu Bash không có chức năng, thì đó sẽ là lỗi cú pháp, vì lý do tương tự echo foo(bar)là lỗi cú pháp.

Nếu bạn thực sự không thích ()ký hiệu thì bạn có thể sử dụng từ khóa functionvà bỏ qua nó, như sudodus đề cập . Lưu ý rằng đây không phải là một phần của cú pháp để xác định các hàm trong hầu hết các shell kiểu Bourne khác - và trong một số, nó được hỗ trợ nhưng các hàm được định nghĩa theo cách đó có ngữ nghĩa khác nhau - và do đó, một tập lệnh sử dụng nó sẽ không thể di động được. (Lý do cú pháp này có thể rõ ràng là functionchính nó là một từ khóa trong Bash biểu thị rằng bất cứ điều gì theo sau nó là sự khởi đầu của một định nghĩa hàm.)

Cuối cùng, lưu ý rằng trong khi hầu hết các định nghĩa hàm sử dụng {trong thực tế, bất kỳ lệnh ghép nào đều được phép. Nếu bạn có một chức năng mà cơ thể bạn muốn luôn luôn chạy trong một lớp con, bạn có thể sử dụng ( )chứ không phải { }.


1
Ở cấp độ cao hơn, đó là về kỳ vọng và khả năng đọc của con người. Trong hầu hết các ngôn ngữ, đặc biệt là C, fortran và các ngôn ngữ khác phổ biến vào thời điểm ngôn ngữ bash script được phát triển, một hàm / chương trình con luôn có một danh sách đối số, có thể trống, được đặt trong dấu ngoặc đơn. Thay đổi mô hình đó, và bạn làm cho việc hiểu ngôn ngữ trở nên khó khăn hơn.
jamesqf

15

()thông báo là để thông báo cho trình thông dịch shell rằng bạn đang khai báo một hàm.

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

Một thay thế trong bashfunction

function do_something {
 echo 'do it'
}

hoặc như một lớp lót bạn có thể kiểm tra

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

2
Các functiontừ khóa là một pre-POSIX ksh-ism mà hỗ trợ bash để tương thích ngược; tuy nhiên, bash hỗ trợ nó rất tệ (không tạo các biến mặc định theo hàm cục bộ, như ksh cũ đã làm trong các hàm được khai báo ở dạng đó). Do đó, nó không lý tưởng cho mã mới. Xem wiki.bash-hackers.org/scripting/obsolete
Charles Duffy

@CharlesDuffy, Cảm ơn lời giải thích này :-)
sudodus

1
@CharlesDuffy Bạn đang nói ksh và bash chấp nhận một số cú pháp tạo một biến cục bộ trong ksh nhưng toàn cầu trong bash? Điều đó không đúng. Sự hiểu biết của tôi, một phần dựa trên bài đăng đókiểm tra (trong ksh93), là ksh làm cho các biến cục bộ trong ít tình huống hơn bash, không bao giờ nhiều hơn. Trong bash, typesetkhông có -gtrong hàm luôn khai báo một biến cục bộ. Với các functionbiến phạm vi từ khóa, bash và ksh theo cùng một cách , phải không?
Eliah Kagan

@EliahKagan Tôi nghĩ những gì Charles đã đề cập đến đã được thể hiện trong câu trả lời của Gille tại đây
Sergiy Kolodyazhnyy

9

Làm thế nào rất một-đúng-phong cách -ist -ist của bạn!

Xem xét khác, phong cách niềng răng hoàn hảo tốt:

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

Làm thế nào shell có thể nói rằng đó là các định nghĩa hàm và không chỉ đơn giản là một lệnh footheo sau danh sách lệnh? Trong tất cả các trường hợp này, một yếu tố định hướng bổ sung, như ()hoặc functiontừ khóa là cần thiết.


Theo suy nghĩ thứ hai, đó không thực sự là OTBS, vì một nơi mà OTBS cho phép niềng răng trên một dòng mới là dành cho các định nghĩa hàm.
muru

3
Tôi nghĩ rằng có một lập luận được đưa ra rằng bạn đã đúng khi mô tả nó là OTBS, bởi vì không giống như các hàm trong C và C ++, một định nghĩa hàm trong tập lệnh shell kiểu Bourne có thể được lồng trực tiếp vào phần thân của định nghĩa hàm khác và do đó có thể tranh cãi không xứng đáng được phân biệt. (Hoặc có thể đây là phong cách phổ biến trong lập trình Java, nơi các phương thức có được dấu ngoặc mở trên cùng một dòng, thay vì OTBS.) Dù sao đi nữa, bạn đưa ra một điểm tuyệt vời về cách cú pháp phải áp đặt các ràng buộc chặt chẽ đối với vị trí dấu ngoặc để hoạt động hoàn toàn không có ()hoặc một cái gì đó giống như nó.
Eliah Kagan
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.