Quy tắc gọi subshell trong Bash?


24

Tôi dường như hiểu sai quy tắc Bash để tạo ra một subshell. Tôi nghĩ rằng dấu ngoặc đơn luôn tạo ra một lớp con, chạy như là quá trình riêng của nó.

Tuy nhiên, điều này dường như không phải là trường hợp. Trong Đoạn mã A (bên dưới), sleeplệnh thứ hai không chạy trong một vỏ riêng (như được xác định bởi pstreetrong một thiết bị đầu cuối khác). Tuy nhiên, trong Code Snippet B, sleeplệnh thứ hai không chạy trong một shell riêng. Sự khác biệt duy nhất giữa các đoạn mã là đoạn mã thứ hai có hai lệnh trong ngoặc đơn.

Ai đó có thể vui lòng giải thích quy tắc khi subshells được tạo ra?

MÃ SNIPPET A:

sleep 5
(
sleep 5
)

MÃ SNIPPET B:

sleep 5
(
x=1
sleep 5
)

Câu trả lời:


20

Các dấu ngoặc luôn luôn bắt đầu một subshell. Điều đang xảy ra là bash phát hiện đó sleep 5là lệnh cuối cùng được thực thi bởi lớp con đó, vì vậy nó gọi execthay vì fork+ exec. Các sleeplệnh thay thế subshell trong quá trình tương tự.

Nói cách khác, trường hợp cơ sở là:

  1. ( … )tạo một subshell. Quá trình ban đầu gọi forkwait. Trong quy trình con, đó là một lớp con:
    1. sleeplà một lệnh bên ngoài đòi hỏi một quy trình con của quy trình con. Các cuộc gọi subshell forkwait. Trong quy trình con:
      1. Các quy trình con thực hiện lệnh bên ngoài → exec.
      2. Cuối cùng, lệnh kết thúc → exit.
    2. wait hoàn thành trong subshell.
  2. wait hoàn thành trong quá trình ban đầu.

Tối ưu hóa là:

  1. ( … )tạo một subshell. Quá trình ban đầu gọi forkwait. Trong quy trình con, đó là một mạng con cho đến khi nó gọi exec:
    1. sleep là một lệnh bên ngoài và đó là điều cuối cùng mà quá trình này cần phải làm.
    2. Quá trình con thực hiện lệnh bên ngoài → exec.
    3. Cuối cùng, lệnh kết thúc → exit.
  2. wait hoàn thành trong quá trình ban đầu.

Khi bạn thêm một cái gì đó khác sau cuộc gọi sleep, lớp con cần được giữ xung quanh, vì vậy việc tối ưu hóa này không thể xảy ra.

Khi bạn thêm một cái gì đó khác trước khi gọi đến sleep, tối ưu hóa có thể được thực hiện (và ksh thực hiện), nhưng bash không làm điều đó (nó rất bảo thủ với tối ưu hóa này).


Subshell được tạo bằng cách gọi forkvà tiến trình con được tạo (để thực thi các lệnh bên ngoài) bằng cách gọifork + exec . Nhưng đoạn đầu tiên của bạn cho thấy rằng nó fork + execcũng được gọi là subshell. Những gì tôi đang nhận được ở đây?
haccks

1
@haccks fork+ execkhông được gọi cho subshell, nó được gọi cho lệnh bên ngoài. Không có bất kỳ sự tối ưu hóa nào, sẽ có một forkcuộc gọi cho subshell và một cuộc gọi khác cho lệnh bên ngoài. Tôi đã thêm một mô tả dòng chi tiết cho câu trả lời của tôi.
Gilles 'SO- đừng trở nên xấu xa'

Cảm ơn rất nhiều cho bản cập nhật. Bây giờ nó giải thích tốt hơn. Tôi có thể suy luận rằng trong trường hợp (...)(trong trường hợp cơ sở), có thể có hoặc không có cuộc gọi execphụ thuộc vào việc liệu lớp con có bất kỳ lệnh bên ngoài nào để thực thi hay không, trong khi trong trường hợp thực hiện bất kỳ lệnh bên ngoài nào thì phải có fork + exec.
haccks

Một câu hỏi nữa: Tối ưu hóa này chỉ hoạt động cho subshell hay nó có thể được thực hiện cho một lệnh như datetrong shell?
haccks

