Tại sao không phải là ps ps rìu tìm thấy một tập lệnh bash đang chạy mà không có tiêu đề # #!


13

Khi tôi chạy tập lệnh này, dự định chạy cho đến khi bị giết ...

# foo.sh

while true; do sleep 1; done

... Tôi không thể tìm thấy nó bằng cách sử dụng ps ax:

>./foo.sh

// In a separate shell:
>ps ax | grep foo.sh
21110 pts/3    S+     0:00 grep --color=auto foo.sh

... nhưng nếu tôi chỉ thêm #!tiêu đề " " chung vào tập lệnh ...

#! /usr/bin/bash
# foo.sh

while true; do sleep 1; done

... Sau đó, tập lệnh có thể tìm thấy bằng cùng một pslệnh ...

>./foo.sh

// In a separate shell:
>ps ax | grep foo.sh
21319 pts/43   S+     0:00 /usr/bin/bash ./foo.sh
21324 pts/3    S+     0:00 grep --color=auto foo.sh

Tại sao cái này rất?
Đây có thể là một câu hỏi liên quan: Tôi nghĩ " #" chỉ là tiền tố nhận xét và nếu vậy thì " #! /usr/bin/bash" không có gì khác hơn là một nhận xét. Nhưng " #!" mang một số ý nghĩa lớn hơn là chỉ là một nhận xét?


Bạn đang sử dụng Unix gì?
Kusalananda

@Kusalananda - Linux linuxbox 3.11.10-301.fc20.x86_64 # 1 SMP Thu ngày 05 tháng 12 14:01:17 UTC 2013 x86_64 x86_64 x86_64 GNU / Linux
StoneThrow

Câu trả lời:


13

Khi trình vỏ tương tác hiện tại là bashvà bạn chạy một tập lệnh không có #!dòng, thì nó bashsẽ chạy tập lệnh. Quá trình sẽ hiển thị trong ps axđầu ra như chỉ bash.

$ cat foo.sh
# foo.sh

echo "$BASHPID"
while true; do sleep 1; done

$ ./foo.sh
55411

Trong một thiết bị đầu cuối khác:

$ ps -p 55411
  PID TT  STAT       TIME COMMAND
55411 p2  SN+     0:00.07 bash

Liên quan:


Các phần có liên quan tạo thành bashhướng dẫn:

Nếu việc thực thi này thất bại vì tệp không ở định dạng thực thi và tệp không phải là thư mục, thì nó được coi là một tập lệnh shell , một tệp chứa các lệnh shell. Một subshell được sinh ra để thực hiện nó. Subshell này tự khởi động lại chính nó, do đó , hiệu ứng như thể một lớp vỏ mới đã được gọi để xử lý tập lệnh , ngoại trừ vị trí của các lệnh được ghi nhớ bởi cha mẹ (xem hàm băm bên dưới SHELL BUILTIN LỆNH) được giữ lại bởi đứa trẻ.

Nếu chương trình là một tệp bắt đầu bằng #!, phần còn lại của dòng đầu tiên chỉ định trình thông dịch cho chương trình. Shell thực thi trình thông dịch đã chỉ định trên các hệ điều hành không tự xử lý định dạng thực thi này. [...]

Điều này có nghĩa là chạy ./foo.shtrên dòng lệnh, khi foo.shkhông có #!dòng-line, giống như chạy các lệnh trong tệp trong một khung con, tức là như

$ ( echo "$BASHPID"; while true; do sleep 1; done )

Với một #!dòng thích hợp chỉ vào ví dụ /bin/bash, nó giống như đang làm

$ /bin/bash foo.sh

Tôi nghĩ rằng tôi làm theo, nhưng những gì bạn nói cũng đúng trong trường hợp thứ hai: bash cũng chạy tập lệnh trong trường hợp thứ hai, như có thể được quan sát khi pshiển thị tập lệnh chạy dưới dạng " /usr/bin/bash ./foo.sh". Vì vậy, trong trường hợp đầu tiên, như bạn nói, bash sẽ chạy tập lệnh, nhưng tập lệnh đó có cần phải được "chuyển" sang tập lệnh bash rẽ nhánh, như với trường hợp thứ hai không? (và nếu vậy, tôi tưởng tượng nó có thể tìm thấy bằng đường ống để grep ...?)
StoneThrow

@StoneThrow Xem câu trả lời cập nhật.
Kusalananda

"... Ngoại trừ việc bạn nhận được một quy trình mới" - tốt, bạn cũng có một quy trình mới, ngoại trừ việc $$vẫn chỉ ra quy trình cũ trong trường hợp phụ ( echo $BASHPID/ bash -c 'echo $PPID').
Michael Homer

@MichaelHomer Ah, cảm ơn vì điều đó! Sẽ nâng cấp.
Kusalananda

12

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 execvecuộ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 execvegọ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/scriptvà nó thấy rằng tệp bắt đầu bằng dòng #!/some/interpreter, kernel thực thi /some/interpretervới đối số /my/script. Sau đó, /some/interpreterquyết định đó /my/scriptlà 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à execvecuộ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 -eprocesstrong đó 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 ENOEXECbằng cách gọi /bin/shvớ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

Tuyệt vời, hiểu câu trả lời. Một câu hỏi RE: triển khai dự phòng mà bạn đã giải thích: Tôi cho rằng vì một đứa trẻ bashbị rẽ nhánh, nó có quyền truy cập vào cùng một argv[]mảng với cha mẹ của nó, đó là cách nó biết "các đối số dòng lệnh ban đầu được truyền khi bạn gọi bash ban đầu" và nếu vì vậy, đây là lý do tại sao đứa trẻ không được thông qua kịch bản gốc dưới dạng một đối số rõ ràng (do đó tại sao grep không thể tìm thấy) - điều đó có chính xác không?
StoneThrow

1
Bạn thực sự có thể tắt hành vi shebang kernel ( BINFMT_SCRIPTmô-đun kiểm soát điều này và có thể được gỡ bỏ / mô-đun hóa, mặc dù nó thường được liên kết tĩnh với kernel), nhưng tôi không hiểu tại sao bạn lại muốn, ngoại trừ có lẽ trong một hệ thống nhúng . Như một giải pháp cho khả năng này, bashcó một cờ cấu hình ( HAVE_HASH_BANG_EXEC) để bù!
ErikF

2
@StoneThrow Nó không đến nỗi đứa trẻ bash có thể biết các đối số dòng lệnh ban đầu, vì nó không sửa đổi chúng. Một chương trình có thể sửa đổi những gì psbáo cáo là đối số dòng lệnh, nhưng chỉ đến một điểm: nó phải sửa đổi bộ đệm hiện có, nó không thể phóng to bộ đệm này. Vì vậy, nếu bash cố gắng sửa đổi nó argvđể thêm tên của tập lệnh, nó sẽ không hoạt động. Đứa trẻ không phải là người đã thông qua một cuộc tranh luận bởi vì không bao giờ có một execvecuộc gọi hệ thống trong đứa trẻ. Nó chỉ là cùng một hình ảnh quá trình bash tiếp tục chạy.
Gilles 'SO- đừng trở nên xấu xa'

-1

Trong trường hợp đầu tiên, tập lệnh được chạy bởi một đứa trẻ rẽ nhánh từ trình bao hiện tại của bạn.

Trước tiên bạn nên chạy echo $$và sau đó xem một shell có id process của shell của bạn là id process process.

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.