Khi một kịch bản shell bắt đầu bằng #!
, dòng đầu tiên đó là một nhận xét khi có liên quan đến shell. Tuy nhiên, hai ký tự đầu tiên có ý nghĩa đối với một phần khác của hệ thống: kernel. Hai nhân vật #!
được gọi là một shebang . Để hiểu vai trò của shebang, bạn cần hiểu cách chương trình được thực thi.
Thực hiện một chương trình từ một tập tin đòi hỏi phải có hành động từ kernel. Điều này được thực hiện như là một phần của execve
cuộc gọi hệ thống. Nhân cần xác minh quyền truy cập tệp, giải phóng tài nguyên (bộ nhớ, v.v.) liên quan đến tệp thực thi hiện đang chạy trong quy trình gọi, phân bổ tài nguyên cho tệp thực thi mới và chuyển điều khiển sang chương trình mới (và nhiều thứ khác Tôi sẽ không đề cập đến). Cuộc execve
gọi hệ thống thay thế mã của quy trình hiện đang chạy; có một cuộc gọi hệ thống riêng fork
để tạo ra một quy trình mới.
Để làm điều này, kernel phải hỗ trợ định dạng của tệp thực thi. Tập tin này phải chứa mã máy, được tổ chức theo cách mà kernel hiểu. Một tập lệnh shell không chứa mã máy, vì vậy nó không thể được thực thi theo cách này.
Cơ chế shebang cho phép kernel trì hoãn nhiệm vụ diễn giải mã sang chương trình khác. Khi kernel thấy rằng tệp thực thi bắt đầu bằng #!
, nó sẽ đọc một vài ký tự tiếp theo và diễn giải dòng đầu tiên của tệp (trừ phần đầu#!
không gian và tùy chọn) làm đường dẫn đến tệp khác (cộng với các đối số mà tôi sẽ không thảo luận ở đây ). Khi kernel được yêu cầu thực thi tệp /my/script
và nó thấy rằng tệp bắt đầu bằng dòng #!/some/interpreter
, kernel thực thi /some/interpreter
với đối số /my/script
. Sau đó, /some/interpreter
quyết định đó /my/script
là một tập tin kịch bản mà nó sẽ thực thi.
Điều gì xảy ra nếu một tệp không chứa mã gốc ở định dạng mà kernel hiểu và không bắt đầu bằng shebang? Chà, sau đó tệp không thể thực thi được và execve
cuộc gọi hệ thống không thành công với mã lỗi ENOEXEC
(Lỗi định dạng thực thi).
Đây có thể là kết thúc của câu chuyện, nhưng hầu hết các shell đều thực hiện tính năng dự phòng. Nếu kernel trả vềENOEXEC
, shell sẽ xem xét nội dung của tệp và kiểm tra xem nó có giống tập lệnh shell hay không. Nếu shell nghĩ rằng tập tin trông giống như một tập lệnh shell, nó sẽ tự thực thi nó. Các chi tiết về cách nó làm điều này phụ thuộc vào vỏ. Bạn có thể thấy một số điều đang xảy ra bằng cách thêm ps $$
vào tập lệnh của mình và hơn thế nữa bằng cách xem quá trình strace -p1234 -f -eprocess
trong đó 1234 là PID của trình bao.
Trong bash, cơ chế dự phòng này được thực hiện bằng cách gọi fork
nhưng không execve
. Quá trình bash con tự xóa trạng thái bên trong của nó và mở tệp tập lệnh mới để chạy nó. Do đó, quá trình chạy tập lệnh vẫn đang sử dụng hình ảnh mã bash ban đầu và các đối số dòng lệnh ban đầu được truyền khi bạn gọi bash ban đầu. ATT ksh hành xử theo cùng một cách.
% bash --norc
bash-4.3$ ./foo.sh
PID TTY STAT TIME COMMAND
21913 pts/2 S+ 0:00 bash --norc
Ngược lại, Dash phản ứng lại ENOEXEC
bằng cách gọi /bin/sh
với đường dẫn đến tập lệnh được truyền dưới dạng đối số. Nói cách khác, khi bạn thực thi một tập lệnh shebangless từ dấu gạch ngang, nó sẽ hoạt động như thể tập lệnh có một dòng shebang với #!/bin/sh
. Mksh và zsh hành xử theo cùng một cách.
% dash
$ ./foo.sh
PID TTY STAT TIME COMMAND
21427 pts/2 S+ 0:00 /bin/sh ./foo.sh