@haccks Tôi không hiểu câu hỏi. Tối ưu hóa này là về việc gọi một lệnh bên ngoài như là điều cuối cùng mà một quá trình shell thực hiện. Nó không bị giới hạn trong các mạng con: so sánh strace -f -e clone,execve,write bash -c 'date'strace -f -e clone,execve,write bash -c 'date; true'
Gilles 'SO- ngừng trở thành ác quỷ'

4

Từ Hướng dẫn lập trình Bash nâng cao :

"Nói chung, một lệnh bên ngoài trong tập lệnh sẽ loại bỏ một quy trình con, trong khi đó, một hàm dựng sẵn của Bash thì không. Vì lý do này, các hàm dựng thực thi nhanh hơn và sử dụng ít tài nguyên hệ thống hơn các lệnh tương đương bên ngoài của chúng."

Và một chút nữa xuống:

"Một danh sách lệnh được nhúng giữa các dấu ngoặc đơn chạy dưới dạng một lớp con."

Ví dụ:

[root@talara test]# echo $BASHPID
10792
[root@talara test]# (echo $BASHPID)
4087
[root@talara test]# (echo $BASHPID)
4088
[root@talara test]# (echo $BASHPID)
4089

Ví dụ sử dụng mã OP (với thời gian ngủ ngắn hơn vì tôi thiếu kiên nhẫn):

echo $BASHPID

sleep 2
(
    echo $BASHPID
    sleep 2
    echo $BASHPID
)

Đầu ra:

[root@talara test]# bash sub_bash
6606
6608
6608

2
Cảm ơn đã trả lời Tim. Tôi không chắc nó trả lời đầy đủ câu hỏi của tôi. Vì "Danh sách lệnh được nhúng giữa các dấu ngoặc đơn chạy dưới dạng một lớp con", tôi sẽ mong đợi phần thứ hai sleepsẽ chạy trong một lớp con (có lẽ trên quy trình của lớp con vì nó được tích hợp sẵn, thay vì một quy trình con của lớp con). Tuy nhiên, trong mọi trường hợp, tôi đã mong đợi một lớp con tồn tại, tức là một quy trình con Bash theo quy trình Bash cha. Đối với Snippet B ở trên, điều này dường như không phải là trường hợp.
rụt rè

Khắc phục: Vì sleepdường như không phải là một tính năng tích hợp, tôi sẽ mong đợi sleepcuộc gọi thứ hai trong cả hai đoạn mã sẽ chạy trong một quy trình con của quy trình phụ.
rụt rè

@bashful Tôi đã tự do hack mã của bạn với $BASHPIDbiến của tôi . Đáng buồn thay, cách bạn đang làm nó đã không cung cấp cho bạn toàn bộ câu chuyện tôi tin. Xem đầu ra bổ sung của tôi trong câu trả lời.
Tim

4

Một lưu ý bổ sung cho câu trả lời @Gilles.

Như đã nói bởi Gilles: The parentheses always start a subshell.

Tuy nhiên, những con số mà lớp vỏ phụ đó có thể lặp lại:

$ (echo "$BASHPID and $$"; sleep 1)
2033 and 31679
$ (echo "$BASHPID and $$"; sleep 1)
2040 and 31679
$ (echo "$BASHPID and $$"; sleep 1)
2047 and 31679

Như bạn có thể thấy, $$ cứ lặp đi lặp lại, và điều đó đúng như mong đợi, bởi vì (thực hiện lệnh này để tìm đúng man bashdòng):

$ LESS=+/'^ *BASHPID' man bash

BASHPID
Mở rộng tới ID tiến trình của quy trình bash hiện tại. Điều này khác với $$ trong một số trường hợp nhất định, chẳng hạn như các mạng con không yêu cầu bash được khởi tạo lại.

Đó là: Nếu shell không được khởi tạo lại, $$ là như nhau.

Hoặc với điều này:

$ LESS=+/'^ *Special Parameters' man bash

Tham số đặc biệt
$ Mở rộng tới ID tiến trình của trình bao. Trong một lớp con (), nó mở rộng tới ID tiến trình của lớp vỏ hiện tại, chứ không phải lớp con.

Các $$là ID của vỏ hiện tại (không phải là subshell).


1
Thủ thuật hay để mở trang bash trong một phần cụ thể
Daniel Serodio
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.