Bagaimana pengalihan file bash ke standar berbeda dari shell (`sh`) di Linux?

9

Saya menulis sebuah skrip yang mengalihkan pengguna saat menjalankan, dan mengeksekusi menggunakan pengalihan file ke standar. Begitu user-switch.shjuga ...

#!/bin/bash

whoami
sudo su -l root
whoami

Dan menjalankannya dengan bashmemberi saya perilaku yang saya harapkan

$ bash < user-switch.sh
vagrant
root

Namun, jika saya menjalankan skrip dengan sh, saya mendapatkan output yang berbeda

$ sh < user-switch.sh 
vagrant
vagrant

Mengapa bash < user-switch.shmemberikan hasil yang berbeda dari sh < user-switch.sh?

Catatan:

  • terjadi pada dua kotak berbeda menjalankan Debian Jessie
popedotninja
sumber
1
Bukan jawaban, tetapi mengenai sudo su: unix.stackexchange.com/questions/218169/…
Kusalananda
1
apakah / bin / sh = dash pada Debian? Saya tidak dapat melaporkan ulang di RHEL di mana / bin / sh = bash
Jeff Schaller
1
/bin/shpada (salinan saya) Debian adalah Dash, ya. Aku bahkan tidak tahu apa itu sampai tadi. Saya terjebak dengan bash sampai sekarang.
popedotninja
1
The pesta perilaku tidak dijamin untuk bekerja oleh semantik didokumentasikan baik.
Charles Duffy
Seseorang menunjukkan kepada saya hari ini bahwa skrip yang saya jalankan melanggar semantik tentang bagaimana bash dimaksudkan untuk digunakan. Ada beberapa jawaban yang bagus untuk pertanyaan ini, tetapi ada baiknya menunjukkan bahwa orang mungkin tidak boleh beralih pengguna skrip tengah. Saya awalnya mencoba user-switch.shdalam pekerjaan Jenkins, dan skrip dieksekusi. Menjalankan skrip yang sama di luar Jenkins menghasilkan hasil yang berbeda, dan saya terpaksa mengajukan redirection untuk mendapatkan perilaku yang saya lihat di Jenkins.
popedotninja

Jawaban:

12

Script yang serupa, tanpa sudo, tetapi hasil yang serupa:

$ cat script.sh
#!/bin/bash
sed -e 's/^/--/'
whoami

$ bash < script.sh
--whoami

$ dash < script.sh
itvirta

Dengan bash, sisa skrip berjalan sebagai input untuk sed, dengan dash, shell mengartikannya.

Menjalankannya strace: dashmembaca satu blok skrip (delapan kB di sini, lebih dari cukup untuk menampung seluruh skrip), dan kemudian memunculkan sed:

read(0, "#!/bin/bash\nsed -e 's/^/--/'\nwho"..., 8192) = 36
stat("/bin/sed", {st_mode=S_IFREG|0755, st_size=73416, ...}) = 0
clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|...

Yang berarti filehandle ada di akhir file, dan sedtidak akan melihat input apa pun. Bagian yang tersisa sedang disangga di dalam dash. (Jika skrip lebih panjang dari ukuran blok 8 kB, bagian yang tersisa akan dibaca oleh sed.)

Bash, di sisi lain, mencari kembali ke akhir perintah terakhir:

read(0, "#!/bin/bash\nsed -e 's/^/--/'\nwho"..., 36) = 36
stat("/bin/sed", {st_mode=S_IFREG|0755, st_size=73416, ...}) = 0
...
lseek(0, -7, SEEK_CUR)                  = 29
clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|...

Jika input berasal dari pipa, seperti di sini:

$ cat script.sh | bash

