Là #! / Bin / sh được người phiên dịch đọc?


66

Trong bashhoặc sh, tôi đoán bất cứ điều gì bắt đầu bằng #một nhận xét .

Nhưng trong bashcác kịch bản chúng tôi viết:

#!/bin/bash

Và trong các tập lệnh Python, có:

#!/bin/python

Điều này có nghĩa là #bản thân nó là một bình luận trong khi #!không?


1
Và một khi bạn bắt đầu nhìn vào hồ sơ Apparmor, bạn sẽ thấy #include. Cũng vậy, #không có nghĩa là một bình luận.

4
@ vasa1 Nhưng điểm mấu chốt thường không được đánh giá cao về các dòng hashbang khi bắt đầu tập lệnh shell là chúng là các bình luận .
Eliah Kagan

Câu trả lời:


100

Các #!dòng được sử dụng trước khi kịch bản được chạy, sau đó bỏ qua khi kịch bản chạy.

Bạn đang hỏi sự khác biệt giữa một dòng shebang và một bình luận thông thường.

Một dòng bắt đầu bằng #!một nhận xét cũng giống như bất kỳ dòng nào khác bắt đầu bằng #. Điều này đúng nếu #!dòng đầu tiên của tệp hoặc bất kỳ nơi nào khác. #!/bin/sh có tác dụng , nhưng nó không được đọc bởi chính người phiên dịch .

#không phải là một nhận xét trong tất cả các ngôn ngữ lập trình, nhưng, như bạn biết, đó là một nhận xét trong các shell kiểu Bourne bao gồm shbash(cũng như hầu hết các shell không theo kiểu Bourne, như csh). Đó cũng là một nhận xét trong Python . Và đó là một nhận xét trong một loạt các tệp cấu hình hoàn toàn không phải là tập lệnh (như /etc/fstab).

Giả sử một kịch bản shell bắt đầu bằng #!/bin/sh. Đó là một nhận xét và trình thông dịch (trình bao) bỏ qua mọi thứ trên dòng sau #ký tự.

Mục đích của một #!dòng là không cung cấp thông tin cho người phiên dịch. Mục đích của #!dòng là báo cho hệ điều hành (hoặc bất kỳ quy trình nào khởi chạy trình thông dịch) những gì sẽ sử dụng làm trình thông dịch .

  • Nếu bạn gọi tập lệnh dưới dạng tệp thực thi, ví dụ, bằng cách chạy ./script.sh, hệ thống sẽ hỏi dòng đầu tiên để xem có bắt đầu bằng không #!, theo sau là 0 hoặc nhiều khoảng trắng, theo sau là lệnh. Nếu có, nó chạy lệnh đó với tên của tập lệnh làm đối số của nó. Trong ví dụ này, nó chạy /bin/sh script.sh(hoặc, về mặt kỹ thuật, /bin/sh ./script.sh).

  • Nếu bạn gọi tập lệnh bằng cách gọi rõ ràng trình thông dịch, #!dòng không bao giờ được hỏi ý kiến. Vì vậy, nếu bạn chạy sh script.sh, dòng đầu tiên không có hiệu lực. Nếu script2.shdòng đầu tiên là #!/usr/games/nibbles, chạy sh script2.shsẽ không cố mở tập lệnh trong nibbles(nhưng ./script2.shsẽ).

Bạn sẽ nhận thấy rằng trong cả hai trường hợp, phần mở rộng của tập lệnh ( .sh), nếu nó có phần mở rộng , sẽ ảnh hưởng đến cách nó được chạy. Trong một hệ thống giống như Unix, điều này thường không ảnh hưởng đến cách chạy tập lệnh. Trên một số hệ thống khác, như Windows, #!dòng shebang có thể bị hệ thống bỏ qua hoàn toàn và tiện ích mở rộng có thể xác định những gì chạy tập lệnh. (Điều này không có nghĩa là bạn cần cung cấp các tiện ích mở rộng tập lệnh của mình, nhưng đó là một trong những lý do tại sao nếu bạn làm như vậy, chúng phải chính xác.)

