Program Bash tidak dijalankan jika pengalihan akan gagal

9

Dalam bash, saya perhatikan bahwa jika perintah menggunakan pengalihan akan gagal, program apa pun yang berjalan sebelum itu tidak dijalankan.

Misalnya, program ini membuka file "a" dan menulis 50 byte ke file "a". Namun, menjalankan perintah ini dengan pengalihan ke file dengan izin tidak mencukupi (~ root / log), tidak menghasilkan perubahan dalam ukuran file "a".

$ ./write_file.py >> ~root/log
-bash: /var/root/log: Permission denied
cdal at Mac in ~/experimental/unix_write
$ ls -lt
total 16
-rw-rw-r--  1 cdal  staff  0 Apr 27 08:54 a <-- SHOULD BE 50 BYTES

Orang akan berpikir program akan berjalan, menangkap output apa pun (tetapi juga menulis ke file "a"), dan kemudian gagal menulis output apa pun ke ~ root / log. Sebaliknya program tidak pernah berjalan.

Mengapa demikian, dan bagaimana bash memilih urutan "cek" yang dijalankannya sebelum menjalankan suatu program? Apakah pemeriksaan lain dilakukan juga?

ps Saya mencoba untuk menentukan apakah suatu program yang dijalankan di bawah cron benar-benar berjalan ketika diarahkan ke file "izin ditolak".

Charlie Dalsass
sumber
Semuanya berjalan dengan baik (yaitu kepemilikan dan izin dari file .py Anda yaitu) program Anda dieksekusi dengan baik. Masalah Anda berasal dari pengalihan. Anda tidak memiliki izin untuk menulis direktori filein / root. Dan Anda telah mengarahkan Anda stdoutuntuk melakukan hal itu. Jadi, Anda tidak akan melihat output apa pun, meskipun program Anda berjalan.
MelBurslan
2
Mel, itu tidak benar, programnya tidak pernah benar-benar berjalan. Lihat jawaban di bawah ini.
Charlie Dalsass
Anda: "Jalankan write_file.pyprogram dan kirim hasilnya ke ~root/logbash:" Maaf, tetapi Anda tidak boleh menulis ke file itu! "Shell melakukan apa yang seharusnya dilakukan. Jika tidak bisa melakukan apa yang Anda minta lakukan, itu segera memberitahu Anda mengapa ada masalah, memberi Anda kesempatan untuk memutuskan bagaimana menghadapinya. Untuk semua pemilik bash tahu, Hal-hal yang Sangat Buruk bisa terjadi jika Anda menjalankan perintah itu dan tidak bisa menyimpan output. Jika itu cukup penting, Anda menentukan tempat untuk menyimpannya, itu akan salah bagi ASS | U | ME, itu OK untuk berjalan tanpa menyimpan stdout.
Monty Harder

Jawaban:

18

Ini sebenarnya bukan masalah memesan cek, hanya urutan di mana shell mengatur segalanya. Pengalihan diatur sebelum perintah dijalankan; jadi pada contoh Anda, shell mencoba membuka ~root/loguntuk menambahkan sebelum mencoba melakukan apa pun yang melibatkan ./write_file.py. Karena file log tidak dapat dibuka, pengalihan gagal dan shell berhenti memproses baris perintah pada saat itu.

Salah satu cara untuk menunjukkan ini adalah dengan mengambil file yang tidak dapat dieksekusi dan mencoba menjalankannya:

$ touch demo
$ ./demo
zsh: permission denied: ./demo
$ ./demo > ~root/log
zsh: permission denied: /root/log

Ini menunjukkan bahwa shell bahkan tidak melihat ./demoketika pengalihan tidak dapat diatur.

Stephen Kitt
sumber
Wow, sesederhana itu? Saya tidak menyadari bahwa pengalihan dilakukan terlebih dahulu. Terima kasih atas jawaban ini dan juga jawaban-jawaban hebat lainnya.
Charlie Dalsass
6
Jika tidak dilakukan terlebih dahulu, ke mana output akan ditulis?
Charles Duffy
Dan jika hasilnya tidak bisa ditulis, bagaimana kita tahu aman menjalankan perintah? Mungkin perintah menampilkan informasi yang sedang dihapus dari penyimpanan data, dan itu benar-benar perlu agar keluaran ditangkap. Untunglah bash tidak akan membiarkannya berjalan sampai Anda memperbaiki izin itu, eh?
Monty Harder
11

Dari halaman bash man, bagian REDIRECTION (penekanan oleh saya):

Sebelum perintah dijalankan, input dan outputnya dapat dialihkan menggunakan notasi khusus yang ditafsirkan oleh shell.

...

Kegagalan untuk membuka atau membuat file menyebabkan pengalihan gagal.

Jadi shell mencoba untuk membuka file target stdout, yang gagal, dan perintah tidak dijalankan sama sekali.

Murphy
sumber
Terima kasih banyak. Saya berharap halaman manual diklarifikasi sedikit dengan "... jika output tidak dapat diarahkan, program tidak akan dieksekusi".
Charlie Dalsass
Diperbarui; agak tersembunyi beberapa paragraf di bawah ini.
Murphy
Sebenarnya cukup jelas. "Kegagalan untuk membuka atau membuat file menyebabkan pengalihan gagal." Itu ada. Terima kasih lagi.
Charlie Dalsass
3

Patut diperhatikan bahwa shell harus membuat pengalihan sebelum memulai program.

Pertimbangkan contoh Anda:

./write_file.py >> ~root/log

Apa yang terjadi di shell adalah:

  1. Kami (cangkang) fork(); proses anak mewarisi deskriptor file terbuka dari induknya (shell).
  2. Dalam proses anak, kita fopen()(perluasan) "~ root / log", dan dup2()ke fd 1 (dan close()fd sementara). Jika fopen()gagal, panggil exit()untuk melaporkan kesalahan kepada orang tua.
  3. Masih di dalam anak, kami exec()"./write_file.py". Proses ini sekarang tidak lagi menjalankan kode kami (kecuali kami gagal mengeksekusi, dalam hal ini kami exit()melaporkan kesalahan kepada orang tua).
  4. Orangtua akan membiarkan wait()anak untuk mengakhiri, dan menangani kode keluarnya (dengan menyalinnya $?, setidaknya).

Jadi pengalihan harus terjadi pada anak antara fork()dan exec(): itu tidak dapat terjadi sebelumnya fork()karena tidak boleh mengubah stdout shell, dan itu tidak dapat terjadi setelah exec()karena nama file dan kode executable shell sekarang telah digantikan oleh program Python . Orang tua tidak memiliki akses ke deskriptor file anak (dan bahkan jika itu, tidak dapat menjamin untuk mengalihkan antara exec()dan menulis pertama ke stdout).

Toby Speight
sumber
0

Saya minta maaf untuk memberi tahu Anda bahwa itu justru sebaliknya. Shell perlu membuka I / O terlebih dahulu dan kemudian melewati kontrol ke program.

tee mungkin terbukti bermanfaat dalam kasus ini: ./write_file.py | tee -a ~root/log > /dev/null

Julie Pelletier
sumber
Bukankah skrip Python hanya mati di SIGPIPE setelah tee gagal?
Kevin
Tidak sesuai dengan tes yang saya lakukan tetapi saya sarankan Anda mencobanya.
Julie Pelletier