Apakah tanda kurung benar-benar menempatkan perintah dalam subkulit?

94

Dari apa yang saya baca, menempatkan perintah dalam tanda kurung harus menjalankannya dalam sebuah subkulit, mirip dengan menjalankan skrip. Jika ini benar, bagaimana cara melihat variabel x jika x tidak diekspor?

x=1

Berjalan (echo $x)di baris perintah menghasilkan 1

Menjalankan echo $xskrip tidak menghasilkan apa-apa, seperti yang diharapkan

Igorio
sumber

Jawaban:

134

Subkulit dimulai sebagai salinan hampir identik dari proses shell asli. Di bawah tenda, shell memanggil forksystem call 1 , yang menciptakan proses baru yang kode dan memorinya adalah salinan 2 . Ketika subkulit dibuat, ada sangat sedikit perbedaan antara itu dan induknya. Secara khusus, mereka memiliki variabel yang sama. Bahkan $$variabel khusus menyimpan nilai yang sama dalam subkulit: ini adalah ID proses shell asli. Demikian pula $PPIDPID induk dari shell asli.

Beberapa shell mengubah beberapa variabel dalam subkulit. Bash mengatur BASHPIDke PID dari proses shell, yang berubah dalam subshell. Bash, zsh dan mksh mengatur untuk $RANDOMmenghasilkan nilai yang berbeda di induk dan di subkulit. Namun terlepas dari kasus khusus bawaan seperti ini, semua variabel memiliki nilai yang sama dalam subkulit seperti pada shell asli, status ekspor yang sama, status hanya baca yang sama, dll. Semua definisi fungsi, alias definisi, opsi shell dan pengaturan lain juga diwarisi.

Subkulit yang dibuat oleh (…)memiliki deskriptor file yang sama dengan pembuatnya. Beberapa cara lain untuk membuat subkulit memodifikasi beberapa deskriptor file sebelum mengeksekusi kode pengguna; misalnya, sisi kiri pipa berjalan dalam subkulit 3 dengan output standar yang terhubung ke pipa. Subkulit juga dimulai dengan direktori saat ini yang sama, masker sinyal yang sama, dll. Salah satu dari beberapa pengecualian adalah bahwa subkulit tidak mewarisi jebakan khusus: sinyal yang diabaikan ( ) tetap diabaikan dalam subkulit, tetapi jebakan lain ( SIGNAL ) diatur ulang ke tindakan standar 4 .trap '' SIGNALtrap CODE

Subshell berbeda dari mengeksekusi skrip. Sebuah skrip adalah program terpisah. Program terpisah ini mungkin secara kebetulan juga merupakan skrip yang dijalankan oleh penerjemah yang sama dengan induknya, tetapi kebetulan ini tidak memberikan program terpisah visibilitas khusus pada data internal induk. Variabel yang tidak diekspor adalah data internal, jadi ketika interpreter untuk skrip shell anak dieksekusi , ia tidak melihat variabel-variabel ini. Variabel yang diekspor, yaitu variabel lingkungan, ditransmisikan ke program yang dijalankan.

Jadi:

x=1
(echo $x)

dicetak 1karena subkulit adalah replikasi dari shell yang memunculkannya.

x=1
sh -c 'echo $x'

kebetulan menjalankan shell sebagai proses anak dari shell, tetapi xpada baris kedua tidak memiliki lebih banyak koneksi dengan xpada baris kedua daripada di

x=1
perl -le 'print $x'

atau

x=1
python -c 'print x'

1 Pengecualian adalah ksh93shell di mana forking dioptimalkan keluar dan sebagian besar efek sampingnya ditiru.
2 Semantik, itu salinan. Dari perspektif implementasi, ada banyak sharing yang terjadi.
3 Untuk sisi kanan, tergantung pada cangkangnya.
4 Jika Anda menguji ini, perhatikan bahwa hal-hal seperti$(trap) dapat melaporkan jebakan dari shell asli. Perhatikan juga bahwa banyak cangkang memiliki bug dalam kasus sudut yang melibatkan jebakan. Misalnya ninjalj mencatat bahwa pada bash 4.3, bash -x -c 'trap "echo ERR at \$BASH_SUBSHELL \$BASHPID" ERR; set -E; false; echo one subshell; (false); echo two subshells; ( (false) )'menjalankan ERRjebakan dari subkulit bersarang dalam kasus "dua subkulit", tetapi bukan ERRjebakan dari subkulit perantara - set -Eopsi harus menyebarkanERRjebakan ke semua subkulit tetapi subkulit perantara dioptimalkan dan jadi tidak ada untuk menjalankan ERRjebakannya.