#!đã được chọn để phục vụ mục đích này chính xác bởi vì # bắt đầu một bình luận. Các #!dòng là đối với hệ thống, chứ không phải phiên dịch, và nó sẽ bị bỏ qua bởi các thông dịch viên.

Dòng Shebang cho Bash Script

Bạn (ban đầu) cho biết bạn sử dụng #!/bin/shcho bashcác kịch bản. Bạn chỉ nên làm điều đó nếu tập lệnh không yêu cầu bất kỳ bashtiện ích mở rộng nào-- shcần có khả năng chạy tập lệnh. shkhông phải lúc nào cũng là một liên kết tượng trưng đến bash. Thông thường, bao gồm trên tất cả các hệ thống Debian và Ubuntu gần đây , shlà một liên kết tượng trưng đến dash.

Dòng Shebang cho tập lệnh Python

Bạn cũng đã nói (trong phiên bản đầu tiên của câu hỏi của bạn, trước khi chỉnh sửa) rằng bạn bắt đầu các tập lệnh Python của mình với #!/bin/sh read by the interpretor. Nếu bạn có nghĩa là theo nghĩa đen, thì bạn chắc chắn nên ngừng làm điều đó. Nếu hello.pybắt đầu với dòng đó, chạy ./hello.pythực thi:

/bin/sh read by the interpretor hello.py

/bin/shsẽ cố gắng thực thi một tập lệnh được gọi read(với by the interpretor hello.pytư cách là đối số của nó), readsẽ (hy vọng) sẽ không được tìm thấy và tập lệnh Python của bạn sẽ không bao giờ được trình thông dịch Python nhìn thấy.

Nếu bạn đang mắc lỗi này nhưng không gặp phải vấn đề mà tôi đang mô tả, có lẽ bạn đang gọi các đoạn mã Python của mình bằng cách chỉ định rõ ràng trình thông dịch (ví dụ python hello.py:), khiến dòng đầu tiên bị bỏ qua. Khi bạn phân phối các tập lệnh của mình cho người khác hoặc sử dụng chúng trong một thời gian dài sau đó, có thể không rõ ràng rằng điều này là cần thiết để chúng hoạt động. Tốt nhất là sửa chúng ngay bây giờ. Hoặc ít nhất là loại bỏ hoàn toàn dòng đầu tiên, để khi chúng không chạy với ./thông báo lỗi sẽ có ý nghĩa.

Đối với các tập lệnh Python, nếu bạn biết trình thông dịch Python ở đâu (hoặc sắp có), bạn có thể viết #!dòng theo cùng một cách:

#!/usr/bin/python

Hoặc, nếu đó là tập lệnh Python 3, bạn nên chỉ định python3, vì pythonhầu như luôn luôn là Python 2 :

#!/usr/bin/python3

Tuy nhiên, vấn đề là trong khi /bin/shđược cho là luôn tồn tại và /bin/bashhầu như luôn tồn tại trên các hệ thống bashđi kèm với HĐH, Python có thể tồn tại ở nhiều nơi.

Do đó, nhiều lập trình viên Python sử dụng điều này thay thế:

#!/usr/bin/env python

