Bagaimana saya bisa mendeteksi jika saya dalam subkulit?

24

Saya mencoba menulis fungsi untuk menggantikan fungsi exitbuiltin untuk mencegah saya keluar dari terminal.

Saya telah mencoba menggunakan SHLVLvariabel lingkungan tetapi tampaknya tidak berubah di dalam subkulit:

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

Fungsi saya adalah sebagai berikut:

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

Ini tidak memungkinkan saya untuk menggunakan exitdalam subkulit:

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

Apa metode yang baik untuk mendeteksi apakah saya dalam subkulit atau tidak?

Jesse_b
sumber
1
Itu karena ini . $ SHLVL adalah 1 karena Anda masih di shell level 1 meskipun perintah $ SHLVL echo dijalankan dalam "subshell". Menurut posting itu, subkulit yang muncul dengan tanda kurung (...)mewarisi semua properti dari proses induk. Jawaban yang diberikan adalah solusi yang lebih kuat untuk menentukan level shell Anda.
kemotep
5
@ Mosvy Saya merasa itu adalah pertanyaan yang berbeda. mis. BASH_SUBSHELLjawabannya (walaupun kontroversial) tidak berlaku untuk pertanyaan itu.
Sparhawk
2
Melihat judul di HNQ dan berpikir ini adalah pertanyaan mekanika kuantum ...
Mehrdad

Jawaban:

43

Di bash, Anda dapat membandingkan $BASHPIDke$$

$ ( 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

Jika Anda tidak dalam bash, $$harus tetap sama dalam subkulit, jadi Anda perlu cara lain untuk mendapatkan ID proses yang sebenarnya.

Salah satu cara untuk mendapatkan pid Anda yang sebenarnya adalah sh -c 'echo $PPID'. Jika Anda hanya meletakkannya di dataran ( … ), sepertinya tidak berfungsi, karena shell Anda telah mengoptimalkan garpu. Coba perintah no-op tambahan ( : ; sh -c 'echo $PPID'; : )untuk membuatnya berpikir subshell terlalu rumit untuk dioptimalkan. Kredit jatuh ke John1024 di Stack Overflow untuk pendekatan itu.

derobert
sumber
Anda mungkin ingin mengubahnya menjadi  (sh -c 'echo $PPID'; : )- lihat komentar saya pada jawaban John1024 .
G-Man Mengatakan 'Reinstate Monica'
@ G-Man Yah, itu hanya untuk mengujinya (karena dalam penggunaan sebenarnya itu akan menjadi sesuatu yang lebih rumit) ... tapi ya, akan lebih baik jika tes bekerja di semua shell. Jadi saya telah meletakkan no-op sebelum dan sesudah, yang diharapkan akan menangani semuanya.
derobert
38

Bagaimana dengan BASH_SUBSHELL?

BASH_SUBSHELL Ditambah
      oleh satu di dalam setiap lingkungan subkulit atau subkulit saat shell
      mulai mengeksekusi di lingkungan itu. Nilai awal adalah 0.

$ echo $BASH_SUBSHELL
0
$ (echo $BASH_SUBSHELL)
1
Freddy
sumber
16
Itu akan menjadi perintah yang nyaman di Inception film.
Eric Duminil
Dalam Inception mungkin $ SHLVL
Granny
19

[ini seharusnya komentar, tetapi komentar saya cenderung dihapus oleh moderator, jadi ini akan tetap sebagai jawaban yang bisa saya gunakan sebagai referensi bahkan jika dihapus]

Menggunakan BASH_SUBSHELLsepenuhnya tidak dapat diandalkan karena hanya diatur ke 1 di beberapa subkulit, tidak di semua subkulit.

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

Sebelum mengklaim bahwa subproses perintah pipeline dijalankan bukan subshell yang benar - benar nyata, pertimbangkan man bashpotongan ini :

Setiap perintah dalam sebuah pipa dieksekusi sebagai proses terpisah (yaitu, dalam subkulit).

dan implikasi praktis - apakah fragmen skrip menjalankan subproses atau tidak yang penting, bukan beberapa terminologi berdalih.

Satu-satunya solusi, seperti yang sudah dijelaskan dalam jawaban untuk pertanyaan ini adalah untuk memeriksa apakah $BASHPIDsama $$atau, mudah dibawa tetapi jauh lebih efisien:

if [ "$(exec sh -c 'echo "$PPID"')" != "$$" ]; then
    echo you\'re in a subshell
fi
mosvy
sumber
11
Nit: BASH_SUBSHELLdiatur cukup andal, tetapi mendapatkan nilainya dengan benar adalah rapuh. Perhatikan apa yang dikatakan dokumen : "Bertambah satu dalam setiap subkulit atau lingkungan subkulit ketika shell mulai mengeksekusi di lingkungan itu. " Saya pikir bahwa dalam contoh pipa, bash belum memulai mengeksekusi dalam subkulit itu ketika variabel diperluas. Anda dapat membandingkan echo $BASH_VERSIONdengan declare -p BASH_VERSION- yang terakhir harus andal keluaran 1 dengan pipa, pekerjaan latar belakang, dll
muru
6
Bahkan mengatakan, eval 'echo $BASH_SUBSHELL $BASHPID' | catakan menghasilkan 1 untuk BASH_SUBSHELL, karena variabel diperluas setelah eksekusi dimulai.
muru
4
semua argumen itu juga harus berlaku untuk proses & perintah substitusi, proses bg, namun hanya pipa yang berbeda. Melihat kode, penambahan subshell_levelbenar-benar ditangguhkan dalam kasus pipa latar depan , yang mungkin memiliki beberapa alasan, tetapi yang saya tidak bisa melihat ;-)
mosvy
2
Kamu benar. Sepertinya Chet secara eksplisit bermaksud seperti itu. lists.gnu.org/archive/html/bug-bash/2015-06/msg00050.html : "BASH_SUBSHELL mengukur subshell (...), bukan elemen pipeline." lists.gnu.org/archive/html/bug-bash/2015-06/msg00054.html : "Saya akan berpikir tentang apakah saya harus mendokumentasikan status quo atau memperluas definisi` subkulit 'yang dicerminkan oleh $ BASH_SUBSHELL. "
muru
2
@ JoL Anda salah, ekspansi terjadi dalam proses terpisah juga, silakan baca tautan dan contoh dari diskusi ini di atas; atau coba saja echo $$ $BASHPID $BASH_SUBSHELL | cat.
Mosvy