Mengapa "ps ax" tidak menemukan skrip bash yang sedang berjalan tanpa tajuk "#!"?

13

Ketika saya menjalankan skrip ini, dimaksudkan untuk menjalankan sampai terbunuh ...

# foo.sh

while true; do sleep 1; done

... Saya tidak dapat menemukannya menggunakan ps ax:

>./foo.sh

// In a separate shell:
>ps ax | grep foo.sh
21110 pts/3    S+     0:00 grep --color=auto foo.sh

... tetapi jika saya hanya menambahkan #!header " " umum ke skrip ...

#! /usr/bin/bash
# foo.sh

while true; do sleep 1; done

... maka skrip menjadi dapat ditemukan oleh psperintah yang sama ...

>./foo.sh

// In a separate shell:
>ps ax | grep foo.sh
21319 pts/43   S+     0:00 /usr/bin/bash ./foo.sh
21324 pts/3    S+     0:00 grep --color=auto foo.sh

Kenapa begitu?
Ini mungkin pertanyaan terkait: Saya pikir " #" hanya awalan komentar, dan jika " #! /usr/bin/bash" itu sendiri tidak lebih dari sebuah komentar. Tetapi apakah " #!" membawa beberapa arti lebih besar daripada hanya sebagai komentar?

StoneThrow
sumber
Apa Unix yang kamu gunakan?
Kusalananda
@Kusalananda - Linux linuxbox 3.11.10-301.fc20.x86_64 # 1 SMP Kamis 5 Des 14:01:17 UTC 2013 x86_64 x86_64 x86_64 GNU / Linux
StoneThrow

Jawaban:

13

Ketika shell interaktif saat ini bash, dan Anda menjalankan skrip tanpa- #!line, maka bashakan menjalankan skrip. Proses akan muncul di ps axoutput sebagai adil bash.

$ cat foo.sh
# foo.sh

echo "$BASHPID"
while true; do sleep 1; done

$ ./foo.sh
55411

Di terminal lain:

$ ps -p 55411
  PID TT  STAT       TIME COMMAND
55411 p2  SN+     0:00.07 bash

Terkait:


Bagian yang relevan membentuk bashmanual:

Jika eksekusi ini gagal karena file tidak dalam format yang dapat dieksekusi, dan file tersebut bukan direktori, itu dianggap sebagai skrip shell , file yang berisi perintah shell. Subkulit ditelurkan untuk menjalankannya. Subshell ini menginisialisasi ulang dirinya sendiri, sehingga efeknya adalah seolah-olah shell baru telah dipanggil untuk menangani skrip , dengan pengecualian bahwa lokasi perintah yang diingat oleh orang tua (lihat hash di bawah di bawah SHELL BUILTIN COMMANDS) dipertahankan oleh anak.

Jika program adalah file yang diawali dengan #!, sisa baris pertama menentukan juru bahasa untuk program tersebut. Shell mengeksekusi interpreter yang ditentukan pada sistem operasi yang tidak menangani sendiri format yang dapat dieksekusi ini. [...]

Ini berarti bahwa menjalankan ./foo.shpada baris perintah, ketika foo.shtidak memiliki #!-line, sama dengan menjalankan perintah dalam file dalam subkulit, yaitu sebagai

$ ( echo "$BASHPID"; while true; do sleep 1; done )

Dengan menunjuk tepat #!ke eg /bin/bash, itu seperti melakukan

$ /bin/bash foo.sh
Kusalananda
sumber
Saya pikir saya mengikuti, tetapi apa yang Anda katakan juga benar dalam kasus kedua: bash juga menjalankan skrip dalam kasus kedua, seperti yang dapat diamati ketika psmenunjukkan skrip berjalan sebagai " /usr/bin/bash ./foo.sh". Jadi, dalam kasus pertama, seperti yang Anda katakan, bash akan menjalankan skrip, tetapi bukankah skrip itu harus "diteruskan" ke bash bercabang yang dapat dieksekusi, seperti halnya pada kasus kedua? (dan jika demikian, saya membayangkan itu akan dapat ditemukan dengan pipa untuk menerima ...?)
StoneThrow
@StoneThrow Lihat jawaban yang diperbarui.
Kusalananda
"... kecuali Anda mendapatkan proses baru" - yah, Anda juga mendapatkan proses baru, kecuali yang $$masih menunjuk pada yang lama dalam kasus subkulit ( echo $BASHPID/ bash -c 'echo $PPID').
Michael Homer
@MichaelHomer Ah, terima kasih untuk itu! Akan diperbarui.
Kusalananda
12

Ketika skrip shell dimulai #!, baris pertama itu adalah komentar sejauh menyangkut shell. Namun dua karakter pertama bermakna bagi bagian lain dari sistem: kernel. Dua karakter #!ini disebut shebang . Untuk memahami peran shebang, Anda perlu memahami bagaimana program dijalankan.

Menjalankan suatu program dari suatu file memerlukan tindakan dari kernel. Ini dilakukan sebagai bagian dari execvepanggilan sistem. Kernel perlu memverifikasi izin file, membebaskan sumber daya (memori, dll.) Yang terkait dengan file yang dapat dieksekusi yang saat ini berjalan dalam proses panggilan, mengalokasikan sumber daya untuk file yang dapat dieksekusi yang baru, dan mentransfer kontrol ke program baru (dan lebih banyak hal yang Saya tidak akan menyebutkan). The execvesystem call menggantikan kode dari proses yang sedang berjalan; ada panggilan sistem terpisah forkuntuk membuat proses baru.

