Aturan untuk memanggil subkulit di Bash?

24

Saya tampaknya salah paham aturan Bash untuk membuat subkulit. Saya pikir tanda kurung selalu membuat subkulit, yang berjalan sebagai prosesnya sendiri.

Namun, sepertinya tidak demikian. Dalam Cuplikan Kode A (di bawah), sleepperintah kedua tidak berjalan di shell yang terpisah (sebagaimana ditentukan oleh pstreeterminal lain). Namun, di Cuplikan Kode B, kedua sleepperintah tidak dijalankan di shell terpisah. Satu-satunya perbedaan antara snippet adalah bahwa snippet kedua memiliki dua perintah di dalam tanda kurung.

Bisakah seseorang tolong jelaskan aturan ketika subkulit dibuat?

SNIPPET KODE A:

sleep 5
(
sleep 5
)

SNIPPET KODE B:

sleep 5
(
x=1
sleep 5
)
malu
sumber

Jawaban:

20

Tanda kurung selalu memulai subkulit. Apa yang terjadi adalah bahwa bash mendeteksi bahwa itu sleep 5adalah perintah terakhir yang dijalankan oleh subkulit itu, sehingga ia memanggil execalih-alih fork+ exec. The sleepperintah menggantikan subkulit dalam proses yang sama.

Dengan kata lain, kasus dasarnya adalah:

  1. ( … )buat subkulit. Proses asli memanggil forkdan wait. Dalam subproses, yang merupakan subkulit:
    1. sleepadalah perintah eksternal yang memerlukan subproses dari subproses. Panggilan subkulit forkdan wait. Dalam sub-proses:
      1. Sub-proses menjalankan perintah eksternal → exec.
      2. Akhirnya perintah berakhir → exit.
    2. wait selesai di subkulit.
  2. wait selesai dalam proses aslinya.

Optimasinya adalah:

  1. ( … )buat subkulit. Proses asli memanggil forkdan wait. Dalam subproses, yang merupakan subkulit hingga panggilan exec:
    1. sleep adalah perintah eksternal, dan ini adalah hal terakhir yang perlu dilakukan proses ini.
    2. Subproses menjalankan perintah eksternal → exec.
    3. Akhirnya perintah berakhir → exit.
  2. wait selesai dalam proses aslinya.

Ketika Anda menambahkan sesuatu yang lain setelah panggilan sleep, subshell perlu dijaga, sehingga optimasi ini tidak dapat terjadi.

Ketika Anda menambahkan sesuatu yang lain sebelum dipanggil sleep, optimasi dapat dilakukan (dan ksh melakukannya), tetapi bash tidak melakukannya (sangat konservatif dengan optimasi ini).

Gilles 'SANGAT berhenti menjadi jahat'
sumber
Subkulit dibuat dengan memanggil forkdan proses anak dibuat (untuk menjalankan perintah eksternal) dengan memanggilfork + exec . Tapi para Anda yang pertama menyarankan agar fork + execdipanggil juga untuk subkulit. Apa yang salah di sini?
pukul
1
@haccks fork+ exectidak dipanggil untuk subshell, itu dipanggil untuk perintah eksternal. Tanpa optimasi, ada forkpanggilan untuk subkulit dan satu lagi untuk perintah eksternal. Saya telah menambahkan uraian alur terperinci ke jawaban saya.
Gilles 'SO- stop being evil'
Terima kasih banyak untuk pembaruan ini. Sekarang ini menjelaskan lebih baik. Saya dapat menyimpulkan bahwa dalam kasus (...)(dalam kasus dasar), mungkin ada atau tidak ada panggilan untuk execbergantung pada apakah subkulit memiliki perintah eksternal untuk dieksekusi, sedangkan dalam kasus mengeksekusi perintah eksternal harus ada fork + exec.
pukul
Satu pertanyaan lagi: Apakah optimasi ini hanya berfungsi untuk subkulit atau dapat dilakukan untuk perintah seperti datedi shell?
pukul
@haccks Saya tidak mengerti pertanyaannya. Pengoptimalan ini adalah tentang menjalankan perintah eksternal sebagai hal terakhir yang dilakukan proses shell. Itu tidak terbatas pada subkulit: bandingkan strace -f -e clone,execve,write bash -c 'date'danstrace -f -e clone,execve,write bash -c 'date; true'
Gilles 'SANGAT berhenti menjadi jahat'
4

Dari Panduan Pemrograman Bash Lanjutan :

"Secara umum, perintah eksternal dalam skrip memotong dari suatu proses, sedangkan Bash builtin tidak. Untuk alasan ini, builtin mengeksekusi lebih cepat dan menggunakan lebih sedikit sumber daya sistem daripada setara dengan perintah eksternal mereka."

Dan sedikit lebih jauh ke bawah:

"Daftar perintah yang disematkan di antara tanda kurung berjalan sebagai subkulit."

Contoh:

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

Contoh menggunakan kode OP (dengan tidur lebih pendek karena saya tidak sabar):

echo $BASHPID

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

Hasil:

[root@talara test]# bash sub_bash
6606
6608
6608
Tim
sumber
2
Terima kasih atas balasan Tim. Saya tidak yakin itu sepenuhnya menjawab pertanyaan saya. Karena "Daftar perintah yang disematkan di antara tanda kurung berjalan sebagai subkulit", saya akan berharap yang kedua sleepberjalan dalam subkulit (mungkin pada proses subkulit karena ini merupakan bawaan, bukan proses dalam subkulit). Namun, dalam hal apa pun, saya akan mengharapkan subkulit ada, yaitu subproses Bash di bawah proses induk Bash. Untuk Cuplikan B di atas, sepertinya tidak demikian.
malu
Koreksi: Karena sleepsepertinya bukan built-in, saya berharap sleeppanggilan kedua di kedua snippet berjalan di subproses proses subshell.
malu
@ malu Saya mengambil kebebasan untuk meretas kode Anda dengan $BASHPIDvariabel saya . Sayangnya cara Anda melakukannya tidak memberi Anda seluruh cerita yang saya percaya. Lihat hasil tambahan saya di jawabannya.
Tim
4

Catatan tambahan untuk jawaban @Gilles.

Seperti yang dikatakan oleh Gilles: The parentheses always start a subshell.

Namun, angka yang mungkin dimiliki oleh sub-shell tersebut:

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

Seperti yang Anda lihat, $$ terus berulang, dan itu seperti yang diharapkan, karena (jalankan perintah ini untuk menemukan man bashbaris yang benar ):

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

BASHPID
Memperluas ID proses dari proses bash saat ini. Ini berbeda dari $$ dalam keadaan tertentu, seperti subkulit yang tidak memerlukan bash untuk diinisialisasi ulang.

Yaitu: Jika shell tidak diinisialisasi ulang, $$ adalah sama.

Atau dengan ini:

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

Parameter Khusus
$ Perluas ke ID proses shell. Dalam subkulit (), itu mengembang ke ID proses shell saat ini, bukan subkulit.

Ini $$adalah ID dari shell saat ini (bukan subshell).


sumber
1
Trik yang bagus untuk membuka halaman bash di bagian tertentu
Daniel Serodio