Mengapa bashrc memeriksa apakah shell saat ini interaktif?

62

Di Arch saya pasang, /etc/bash.bashrcdan /etc/skel/.bashrcmengandung baris-baris ini:

# If not running interactively, don't do anything
[[ $- != *i* ]] && return

Di Debian, /etc/bash.bashrcmemiliki:

# If not running interactively, don't do anything
[ -z "$PS1" ] && return

Dan /etc/skel/.bashrc:

# If not running interactively, don't do anything
case $- in
    *i*) ;;
      *) return;;
esac

Menurut man bash, namun, kerang non-interaktif bahkan tidak membaca file-file ini:

   When  bash  is  started  non-interactively,  to run a shell script, for
   example, it looks for the variable BASH_ENV in the environment, expands
   its  value if it appears there, and uses the expanded value as the name
   of a file to read and execute.  Bash behaves as if the  following  com
   mand were executed:
          if [ -n "$BASH_ENV" ]; then . "$BASH_ENV"; fi
   but  the value of the PATH variable is not used to search for the file
   name.

Jika saya mengerti dengan benar, *.bashrcfile hanya akan dibaca jika BASH_ENVdiatur untuk mengarahkannya. Ini adalah sesuatu yang tidak dapat terjadi secara kebetulan dan hanya akan terjadi jika seseorang telah secara eksplisit mengatur variabel yang sesuai.

Itu tampaknya mematahkan kemungkinan memiliki skrip sumber pengguna .bashrcsecara otomatis dengan menetapkan BASH_ENV, sesuatu yang bisa berguna. Mengingat bahwa bash tidak akan pernah membaca file-file ini ketika dijalankan secara non-interaktif kecuali secara eksplisit disuruh melakukannya, mengapa *bashrcfile-file default tidak memperbolehkannya?

terdon
sumber
5
Untuk jawaban praktis, lihat SCP gagal tanpa kesalahan
Gilles 'SO-stop being evil'

Jawaban:

69

Ini adalah pertanyaan yang akan saya posting di sini beberapa minggu yang lalu. Seperti terdon , saya mengerti bahwa a .bashrchanya bersumber untuk shell Bash interaktif sehingga tidak perlu untuk .bashrcmemeriksa apakah itu berjalan dalam shell interaktif. Yang membingungkan, semua distribusi yang saya gunakan (Ubuntu, RHEL dan Cygwin) memiliki beberapa jenis pemeriksaan (pengujian $-atau $PS1) untuk memastikan shell saat ini bersifat interaktif. Saya tidak suka pemrograman pemujaan kargo, jadi saya mulai memahami tujuan kode ini di blog saya .bashrc.

Bash memiliki kasing khusus untuk kerang jarak jauh

Setelah meneliti masalah ini, saya menemukan bahwa shell jarak jauh diperlakukan secara berbeda. Sementara shell Bash non-interaktif biasanya tidak menjalankan ~/.bashrcperintah saat start-up, sebuah kasus khusus dibuat ketika shell dipanggil oleh daemon shell jarak jauh :

Bash mencoba untuk menentukan kapan sedang dijalankan dengan input standar yang terhubung ke koneksi jaringan, seperti ketika dieksekusi oleh daemon shell jarak jauh, biasanya rshd, atau daemon shell aman sshd. Jika Bash menentukan sedang dijalankan dengan cara ini, ia membaca dan mengeksekusi perintah dari ~ / .bashrc, jika file itu ada dan dapat dibaca. Ini tidak akan melakukan ini jika dipanggil sebagai sh. The --norcpilihan dapat digunakan untuk menghambat perilaku ini, dan --rcfilepilihan dapat digunakan untuk memaksa file lain untuk dibaca, tetapi tidak rshdjuga sshdumumnya memohon shell dengan orang-orang pilihan atau memungkinkan mereka untuk ditentukan.

Contoh

Masukkan yang berikut ini di awal remote .bashrc. (Jika .bashrcdipasok oleh .profileatau .bash_profile, nonaktifkan sementara ini saat pengujian):

