Bash secara otomatis memuat ulang (menyuntikkan) pembaruan ke dalam skrip yang berjalan setelah menyimpannya: Mengapa? Adakah penggunaan praktis?

10

Saya sedang menulis skrip bash, dan kebetulan memperbarui kode (menyimpan file skrip ke disk) ketika skrip sedang menunggu beberapa input dalam satu whilelingkaran. Setelah saya kembali ke terminal dan melanjutkan dengan permintaan skrip sebelumnya, bash memberi kesalahan tentang sintaksis file:

/home/aularon/bin/script: line 58: unexpected EOF while looking for matching `"'
/home/aularon/bin/script: line 67: syntax error: unexpected end of file

Jadi saya mencoba melakukan hal berikut:

1: buat skrip, self-update.shsebut saja:

#!/bin/bash
fname=$(mktemp)
cat $0 | sed 's/BEFORE\./AFTER!./' > $fname
cp $fname $0
rm -f $fname
echo 'String: BEFORE.';

Apa yang dilakukan skrip adalah membaca kodenya, mengubah kata 'SEBELUM' menjadi 'SETELAH', kemudian menulis ulang sendiri dengan kode baru.

2nd Run it:

chmod +x self-update.sh
./self-update.sh

Keajaiban ke - 3 ...

aularon@aularon-laptop:~$ ./self-update.sh 
String: AFTER!.

Sekarang, saya tidak akan menduga bahwa pada doa yang sama itu akan menghasilkan SETELAH! , pada menjalankan kedua pasti, tetapi tidak pada yang pertama.

Jadi pertanyaan saya adalah: apakah disengaja (berdasarkan desain)? atau itu karena cara bash menjalankan skrip? Baris demi baris atau perintah demi perintah. Apakah ada gunanya perilaku seperti itu? Adakah contohnya?


Sunting: Saya mencoba memformat ulang file untuk menempatkan semua perintah dalam satu baris, itu tidak berfungsi sekarang:

#!/bin/bash
fname=$(mktemp);cat $0 | sed 's/BEFORE\./AFTER!./' > $fname;cp $fname $0;rm -f $fname;echo 'String: BEFORE.';

Keluaran:

aularon@aularon-laptop:~$ ./self-update.sh #First invocation
String: BEFORE.
aularon@aularon-laptop:~$ ./self-update.sh #Second invocation
String: AFTER!.

Saat memindahkan echostring ke baris berikutnya, pisahkan dari cppanggilan penulisan ulang ( ):

#!/bin/bash
fname=$(mktemp);cat $0 | sed 's/BEFORE\./AFTER!./' > $fname;cp $fname $0;rm -f $fname;
echo 'String: BEFORE.';

Dan sekarang berfungsi lagi:

aularon@aularon-laptop:~$ ./self-update.sh 
String: AFTER!.
aularon
sumber
1
Terkait: stackoverflow.com/questions/4754592/...
Martin von Wittich
1
Saya telah memeriksa sumbernya, tetapi saya tidak dapat menemukan penjelasan untuk ini. Saya cukup yakin bahwa saya telah melihat beberapa installer self-extracting yang diimplementasikan sebagai skrip shell beberapa tahun yang lalu. Script ini berisi beberapa baris perintah shell yang dapat dieksekusi, dan kemudian blok data besar yang dibaca oleh perintah ini. Jadi saya akan berasumsi bahwa ini dirancang sedemikian rupa sehingga bash tidak harus membaca seluruh skrip ke dalam memori yang tidak akan berfungsi dengan skrip self-extracting besar.
Martin von Wittich
Berikut adalah contoh skrip SFX: ftp.games.skynet.be/pub/wolfenstein/…
Martin von Wittich
Bagus! Saya ingat melihat skrip penginstalan mandiri ini, driver AMD Catalyst (berpemilik) masih dikirimkan seperti ini, ia mengekstrak sendiri lalu menginstalnya. Ini tergantung pada perilaku membaca file dalam potongan. Terima kasih untuk contohnya!
aularon

Jawaban:

12

Ini dengan desain. Bash membaca skrip dalam potongan. Jadi itu akan membaca sebagian dari skrip, menjalankan setiap baris yang dapat, dan kemudian membaca potongan berikutnya.

Jadi Anda mengalami sesuatu seperti ini:

  • Bash membaca 256 byte pertama (byte 0-255) dari skrip.
  • Dalam 256 byte pertama adalah perintah yang membutuhkan waktu untuk berjalan, dan bash memulai perintah itu, menunggu untuk keluar.
  • Saat perintah sedang berjalan, skrip diperbarui, dan bagian yang diubah adalah setelah 256 byte yang sudah dibaca.
  • Ketika perintah bash berjalan selesai, ia melanjutkan membaca file, melanjutkan dari tempatnya, mendapatkan byte 256-511.
  • Bagian skrip itu berubah, tetapi bash tidak tahu itu.

Di mana ini menjadi lebih bermasalah adalah bahwa jika Anda mengedit sesuatu sebelum byte 256. Katakanlah Anda menghapus beberapa baris. Kemudian data dalam skrip yang pada byte 256, sekarang di tempat lain, katakanlah pada byte 156 (100 byte sebelumnya). Karena itu, ketika bash terus membaca, itu akan mendapatkan apa yang semula 356.

Ini hanya sebuah contoh. Bash tidak harus membaca 256 byte sekaligus. Saya tidak tahu persis berapa banyak dibaca pada satu waktu, tetapi tidak masalah, perilaku masih sama.

Patrick
sumber
Tidak, itu dibaca dengan potongan, tetapi mundur ke tempat itu untuk memastikan untuk membaca perintah berikutnya seperti setelah perintah sebelumnya telah kembali.
Stéphane Chazelas
@StephaneChazelas Dari mana Anda mendapatkannya? Saya baru saja melakukan strace dan bahkan tidak sebanyak statfile untuk melihat apakah itu berubah. Tidak ada lseekpanggilan.
Patrick
Lihat pastie.org/8662761 untuk bagian relevan dari output strace. Lihat bagaimana echo fooperubahan menjadi echo barselama sleep. Sudah berperilaku seperti itu sejauh versi 2, jadi saya tidak berpikir itu masalah versi.
Stéphane Chazelas
Ternyata ia membaca file baris demi baris, saya mencoba dengan file tersebut dan itulah yang saya temukan. Saya akan mengedit pertanyaan saya untuk menyoroti perilaku itu.
aularon
@ Patrick jika Anda dapat memperbarui jawaban Anda untuk mencerminkan bahwa itu membaca baris demi baris, jadi saya dapat menerima jawaban Anda. (Periksa edit saya untuk pertanyaan tentang hal itu).
aularon