(Hoặc #!/usr/bin/env python3cho Python 3.)

Điều này làm cho kịch bản dựa vào envviệc ở đúng "chỗ" thay vì dựa vào pythonđúng chỗ. Đó là một điều tốt, bởi vì:

  • envhầu như luôn luôn nằm trong /usr/bin.
  • Trên hầu hết các hệ thống, bất kỳ hệ thống nào python nên chạy tập lệnh của bạn là tập lệnh xuất hiện đầu tiên trong PATH. Bắt đầu hello.pyvới #!/usr/bin/env pythonmake ./hello.pyrun /usr/bin/env python hello.py, gần như tương đương với chạy python hello.py.

Lý do bạn không thể sử dụng #!pythonlà:

  • Bạn muốn trình thông dịch được chỉ định sẽ được cung cấp bởi một đường dẫn tuyệt đối (nghĩa là bắt đầu bằng /).
  • Quá trình gọi sẽ thực hiện python trong thư mục hiện tại . Tìm kiếm đường dẫn khi lệnh không chứa dấu gạch chéo là hành vi shell cụ thể.

Đôi khi một Python hoặc kịch bản khác mà không phải là một kịch bản shell sẽ có một dòng công việc bắt đầu với #!/bin/sh ...nơi ...là một số mã khác. Điều này đôi khi đúng, bởi vì có một số cách để gọi shell tương thích Bourne ( sh) với các đối số để làm cho nó gọi trình thông dịch Python. (Một trong những đối số có thể sẽ chứa python.) Tuy nhiên, đối với hầu hết các mục đích, #!/usr/bin/env pythonđơn giản hơn, thanh lịch hơn và có nhiều khả năng hoạt động theo cách bạn muốn.

Dòng Shebang trong các ngôn ngữ khác

Nhiều ngôn ngữ lập trình và kịch bản, và một số định dạng tệp khác, sử dụng #làm nhận xét. Đối với bất kỳ ai trong số họ, một tệp trong ngôn ngữ có thể được chạy bởi một chương trình lấy nó làm đối số bằng cách chỉ định chương trình trên dòng đầu tiên sau #!.

Trong một số ngôn ngữ lập trình, #thông thường không phải là một nhận xét, nhưng trong trường hợp đặc biệt, dòng đầu tiên bị bỏ qua nếu nó bắt đầu bằng #!. Điều này tạo thuận lợi cho việc sử dụng #!cú pháp mặc dù #không đưa ra nhận xét.

Dòng Shebang cho các tệp không chạy dưới dạng tập lệnh

Mặc dù ít trực quan hơn, nhưng bất kỳ tệp nào có định dạng tệp có thể chứa dòng đầu tiên bắt đầu #!bằng đường dẫn đầy đủ của tệp thực thi có thể có dòng shebang. Nếu bạn làm điều này và tệp được đánh dấu thực thi, thì bạn có thể chạy nó như một chương trình ... khiến nó được mở như một tài liệu.

Một số ứng dụng sử dụng hành vi này có chủ ý. Ví dụ, trong VMware, .vmxcác tệp định nghĩa các máy ảo. Bạn có thể "chạy" một máy ảo như thể nó là một tập lệnh vì các tệp này được đánh dấu có thể thực thi được và có một dòng shebang khiến chúng được mở trong tiện ích VMware.

Dòng Shebang cho các tệp không chạy dưới dạng Scrips nhưng hành động như kịch bản

rmloại bỏ các tập tin. Nó không phải là một ngôn ngữ kịch bản. Tuy nhiên, một tệp bắt đầu #!/bin/rmvà được đánh dấu thực thi có thể được chạy và khi bạn chạy nó, rmđược gọi trên đó, xóa nó.

Điều này thường được khái niệm hóa là "tập tin tự xóa." Nhưng các tập tin không thực sự chạy ở tất cả. Điều này giống như tình huống được mô tả ở trên cho .vmxcác tập tin.

Tuy nhiên, vì #!dòng tạo điều kiện cho việc chạy một lệnh đơn giản (bao gồm các đối số dòng lệnh), bạn có thể thực hiện một số kịch bản theo cách này. Như một ví dụ đơn giản về một "tập lệnh" phức tạp hơn #!/bin/rm, hãy xem xét:

#!/usr/bin/env tee -a

Thao tác này sẽ đưa đầu vào của người dùng một cách tương tác, lặp lại nó theo từng dòng của người dùng và nối nó vào cuối tệp "tập lệnh".

Hữu ích? Không hẳn. Khái niệm thú vị? Tổng cộng! Đúng. (Một chút nào đó.)

Khái niệm lập trình / lập trình tương tự về mặt khái niệm (chỉ để giải trí)


@Rinzwind Thx! (Btw câu trả lời này không bắt nguồn từ nơi khác, nếu đó là điều bạn đang thắc mắc.)
Eliah Kagan

@Rinzwind Đừng lo lắng, với 8 lượt upvote sau 1 giờ, nó có khả năng tăng thêm nhiều nữa :-)
guntbert