echo bashrc
fun()
{
    echo functions work
}

Jalankan perintah berikut secara lokal:

$ ssh remote_host 'echo $- $0'
bashrc
hBc bash
  • Tidak idalam $-menunjukkan bahwa shell itu non-interaktif .
  • Tidak ada terkemuka -di $0menunjukkan bahwa shell tidak shell login .

Fungsi-fungsi Shell yang didefinisikan di remote .bashrcjuga dapat dijalankan:

$ ssh remote_host fun
bashrc
functions work

Saya melihat bahwa ~/.bashrcini hanya bersumber ketika perintah ditetapkan sebagai argumen untuk ssh. Ini masuk akal: ketika sshdigunakan untuk memulai shell login biasa, .profileatau .bash_profiledijalankan (dan .bashrchanya bersumber jika dilakukan secara eksplisit oleh salah satu file ini).

Manfaat utama yang bisa saya lihat .bashrcberasal dari menjalankan perintah remote (non-interaktif) adalah bahwa fungsi shell dapat dijalankan. Namun, sebagian besar perintah dalam tipikal .bashrchanya relevan dalam shell interaktif, misalnya, alias tidak diperluas kecuali shell tersebut interaktif.

Transfer file jarak jauh bisa gagal

Ini biasanya bukan masalah ketika rshatau sshdigunakan untuk memulai shell login interaktif atau ketika shell non-interaktif digunakan untuk menjalankan perintah. Namun, ini bisa menjadi masalah untuk program seperti rcp, scpdan sftpyang menggunakan kerang remote untuk mentransfer data.

Ternyata shell default pengguna jarak jauh (seperti Bash) secara implisit dimulai ketika menggunakan scpperintah. Tidak disebutkan tentang ini di halaman manual - hanya menyebutkan yang scpdigunakan sshuntuk transfer datanya. Ini memiliki konsekuensi bahwa jika .bashrcberisi perintah yang mencetak ke output standar, transfer file akan gagal , misalnya, scp gagal tanpa kesalahan .

Lihat juga laporan bug Red Hat terkait ini dari 15 tahun yang lalu, scp rusak ketika ada perintah echo di / etc / bashrc (yang akhirnya ditutup sebagai WONTFIX).

Kenapa scpdan sftpgagal

SCP (Secure copy) dan SFTP (Secure File Transfer Protocol) memiliki protokol sendiri untuk tujuan lokal dan jarak jauh untuk bertukar informasi tentang file yang sedang ditransfer. Teks yang tidak terduga dari ujung jarak jauh (salah) ditafsirkan sebagai bagian dari protokol dan transfer gagal. Menurut sebuah FAQ dari Snail Book

Yang sering terjadi, meskipun, adalah bahwa ada pernyataan dalam baik sistem atau file shell startup per-user di server ( .bashrc, .profile, /etc/csh.cshrc, .login, dll) yang keluaran pesan teks pada login, dimaksudkan untuk dibaca oleh manusia (seperti fortune, echo "Hi there!", dll.)

Kode tersebut hanya akan menghasilkan output pada login interaktif, ketika ada ttyinput standar yang dilampirkan. Jika tidak melakukan tes ini, itu akan menyisipkan pesan teks ini di mana mereka tidak termasuk: dalam hal ini, mencemari aliran protokol antara scp2/ sftpdan sftp-server.

Alasan mengapa file startup shell relevan sama sekali, adalah karena sshd mempekerjakan shell pengguna ketika memulai program apa pun atas nama pengguna (menggunakan mis. / Bin / sh -c "command"). Ini adalah tradisi Unix, dan memiliki kelebihan:

  • Pengaturan biasa pengguna (alias perintah, variabel lingkungan, umask, dll.) Berlaku ketika perintah jarak jauh dijalankan.
  • Praktik umum pengaturan shell akun ke / bin / false untuk menonaktifkannya akan mencegah pemilik menjalankan perintah apa pun, seandainya otentikasi secara tidak sengaja berhasil karena suatu alasan.