Gilles
sumber
2
@ Kusalananda No. ( x=out; (x=in; echo $x))
Gilles
2
@ flow2k Ini adalah urutan ekspansi untuk hal-hal yang terjadi pada level yang sama. Tetapi Anda juga perlu mempertimbangkan bagaimana ekspansi dicampur dengan evaluasi. Ketika ekspansi membutuhkan evaluasi konstruk bersarang, konstruk batin dievaluasi terlebih dahulu. Jadi, misalnya, untuk mengevaluasi echo $(x=2; echo $x), fragmen $(x=2; echo $x)perlu diperluas. Ini membutuhkan evaluasi perintah x=2; echo $x. Perluasan $xterjadi selama evaluasi ini, setelah mengevaluasi bagian x=2.
Gilles
2
@ flow2k Tidak ada urutan antara ekspansi parameter dan penggantian perintah. Perhatikan bahwa kalimat ini menggunakan titik koma untuk memisahkan langkah ekspansi, tetapi ekspansi parameter dan substitusi perintah berada dalam klausa yang dipisahkan dengan titik koma yang sama (ya, itu halus). Urutan penting ketika salah satu bagian memiliki efek samping yang mempengaruhi bagian lainnya, misalnya ( xtidak disetel) echo $(echo foo >somefile)${x-$(cat somefile)}atau echo $(echo $x),${x=1}.
Gilles
1
@Gilles; Saya bingung. Jika suatu subkulit berbeda dari mengeksekusi skrip maka mengapa dikatakan bahwa: Menjalankan skrip shell meluncurkan proses baru, sebuah subkulit. ? Juga, lingkungan subkulit harus dibuat sebagai duplikat dari lingkungan shell . Oleh karena itu, ./file akan dieksekusi di lingkungan subshell dan karenanya harus mewarisi parameter shell yang ditetapkan oleh penugasan variabel.
jam
2
@ haccks Definisi dalam ABS adalah perkiraan, dan bukan yang sangat bagus. Contohnya bagus, tetapi dua baris pertama dari halaman itu terlalu disederhanakan sehingga mereka salah. Menjalankan skrip dari skrip lain meluncurkan proses baru yang bukan subkulit. Dalam SUS, definisi-definisi itu benar (tetapi tidak selalu sangat mudah dimengerti). ./filetidak dieksekusi dalam subkulit. Lihat juga unix.stackexchange.com/q/261638 dan unix.stackexchange.com/a/157962
Gilles
15

Jelas, ya, seperti semua dokumentasi mengatakan, perintah di dalam tanda kurung dijalankan dalam subkulit.

Subkulit mewarisi salinan dari semua variabel induk. Perbedaannya adalah bahwa setiap perubahan yang Anda buat dalam subkulit tidak juga dibuat di induknya.

Halaman manual ksh membuat ini sedikit lebih jelas daripada halaman bash:

man ksh:

Perintah dalam tanda kurung dieksekusi dalam sub-shell tanpa menghapus variabel yang tidak diekspor.

man bash:

(daftar)

daftar dieksekusi dalam lingkungan subkulit (lihat LINGKUNGAN PERINTAH EKSEKUSI di bawah). Penugasan variabel dan perintah bawaan yang memengaruhi lingkungan shell tidak tetap berlaku setelah perintah selesai.

LINGKUNGAN EKSEKUSI PERINTAH

Shell memiliki lingkungan eksekusi, yang terdiri dari yang berikut: [...] parameter shell yang ditetapkan oleh penugasan variabel [...].
Substitusi perintah, perintah yang dikelompokkan dengan tanda kurung, dan perintah asinkron dipanggil dalam lingkungan subkulit yang merupakan duplikat dari lingkungan shell, [...]

Mikel
sumber
3
Ini harus dikontraskan dengan When a simple command other than a builtin or shell function is to be executed, it is invoked in a separate execution environment that consists of the following., yang berisi item: · shell variables and functions marked for export, along with variables exported for the command, passed in the environment(dari bagian yang sama man bash) yang menjelaskan mengapa echo $x-script tidak mencetak apa-apa jika xtidak diekspor.
Johan E