Untuk melakukan ini, kernel harus mendukung format file yang dapat dieksekusi. File ini harus berisi kode mesin, diatur sedemikian rupa sehingga kernel mengerti. Script shell tidak mengandung kode mesin, jadi tidak bisa dijalankan dengan cara ini.

Mekanisme shebang memungkinkan kernel untuk menunda tugas menafsirkan kode ke program lain. Ketika kernel melihat bahwa file yang dapat dieksekusi dimulai #!, ia membaca beberapa karakter berikutnya dan menginterpretasikan baris pertama dari file tersebut (minus ruang memimpin #!dan opsional) sebagai jalur ke file lain (ditambah argumen, yang tidak akan saya bahas di sini ). Ketika kernel diberitahu untuk mengeksekusi file /my/script, dan ia melihat bahwa file dimulai dengan baris #!/some/interpreter, kernel mengeksekusi /some/interpreterdengan argumen /my/script. Maka terserah untuk /some/interpretermemutuskan bahwa itu /my/scriptadalah file skrip yang harus dijalankan.

Bagaimana jika suatu file tidak mengandung kode asli dalam format yang dimengerti oleh kernel, dan tidak dimulai dengan shebang? Nah, maka file tidak dapat dieksekusi, dan execvepanggilan sistem gagal dengan kode kesalahan ENOEXEC(Executable format error).

Ini bisa menjadi akhir dari cerita, tetapi sebagian besar shell menerapkan fitur mundur. Jika kernel kembali ENOEXEC, shell melihat isi file dan memeriksa apakah itu terlihat seperti skrip shell. Jika shell mengira file tersebut terlihat seperti skrip shell, ia menjalankannya dengan sendirinya. Rincian cara melakukannya tergantung pada shell. Anda dapat melihat sebagian dari apa yang terjadi dengan menambahkan ps $$skrip Anda, dan lebih banyak lagi dengan menyaksikan proses dengan di strace -p1234 -f -eprocessmana 1234 adalah PID dari shell.

Dalam bash, mekanisme fallback ini diimplementasikan dengan memanggil forktetapi tidak execve. Proses bash anak membersihkan keadaan internalnya sendiri dan membuka file skrip baru untuk menjalankannya. Oleh karena itu proses yang menjalankan skrip masih menggunakan gambar kode bash asli dan argumen baris perintah asli berlalu ketika Anda memanggil bash pada awalnya. ATT ksh berperilaku dengan cara yang sama.

% bash --norc
bash-4.3$ ./foo.sh 
  PID TTY      STAT   TIME COMMAND
21913 pts/2    S+     0:00 bash --norc

Dash, sebaliknya, bereaksi ENOEXECdengan menelepon /bin/shdengan jalur ke skrip yang diteruskan sebagai argumen. Dengan kata lain, ketika Anda menjalankan skrip shebangless dari dash, berperilaku seolah-olah skrip tersebut memiliki garis shebang #!/bin/sh. Mksh dan zsh berperilaku dengan cara yang sama.

% dash
$ ./foo.sh
  PID TTY      STAT   TIME COMMAND
21427 pts/2    S+     0:00 /bin/sh ./foo.sh
Gilles 'SANGAT berhenti menjadi jahat'
sumber
Jawaban hebat dan komprehensif. Satu pertanyaan RE: implementasi fallback yang Anda jelaskan: Saya kira karena seorang anak bashbercabang, ia memiliki akses ke argv[]array yang sama dengan orang tuanya, yaitu bagaimana ia mengetahui "argumen baris perintah asli yang disahkan ketika Anda memanggil bash awalnya", dan jika jadi, inilah mengapa anak tidak lulus skrip asli sebagai argumen eksplisit (maka mengapa tidak dapat ditemukan oleh grep) - apakah itu akurat?
StoneThrow
1
Anda sebenarnya dapat mematikan perilaku kernel shebang ( BINFMT_SCRIPTmodul mengontrol ini dan dapat dihapus / dimodulasi, meskipun biasanya terhubung secara statis ke dalam kernel), tetapi saya tidak melihat mengapa Anda ingin melakukannya, kecuali mungkin dalam sistem tertanam . Sebagai solusi untuk kemungkinan ini, bashmemiliki flag konfigurasi ( HAVE_HASH_BANG_EXEC) untuk mengimbangi!
ErikF
2
@StoneThrow Ini bukan karena bash anak "tahu argumen baris perintah asli", karena itu tidak memodifikasinya. Suatu program dapat memodifikasi pslaporan apa sebagai argumen baris perintah, tetapi hanya sampai pada titik tertentu: ia harus memodifikasi buffer memori yang ada, tidak dapat memperbesar buffer ini. Jadi, jika bash mencoba memodifikasinya argvuntuk menambahkan nama skrip, itu tidak akan selalu berhasil. Anak itu tidak “mengeluarkan argumen” karena tidak pernah ada execvepanggilan sistem pada anak tersebut. Hanya gambar proses bash yang sama yang terus berjalan.
Gilles 'SANGAT berhenti menjadi jahat'
-1

Dalam kasus pertama, skrip dijalankan oleh anak bercabang dari shell Anda saat ini.

Pertama-tama Anda harus menjalankan echo $$dan kemudian melihat shell yang memiliki id proses shell Anda sebagai id proses induk.

schily
sumber