Detail protokol SCP

Bagi mereka yang tertarik dengan detail tentang cara kerja SCP, saya menemukan informasi menarik di Bagaimana protokol SCP bekerja yang mencakup detail tentang Menjalankan scp dengan profil shell latah di sisi jarak jauh? :

Misalnya, ini bisa terjadi jika Anda menambahkan ini ke profil shell Anda di sistem jarak jauh:

gema ""

Kenapa hanya hang? Itu datang dari cara bagaimana scpdalam mode sumber menunggu konfirmasi pesan protokol pertama. Jika bukan biner 0, ia mengharapkan pemberitahuan masalah jarak jauh dan menunggu lebih banyak karakter untuk membentuk pesan kesalahan hingga baris baru tiba. Karena Anda tidak mencetak baris baru setelah yang pertama, lokal Anda scphanya tinggal dalam satu lingkaran, diblokir read(2). Sementara itu, setelah profil shell diproses pada sisi jarak jauh, scpdalam mode sink dimulai, yang juga memblokir read(2), menunggu nol biner yang menunjukkan dimulainya transfer data.

Kesimpulan / TLDR

Sebagian besar pernyataan dalam tipikal .bashrchanya berguna untuk shell interaktif - bukan ketika menjalankan perintah jarak jauh dengan rshatau ssh. Dalam sebagian besar situasi seperti itu, pengaturan variabel shell, alias dan fungsi mendefinisikan tidak diinginkan - dan mencetak teks apa pun ke standar keluar secara aktif berbahaya jika mentransfer file menggunakan program seperti scpatau sftp. Keluar setelah memverifikasi bahwa shell saat ini adalah non-interaktif adalah perilaku paling aman untuk .bashrc.

Anthony G - keadilan untuk Monica
sumber
artikel yang bagus. tetapi saya harus melakukan itu? saya memang sudah check in oleh .bashrc tapi masih scp keluar dengan 0 kode dan tidak menyalin apa pun ke kotak jauh
ses
@ses Sebaiknya Anda mengajukan pertanyaan baru di mana Anda dapat menggambarkan situasi Anda dengan lebih rinci.
Anthony G - keadilan untuk Monica
16

Halaman manual lupa menyebutkan bahwa bashjuga sumber .bashrcuntuk shell remote non-interaktif, seperti pada

ssh hostname command

http://git.savannah.gnu.org/cgit/bash.git/tree/shell.c#n1010

 COMMAND        EXECUTE BASHRC
 --------------------------------
 bash -c foo        NO
 bash foo           NO
 foo                NO
 rsh machine ls     YES (for rsh, which calls 'bash -c')
 rsh machine foo    YES (for shell started by rsh) NO (for foo!)
 echo ls | bash     NO
 login              NO
 bash               YES