1
Nếu nó luôn bị bỏ qua, thì -xcờ Pythons sẽ làm gì?
gerrit

4
@gerrit Câu hỏi hay. Trong bất kỳ ngôn ngữ nào mà trình biên dịch / trình biên dịch báo cáo các thông báo có số dòng, nội dung của các bình luận sẽ bị bỏ qua, nhưng các dòng bình luận vẫn được tính . Thêm một nhận xét hoặc dòng trống trước một dòng mã vẫn dẫn đến dòng mã đó có số dòng tăng lên. -x"bỏ qua [s] dòng đầu tiên ..." dòng thứ 2 được đánh số 1thay vì 2dòng thứ 3 2thay vì 3, v.v ... Đây là lý do tại sao bạn không nên sử dụng cờ đó. ;) -xdành cho kịch bản trên các hệ điều hành không giống Unix có cú pháp giống shebang không bắt đầu bằng #(do đó không phải là một nhận xét Python).
Eliah Kagan

4
Trong Perl, nếu trình thông dịch được bắt đầu trực tiếp ( perl script.plso với ./script.pl) thì trình thông dịch sẽ đọc dòng shebang để phân tích các cờ như -w. Không nên dựa vào tính năng này.
Ngừng làm hại Monica

7

Một shebang là chuỗi ký tự bao gồm ký hiệu số ký tự và dấu chấm than (ví dụ: "#!") Khi nó xuất hiện dưới dạng hai ký tự ban đầu trên dòng ban đầu của tập lệnh.

Trong các hệ điều hành * nix, khi tập lệnh bắt đầu bằng shebang được chạy, trình tải chương trình sẽ phân tích phần còn lại của dòng ban đầu của tập lệnh dưới dạng một lệnh phiên dịch; thay vào đó, chương trình thông dịch đã chỉ định được chạy, chuyển đến nó làm đối số đường dẫn ban đầu được sử dụng khi cố chạy tập lệnh. Ví dụ: nếu một tập lệnh được đặt tên theo đường dẫn "path / to / your-script" và nó bắt đầu bằng dòng sau:

#!/bin/sh

sau đó trình tải chương trình được hướng dẫn chạy chương trình "/ bin / sh" thay vào đó, ví dụ trình bao Bourne hoặc trình bao tương thích, chuyển "path / to / your-script" làm đối số đầu tiên.

Theo đó, tập lệnh được đặt tên theo đường dẫn "path / to / python-script" và nó bắt đầu bằng dòng sau:

#!/bin/python

sau đó chương trình được tải được hướng dẫn để chạy chương trình "/ bin / python" thay vào đó, ví dụ như trình thông dịch Python, truyền "path / to / python-script" làm đối số đầu tiên.

Trong ngắn hạn "#" sẽ nhận xét một dòng trong khi chuỗi ký tự "#!" xảy ra như hai ký tự đầu tiên trên dòng ban đầu của tập lệnh có nghĩa được phác thảo như trên.

Để biết chi tiết, xem Tại sao một số tập lệnh bắt đầu bằng #! ...?

Nguồn: Một số phần của câu trả lời này có nguồn gốc (với một chút sửa đổi) từ Shebang (Unix) trên Wikipedia tiếng Anh (bởi những người đóng góp Wikipedia ). Bài viết này được cấp phép theo CC-BY-SA 3.0 , giống như nội dung của người dùng ở đây trên AU, do đó, phái sinh này được cho phép với sự ghi nhận.


4