rewinding tidak dapat dilakukan, karena pipa dan soket tidak dapat dicari. Dalam hal ini, Bash kembali membaca input karakter satu per satu untuk menghindari overreading. ( fd_to_buffered_stream()dalaminput.c ) Melakukan panggilan sistem penuh untuk setiap byte pada prinsipnya tidak terlalu efektif. Dalam praktiknya, saya tidak berpikir bacaan akan menjadi biaya overhead yang besar dibandingkan misalnya fakta bahwa sebagian besar hal shell melibatkan pemijahan seluruh proses baru.

Situasi serupa adalah ini:

echo -e 'foo\nbar\ndoo' | bash -c 'read a; head -1'

Subshell harus memastikan readhanya membaca baris baru pertama, sehingga headmelihat baris berikutnya. (Ini juga berfungsi dengan baik dash.)


Dengan kata lain, Bash berusaha lebih keras untuk mendukung membaca sumber yang sama untuk skrip itu sendiri, dan untuk perintah yang dijalankan darinya. dashtidak. Itu zsh, dan ksh93dikemas dalam Debian pergi dengan Bash pada ini.

ilkkachu
sumber
1
Terus terang, saya agak terkejut itu berhasil.
ilkkachu
terima kasih atas jawaban yang bagus! Saya akan menjalankan kedua perintah menggunakan stracenanti dan membandingkan output. Saya belum menggunakan pendekatan itu.
popedotninja
2
Ya, reaksi saya membaca pertanyaan itu mengejutkan bahwa itu tidak bekerja dengan pesta - Aku sudah mengajukan diri sebagai sesuatu yang pasti tidak akan bekerja. Saya kira saya hanya setengah benar :)
hobbs
12

Shell membaca skrip dari input standar. Di dalam skrip, Anda menjalankan perintah yang juga ingin membaca input standar. Masukan mana yang akan dituju? Anda tidak bisa mengatakan dengan andal .

Cara shell bekerja adalah mereka membaca sepotong kode sumber, menguraikannya, dan jika mereka menemukan perintah yang lengkap, jalankan perintah, kemudian lanjutkan dengan sisa potongan dan sisa file. Jika chunk tidak berisi perintah lengkap (dengan karakter terminating di akhir - saya pikir semua shell membaca hingga akhir baris), shell membaca chunk lain, dan seterusnya.

Jika perintah dalam skrip mencoba membaca dari deskriptor file yang sama dengan shell membaca skrip, maka perintah akan menemukan apa pun yang datang setelah potongan terakhir yang dibacanya. Lokasi ini tidak dapat diprediksi: tergantung pada ukuran cangkang yang dipilih, dan itu tidak hanya bergantung pada cangkang dan versinya tetapi juga pada konfigurasi mesin, memori yang tersedia, dll.

Bash mencari hingga akhir kode sumber perintah dalam skrip sebelum menjalankan perintah. Ini bukan sesuatu yang dapat Anda andalkan, tidak hanya karena shell lain tidak melakukannya, tetapi juga karena ini hanya berfungsi jika shell membaca dari file biasa. Jika shell membaca dari pipa (misalnya ssh remote-host.example.com <local-script-file.sh), data yang telah dibaca dibaca dan tidak dapat dibaca.

Jika Anda ingin memberikan input ke perintah dalam skrip, Anda harus melakukannya secara eksplisit, biasanya dengan dokumen di sini . (Dokumen di sini biasanya yang paling nyaman untuk input multi-line, tetapi metode apa pun akan melakukannya.) Kode yang Anda tulis hanya berfungsi dalam beberapa shell, hanya jika skrip dilewatkan sebagai input ke shell dari file biasa; jika Anda berharap yang kedua whoamiakan diteruskan sebagai input sudo …, pikirkan lagi, ingat bahwa sebagian besar waktu script tidak diteruskan ke input standar shell.

#!/bin/bash
whoami
sudo su -l root <<'EOF'
whoami
EOF

Perhatikan bahwa dekade ini, dapat Anda gunakan sudo -i root. Menjalankan sudo suadalah peretasan dari masa lalu.

Gilles 'SANGAT berhenti menjadi jahat'
sumber