http://git.savannah.gnu.org/cgit/bash.git/tree/shell.c#n1050

          /* If we were run by sshd or we think we were run by rshd, execute
             ~/.bashrc if we are a top-level shell. */
          if ((run_by_ssh || isnetconn (fileno (stdin))) && shell_level < 2)
            {
              maybe_execute_file (SYS_BASHRC, 1);
              maybe_execute_file (bashrc_file, 1);
Mikel
sumber
Apakah Anda mengambil .bashrc dari .bash_profile (atau .profile) Anda?
glenn jackman
Ya, tapi bukan itu intinya. Kosong .bash_profileakan menunjukkan perilaku yang sama.
Mikel
Tidak yakin apa yang ditunjukkan kepada saya. Apakah Anda mengatakan bahwa rsh(yang saya tidak pernah menggunakan) sumber ~/.bashrc? Dan itu, dengan ekstensi, distro Linux memblokirnya untuk bashberjaga-jaga kalau-kalau ada yang mencoba menjalankan skrip dengan rsh? ssh servertidak boleh disentuh .bashrckarena itu adalah shell login. Tidak, kecuali jika sistem Anda adalah salah satu dari yang sumber ~ / .bashrc` dari ~/.profile. Dan ssh server commandseharusnya tidak melakukan itu. Tentu tidak di sistem saya.
terdon
2
Tidak. Saya menautkan Anda ke bashsumbernya, bukan rshsumbernya. :) Komentar berlaku sshjuga, itu baru saja ditulis sejak lama. Lihat jawaban yang diperbarui dengan lebih banyak sumber.
Mikel
Ah, itu membantu, terima kasih. Bagaimana saya menguji ini? Saya mencoba menambahkan sebuah echodi bagian paling atas /etc/bash.bashrcdan ~/.bashrc(sebelum tes shell interaktif) dan kemudian ssh ke mesin target dengan ssh server hostnamedan saat hostnamedijalankan, saya tidak melihat salah satu gema saya. Apakah saya shell_level(apa pun itu) bukan saya <2?
terdon
5

Dengan konvensi, .bashrcadalah tempat di mana pengguna menyimpan konfigurasi khusus untuk shell.

Konfigurasi penyesuaian ini dapat berupa variabel lingkungan, alias, perintah cepat. Dengan shell non-interaktif, kekurangan itu tidak ada artinya. Selain itu, shell non-interaktif dapat dipanggil dalam banyak konteks, Anda tidak yakin variabel lingkungan tersebut dapat mengarah ke kasus negatif palsu, atau bahkan rentan terhadap keamanan.

Contoh terdekat adalah alias seperti:

alias cp='cp -i'

Kemudian menggantung shell non-interaktif Anda selamanya.

Jadi, periksa dilakukan di atas .bashrcuntuk memastikan bahwa kami tidak akan mendapatkan masalah.


Karena shell dapat disebut sebagai shell login non-interaktif , jadi secara eksplisit memblokir sumber *bashrctidak masuk akal.

Ketika shell itu disebut sebagai shell login non-interaktif , itu sumber /etc/profile, maka sumber yang pertama ditemukan di ~/.bash_profile, ~/.bash_login, dan ~/.profile:

Ketika bash dipanggil sebagai shell login interaktif, atau sebagai shell non-interaktif dengan opsi --login , ia pertama kali membaca dan mengeksekusi perintah dari file / etc / profile, jika file itu ada. Setelah membaca file itu, ia mencari ~ / .bash_profile, ~ / .bash_login, dan ~ / .profile, dalam urutan itu, dan membaca serta mengeksekusi perintah dari yang pertama yang ada dan dapat dibaca.

Tidak ada yang mencegah file-file itu dari sumber .bashrcitu sendiri, jadi melakukan pemeriksaan di dalam .bashrclebih aman dan membuat hal-hal sederhana.

cuonglm
sumber
Ya, aku tahu itu. Inilah mengapa cangkang non-interaktif tidak sumber *bashrcfile sama sekali. Pertanyaan saya adalah mengapa berbagai vendor Linux menghadapi masalah dengan secara eksplisit memblokir cangkang non-interaktif dari membaca file-file ini jika file-file ini tidak dibaca oleh cangkang non-interaktif.
terdon
1
@terdon: Karena shell dapat disebut sebagai shell login non-interaktif , maka shell login akan menjadi sumber .bash_profile, yang dapat menjadi sumber .bashrc.
cuonglm
Tidak, dalam cangkang non-interaktif, satu-satunya hal yang dibaca adalah $BASH_ENV. Keduanya *profiledan *bashrcdiabaikan. Atau, setidaknya, itulah yang dikatakan halaman manual. Namun, seperti yang Mikel tunjukkan, halaman manual itu mungkin bohong.
terdon
@terdon: Baca jawaban saya yang diperbarui dengan tautan di dalamnya. Sudahkah Anda mencoba bash -l -c :menggunakan shell login non-interaktif?
cuonglm
Wow. Anda benar sekali. Saya sudah membaca bagian itu sekitar --login100 kali tetapi, tampaknya, itu tidak pernah terdaftar. Terima kasih!
terdon