Làm thế nào tôi có thể phát hiện nếu tôi ở trong một subshell?


24

Tôi đang cố gắng viết một hàm để thay thế chức năng của exitnội trang để ngăn bản thân thoát khỏi thiết bị đầu cuối.

Tôi đã cố gắng sử dụng SHLVLbiến môi trường nhưng dường như nó không thay đổi trong các khung con:

$ echo $SHLVL
1
$ ( echo $SHLVL )
1
$ bash -c 'echo $SHLVL'
2

Chức năng của tôi như sau:

exit () {
    if [[ $SHLVL -eq 1 ]]; then
        printf '%s\n' "Nice try!" >&2
    else
        command exit
    fi
}

Điều này sẽ không cho phép tôi sử dụng exittrong các subshells mặc dù:

$ exit
Nice try!
$ (exit)
Nice try!

Một phương pháp tốt để phát hiện xem tôi có ở trong một mạng con hay không?



1
Đó là vì điều này . $ SHLVL là 1 vì bạn vẫn ở trình bao cấp 1 mặc dù lệnh echo $ SHLVL được chạy trong "subshell". Theo bài đăng đó, các subshells sinh ra với dấu ngoặc đơn (...)kế thừa tất cả các thuộc tính của quá trình cha. Các câu trả lời được cung cấp là các giải pháp mạnh mẽ hơn để xác định mức độ vỏ của bạn.
kemotep


5
@mosvy Tôi cảm thấy như đó là một câu hỏi khác. ví dụ: BASH_SUBSHELLcâu trả lời (ngay cả khi gây tranh cãi) sẽ không áp dụng cho câu hỏi đó.
Sparhawk

2
Nhìn thấy tiêu đề trên HNQ và nghĩ rằng đây là một câu hỏi cơ học lượng tử ...
Mehrdad

Câu trả lời:


43

Trong bash, bạn có thể so sánh $BASHPIDvới$$

$ ( if [ "$$" -eq "$BASHPID" ]; then echo not subshell; else echo subshell; fi )
subshell
$   if [ "$$" -eq "$BASHPID" ]; then echo not subshell; else echo subshell; fi
not subshell

Nếu bạn không ở trong bash, $$nên giữ nguyên trong một lớp con, vì vậy bạn cần một số cách khác để nhận ID quy trình thực tế của mình.

Một cách để có được pid thực tế của bạn là sh -c 'echo $PPID'. Nếu bạn chỉ đặt nó ở một nơi đơn giản, ( … )nó có vẻ không hoạt động, vì vỏ của bạn đã tối ưu hóa ngã ba. Hãy thử các lệnh no-op bổ sung ( : ; sh -c 'echo $PPID'; : )để làm cho nó nghĩ rằng lớp con quá phức tạp để tối ưu hóa đi. Tín dụng vào John1024 trên Stack Overflow cho cách tiếp cận đó.


Bạn có thể muốn thay đổi điều đó thành  (sh -c 'echo $PPID'; : )- xem nhận xét của tôi về câu trả lời của John1024 .
G-Man nói 'Phục hồi Monica'

@ G-Man Chà, đó chỉ là để thử nghiệm nó (vì trong thực tế sử dụng nó sẽ phức tạp hơn) ... nhưng vâng, sẽ tốt nhất nếu thử nghiệm hoạt động trong tất cả các vỏ. Vì vậy, tôi đã đặt không có cả trước và sau, hy vọng sẽ xử lý mọi thứ.
derobert

38

Thế còn BASH_SUBSHELL?

BASH_SUBSHELL Được
      tăng thêm bởi một trong mỗi môi trường lớp con hoặc lớp con khi lớp vỏ
      bắt đầu thực thi trong môi trường đó. Giá trị ban đầu là 0.

$ echo $BASH_SUBSHELL
0
$ (echo $BASH_SUBSHELL)
1

16
Nó sẽ là một lệnh thuận tiện trong bộ phim Inception.
Eric Duminil

Khi bắt đầu, có lẽ $ SHLVL
Granny Aching

19

[đây phải là một bình luận, nhưng bình luận của tôi có xu hướng bị xóa bởi người kiểm duyệt, vì vậy đây sẽ là một câu trả lời mà tôi có thể sử dụng nó làm tài liệu tham khảo ngay cả khi bị xóa]

Việc sử dụng BASH_SUBSHELLlà hoàn toàn không đáng tin cậy vì nó chỉ được đặt thành 1 trong một số lớp con, không phải trong tất cả các lớp con.

$ (echo $BASH_SUBSHELL)
1
$ echo $BASH_SUBSHELL | cat
0

Trước khi tuyên bố rằng quy trình con, một lệnh đường ống được chạy không phải là một nhánh con thực sự, hãy xem xét man bashđoạn trích này :

Mỗi lệnh trong một đường ống được thực thi như một quy trình riêng biệt (nghĩa là trong một lớp con).

và ý nghĩa thực tế - đó là liệu một đoạn script có chạy một quy trình con hay không là điều cần thiết, không phải là một số thuật ngữ phân biệt.

Giải pháp duy nhất, như đã được giải thích trong các câu trả lời cho câu hỏi này là kiểm tra xem $BASHPIDbằng $$hay không, có hiệu quả nhưng kém hiệu quả hơn nhiều:

if [ "$(exec sh -c 'echo "$PPID"')" != "$$" ]; then
    echo you\'re in a subshell
fi

11
Nit: BASH_SUBSHELLđược thiết lập khá đáng tin cậy, nhưng nhận được giá trị của nó một cách chính xác là iffy. Lưu ý những gì các tài liệu nói: "Được tăng thêm bởi một trong mỗi môi trường lớp con hoặc lớp con khi lớp vỏ bắt đầu thực thi trong môi trường đó. " Tôi nghĩ rằng trong ví dụ về đường ống, bash chưa bắt đầu thực thi trong lớp con đó khi biến được mở rộng. Bạn có thể so sánh echo $BASH_VERSIONvới declare -p BASH_VERSION- phần sau đáng tin cậy đầu ra 1 với các đường ống, công việc nền, v.v.
muru

6
Thậm chí nói, eval 'echo $BASH_SUBSHELL $BASHPID' | catsẽ xuất 1 cho BASH_SUBSHELL, vì biến được mở rộng sau khi thực thi đã bắt đầu.
muru

4
tất cả các đối số đó cũng nên áp dụng cho quy trình & thay thế lệnh, quy trình bg, nhưng đó chỉ là các đường ống khác nhau. Nhìn vào mã, gia tăng subshell_levelthực sự được hoãn lại trong trường hợp đường ống tiền cảnh , có thể có một số lý do, nhưng tôi không thể tìm ra ;-)
mosvy

2
Bạn đúng. Có vẻ Chet rõ ràng dự định theo cách đó. list.gnu.org/archive/html/orms-bash/2015-06/msg00050.html : "BASH_SUBSHELL biện pháp (...) các phần tử con, không phải các phần tử đường ống." list.gnu.org/archive/html/orms-bash/2015-06/msg00054.html : "Tôi sẽ suy nghĩ về việc liệu tôi nên ghi lại hiện trạng hay mở rộng định nghĩa của 'subshell' mà $ BASH_SUBSHELL phản ánh. "
muru

2
@JoL bạn đã sai, việc mở rộng cũng xảy ra trong quy trình riêng biệt, vui lòng đọc các liên kết và ví dụ từ cuộc thảo luận ở trên; hoặc chỉ cần thử với echo $$ $BASHPID $BASH_SUBSHELL | cat.
mosvy
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.