Tại sao `[` một shell dựng và` [[`một từ khóa shell?


64

Theo như tôi biết, [[là một phiên bản nâng cao của [, nhưng tôi bối rối khi tôi xem đó [[là một từ khóa và [được hiển thị dưới dạng dựng sẵn.

[root@server ~]# type [
[ is a shell builtin
[root@server ~]# type [[
[[ is a shell keyword

TLDP nói

Nội dung có thể là từ đồng nghĩa với lệnh hệ thống cùng tên, nhưng Bash tái hiện nó bên trong. Ví dụ, lệnh Bash echo không giống với / bin / echo, mặc dù hành vi của chúng gần như giống hệt nhau.

Từ khóa là một từ dành riêng, mã thông báo hoặc toán tử. Từ khóa có ý nghĩa đặc biệt với shell và thực sự là các khối xây dựng theo cú pháp của shell. Như ví dụ, cho, trong khi, làm, và! là những từ khóa. Tương tự như một nội trang, một từ khóa được mã hóa cứng vào Bash, nhưng không giống như một nội dung, một từ khóa không phải là một lệnh, mà là một tiểu đơn vị của một cấu trúc lệnh. [2]

Không nên làm cả hai [[[một từ khóa? Có bất cứ điều gì mà tôi đang thiếu ở đây? Ngoài ra, liên kết này xác nhận lại rằng cả hai [[[nên thuộc cùng một loại.



9
/ bin / [tồn tại trên máy của tôi.
Joshua

2
Như một cuộc biểu tình đơn giản của một sự khác biệt giữa hai: if "[" $x -eq 3 ]làm việc như mong đợi (vì Bash tìm kiếm lệnh gọi [, và điều này tồn tại), nhưng if "[[" $x -eq 3 ]]không không làm việc (vì một lần nữa Bash tìm kiếm một lệnh của tên thích hợp, nhưng không có [[chỉ huy).
Kyle Strand

1
@Joshua cũng vậy /usr/bin/echo, nhưng điều đó không có nghĩa là nó không phải là nội dung .
Jonathon Reinhart

Tất cả các bulitin cũng tồn tại các đối số phân tích bên ngoài nếu chúng không phải là nội dung.
Joshua

Câu trả lời:


80

Sự khác biệt giữa [[[là khá cơ bản.

  • [là một mệnh lệnh. Các đối số của nó được xử lý theo cách mà bất kỳ đối số lệnh nào khác được xử lý. Ví dụ: xem xét:

    [ -z $name ]

    Shell sẽ mở rộng $namevà thực hiện cả việc tách từ và tạo tên tệp trên kết quả, giống như đối với bất kỳ lệnh nào khác.

    Ví dụ, những điều sau đây sẽ thất bại:

    $ name="here and there"
    $ [ -n $name ] && echo not empty
    bash: [: too many arguments
    

    Để có tác phẩm này chính xác, trích dẫn là cần thiết:

    $ [ -n "$name" ] && echo not empty
    not empty
    
  • [[là một từ khóa shell và các đối số của nó được xử lý theo các quy tắc đặc biệt. Ví dụ: xem xét:

    [[ -z $name ]]

    Shell sẽ mở rộng $namenhưng, không giống như bất kỳ lệnh nào khác, nó sẽ thực hiện cả việc tách từ và tạo tên tệp trên kết quả. Ví dụ: các mục sau sẽ thành công mặc dù các khoảng trắng được nhúng trong name:

    $ name="here and there"
    $ [[ -n $name ]] && echo not empty
    not empty
    

Tóm lược

[ là một lệnh và tuân theo các quy tắc giống như tất cả các lệnh khác mà shell thực thi.

Bởi vì [[là một từ khóa, không phải là một lệnh, tuy nhiên, shell xử lý nó đặc biệt và nó hoạt động theo các quy tắc rất khác nhau.


+1. Cảm ơn. Bạn có thể cung cấp nguồn (tham khảo) cho theo quy tắc nào một lệnh và từ khóa hoạt động?
Tim

@Tim Các quy tắc theo đó các lệnh hoạt động được chi tiết trong man bash. Đặc biệt, hãy xem các phần có tiêu đề "MỞ RỘNG QUYỀN ĐƠN GIẢN" và "THỰC HIỆN QUY ĐỊNH". Ngoài [[, bash khác từ khóa bao gồm if, then, while, và 'trường hợp'. Không có quy tắc chung cho các từ khóa: mỗi từ khóa là một trường hợp đặc biệt. man bash bao gồm các chi tiết cho mỗi.
John1024

62

Trong V7 Unix - nơi vỏ Bourne xuất hiện lần đầu tiên - [được gọi testvà nó chỉ tồn tại như /bin/test. Vì vậy, mã bạn sẽ viết ngày hôm nay là:

if [ "$foo" = "bar" ] ; then ...

thay vào đó bạn sẽ viết

if test "$foo" = "bar" ; then ...

Ký hiệu thứ hai này vẫn còn tồn tại, và tôi thấy rằng nó rõ ràng hơn về những gì đang xảy ra: bạn đang gọi một lệnh gọi test, mà đánh giá đối số của nó và trả về một mã trạng thái thoátifsử dụng để quyết định làm gì tiếp theo. Lệnh đó có thể được tích hợp vào trình bao hoặc nó có thể là một chương trình bên ngoài .¹

[như là một thay thế để testđến sau.² Nó có thể là một từ đồng nghĩa dựng sẵn cho test, nhưng nó cũng được cung cấp như /bin/[trên các hệ thống hiện đại cho các vỏ không có nó như là một nội dung.

[testcó thể được thực hiện bằng cách sử dụng cùng một mã. Đây là trường hợp cho /bin/[/bin/testtrên OS X, trong đó đây là các liên kết cứng đến cùng một tệp thực thi. Do đó, việc triển khai hoàn toàn bỏ qua dấu vết ]: nó không yêu cầu nếu bạn gọi nó là /bin/[và nó không phàn nàn nếu bạn làm cho nó để /bin/test.⁴

Không có lịch sử nào ảnh hưởng [[, bởi vì chưa bao giờ có một chương trình nguyên thủy nào được gọi [[. Nó tồn tại hoàn toàn bên trong các shell thực hiện nó như một phần mở rộng cho shell POSIX .

Một phần của sự khác biệt giữa "nội dung" và "từ khóa" là do lịch sử này. Nó cũng phản ánh thực tế là các quy tắc cú pháp để phân tích các [[biểu thức là khác nhau, như được chỉ ra trong câu trả lời của John1024 .⁵


Chú thích:

  1. Khi bạn nhìn theo cách đó, nó cho thấy rõ lý do tại sao bạn phải đặt khoảng trắng xung quanh [trong tập lệnh shell, không giống như cách dấu ngoặc đơn và dấu ngoặc làm việc trong hầu hết các ngôn ngữ lập trình khác. Nếu trình phân tích cú pháp của shell cho phép if["$x"..., nó cũng sẽ phải cho phépiftest"$x"...

  2. Nó đã xảy ra vào khoảng năm 1980. /bin/[không tồn tại trong bản sao Unix V7 cổ đại của tôi từ năm 1979, và cũng không man testghi nhận nó là bí danh. Trong mục nhập trang người đàn ông tương ứng tôi có trong một bản sao trước khi phát hành của Hướng dẫn hệ thống III từ năm 1980, nó được liệt kê.

  3. ls -i /bin/[ /bin/test

  4. Nhưng đừng tin vào hành vi này. Việc xây dựng trong phiên bản Bash của [đòi hỏi đóng cửa ], và built-in của nó testthực hiện sẽ phàn nàn nếu bạn làm cung cấp nó.

  5. Phân biệt lệnh dựng sẵn và phân biệt bên ngoài cũng có thể quan trọng vì một lý do khác: hai triển khai có thể hành xử khác nhau. Đây là trường hợp echotrên nhiều hệ thống. Bởi vì chỉ có một triển khai, không cần phải phân biệt như vậy cho một từ khóa.


Cảm ơn bạn @Warren Young, nhưng tại sao builtinkeywordsự khác biệt giữa [[[khi cả hai cung cấp cùng một chức năng (ngoại trừ thực tế [[đi kèm với nhiều tính năng hơn [)?
Sree

2
@sree [[là một từ khóa cho phép bash làm những việc không thể thực hiện được [- ví dụ như trích dẫn không cần thiết nhiều thời gian, bởi vì shell nhận thức được rằng đó là một biến. Điều đó có nghĩa là, việc xử lý dòng lệnh bị ảnh hưởng khi sử dụng từ khóa, nhưng không phải khi sử dụng nội dung - điều đó xảy ra muộn hơn nhiều.
muru

1
Lưu ý rằng mã cho vỏ V7 Bourne hiển thị [nội dung, nhưng mã được nhận xét.
Stéphane Chazelas

cdlà một nội dung, nhưng nó không che giấu bất cứ điều gì ( cdkhông thể được thực hiện như một chương trình bên ngoài).
Paŭlo Ebermann

2
Đây là một bản tóm tắt lịch sử xuất sắc, nhưng nó bỏ qua chi tiết quan trọng (IMO), được chỉ ra bởi muru ở trên và bởi John1024 trong câu trả lời của họ, rằng việc tạo [[một từ khóa cho phép shell sử dụng các quy tắc phân tích cú pháp đặc biệt cho các đối số của nó. Vì vậy, than ôi, upvote của tôi đi đến John1024.
Ilmari Karonen

3

[ban đầu chỉ là một lệnh bên ngoài, một tên khác cho /bin/test. Nhưng một số lệnh, chẳng hạn như [echo, được sử dụng rất thường xuyên trong các tập lệnh shell đến nỗi những người triển khai shell đã quyết định sao chép mã trực tiếp vào chính shell, thay vì phải chạy một quy trình khác mỗi khi chúng được sử dụng. Điều đó đã biến các lệnh này thành "nội trang", mặc dù bạn vẫn có thể gọi chương trình bên ngoài thông qua đường dẫn đầy đủ của nó.

[[đến nhiều sau Mặc dù nội dung được triển khai bên trong shell, nó được phân tích cú pháp giống như các lệnh bên ngoài. Như đã giải thích trong câu trả lời của John1024, điều này có nghĩa là các biến không được trích dẫn sẽ thực hiện phân tách từ được thực hiện trên chúng và các mã thông báo thích ><được xử lý như chuyển hướng I / O. Điều này làm cho việc viết biểu thức so sánh phức tạp bất tiện. [[đã được tạo như cú pháp shell, để nó có thể được phân tích cú pháp ideosyncratically. Trong [[các biến không nhận được phân tách từ <>có thể được sử dụng làm toán tử so sánh, =có thể hoạt động khác nhau tùy thuộc vào việc tham số tiếp theo có được trích dẫn hay không, v.v ... Đây là tất cả các tiện ích giúp [[dễ sử dụng hơn so với [lệnh / dựng sẵn truyền thống .

Họ không thể đơn giản mã hóa lại [thành cú pháp như thế này vì nó sẽ là một thay đổi không tương thích với hàng triệu tập lệnh. Bằng cách sử dụng [[cú pháp mới , trước đây không tồn tại, họ hoàn toàn có thể cải tiến cách nó được sử dụng theo cách tương thích hướng lên.

Điều này tương tự với sự tiến hóa dẫn đến $((...))cú pháp cho các biểu thức số học, phần lớn đã thay thế exprlệnh truyền thống .


0

Các phiên bản mới hơn [[trong bashlà một tối ưu hóa của [.

Cổ điển [có một nhược điểm lớn, khi nó được sử dụng thường xuyên để thực hiện một thao tác tầm thường: nó sẽ sinh ra một quy trình mới mỗi lần:
(Nó tạo ra một không gian địa chỉ mới chỉ để so sánh 01! Mỗi lần!)

Tôi nghĩ rằng một điểm chính của việc bổ sung [[là làm cho việc đánh giá biểu thức bên trong [không sinh ra một quá trình bổ sung. Nhưng cách [làm việc không thể thay đổi - nó sẽ tạo ra nhiều sự nhầm lẫn và vấn đề. Vì vậy, việc tối ưu hóa đã được thực hiện với một tên mới, theo cách hiệu quả hơn, cụ thể là lệnh shell dựng sẵn.
Nó trở thành từ khóa trong cú pháp shell như là một tác dụng phụ.

Tại thời điểm [được sử dụng đầu tiên, đó là cách đúng đắn để làm điều đó với một quy trình bên ngoài.


5
Lưu ý rằng mặc dù [ban đầu là một lệnh bên ngoài, nó đã được thêm vào dưới dạng vỏ từ khá sớm, có lẽ là vào thời điểm Unix System III được phát hành và chắc chắn trước khi Unix System V được phát hành. Vì vậy, "quá trình bổ sung" không phải là vấn đề từ lâu. Tuy nhiên, cú pháp vẫn không thay đổi - nó được xử lý như thể nó sẽ là một lệnh bên ngoài.
Jonathan Leffler

@JonathanLeffler Ồ, cảm ơn, tôi đã bỏ lỡ [cả hai - điều đó có nghĩa là một số thay đổi ...
Volker Siegel
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.