#!được gọi là shebangkhi nó xuất hiện dưới dạng hai ký tự ban đầu trên dòng ban đầu của tập lệnh. Nó được sử dụng trong các tập lệnh để chỉ ra một trình thông dịch để thực thi. Cái shebangnày dành cho hệ điều hành (kernel), không phải cho shell; vì vậy nó sẽ không được hiểu là một bình luận.

Lịch sự: http://en.wikipedia.org/wiki/Shebang_%28Unix%29

Nói chung, nếu một tệp có thể thực thi được, nhưng thực tế không phải là chương trình thực thi (nhị phân) và có một dòng như vậy, thì chương trình được chỉ định sau #! được bắt đầu với scriptname và tất cả các đối số của nó. Hai nhân vật # và! phải là hai byte đầu tiên trong tập tin!

Thông tin chi tiết: http://wiki.bash-hackers.org/scripting/basics#the_shebang


0

Không, nó chỉ được sử dụng bởi lệnh execgọi hệ thống của nhân Linux và được coi là một nhận xét của trình thông dịch

Khi bạn làm trên bash:

./something

trên Linux, điều này gọi cuộc gọi exechệ thống với đường dẫn ./something.

Dòng này của kernel được gọi trên tệp được chuyển đến exec: https://github.com/torvalds/linux/blob/v4.8/fs/binfmt_script.c#L25

if ((bprm->buf[0] != '#') || (bprm->buf[1] != '!'))

Nó đọc các byte đầu tiên của tệp và so sánh chúng với #!.

Nếu so sánh là đúng, thì phần còn lại của dòng được phân tích cú pháp bởi nhân Linux, điều này thực hiện một execcuộc gọi khác với đường dẫn /usr/bin/env pythonvà tệp hiện tại làm đối số đầu tiên:

/usr/bin/env python /path/to/script.py

và điều này hoạt động cho bất kỳ ngôn ngữ kịch bản sử dụng #như một nhân vật bình luận.

Và vâng, bạn có thể tạo một vòng lặp vô hạn với:

printf '#!/a\n' | sudo tee /a
sudo chmod +x /a
/a

Bash nhận ra lỗi:

-bash: /a: /a: bad interpreter: Too many levels of symbolic links

#! tình cờ có thể đọc được, nhưng điều đó là không bắt buộc.

Nếu tệp bắt đầu với các byte khác nhau, thì lệnh execgọi hệ thống sẽ sử dụng một trình xử lý khác. Trình xử lý tích hợp quan trọng nhất khác dành cho các tệp thực thi ELF: https://github.com/torvalds/linux/blob/v4.8/fs/binfmt_elf.c#L1305 để kiểm tra byte 7f 45 4c 46(cũng có thể là con người có thể đọc được .ELF). Hãy xác nhận rằng bằng cách đọc 4 byte đầu tiên /bin/ls, đây là tệp thực thi ELF:

head -c 4 "$(which ls)" | hd 

đầu ra:

00000000  7f 45 4c 46                                       |.ELF|
00000004                                                                 

Vì vậy, khi kernel nhìn thấy các byte đó, nó sẽ lấy tệp ELF, đặt nó vào bộ nhớ một cách chính xác và bắt đầu một quy trình mới với nó. Xem thêm: https://stackoverflow.com/questions/8352535/how-does-kernel-get-an-executable-binary-file-ricky-under-linux/31394861#31394861

Cuối cùng, bạn có thể thêm trình xử lý shebang của riêng bạn với binfmt_misccơ chế. Ví dụ: bạn có thể thêm một trình xử lý tùy chỉnh cho .jarcác tệp . Cơ chế này thậm chí hỗ trợ xử lý bằng cách mở rộng tập tin. Một ứng dụng khác là chạy trong suốt các tệp thực thi của một kiến ​​trúc khác với QEMU .

Tôi không nghĩ rằng POSIX định shebangs tuy nhiên: https://unix.stackexchange.com/a/346214/32558 , mặc dù nó không đề cập đến ở trên phần lý do, và theo hình thức "nếu kịch bản thực thi được hỗ trợ bởi một cái gì đó hệ thống có thể xảy ra ".

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.