CATATAN: @ jw013 mengajukan keberatan yang tidak didukung berikut dalam komentar di bawah:
Downvote adalah karena kode modifikasi diri umumnya dianggap praktik buruk. Kembali di masa lalu program perakitan kecil itu adalah cara cerdas untuk mengurangi cabang bersyarat dan meningkatkan kinerja, tetapi saat ini risiko keamanan lebih besar daripada keuntungannya. Pendekatan Anda tidak akan berfungsi jika pengguna yang menjalankan skrip tidak memiliki hak istimewa menulis pada skrip.
Aku menjawab keberatan keamanannya dengan menunjukkan bahwa setiap izin khusus yang hanya diperlukan sekali per install / update tindakan untuk menginstal / memperbarui yang self-menginstal naskah - yang saya pribadi sebut cukup aman. Saya juga mengarahkannya ke man sh
referensi untuk mencapai tujuan yang sama dengan cara yang sama. Saya tidak, pada saat itu, repot-repot menunjukkan bahwa keamanan apa pun cacat atau praktik - praktik yang umumnya tidak disarankan yang mungkin atau mungkin tidak terwakili dalam jawaban saya, mereka lebih cenderung berakar pada pertanyaan itu sendiri daripada mereka dalam jawaban saya untuk itu:
Bagaimana saya mengatur shebang sehingga menjalankan skrip sebagai /path/to/script.sh selalu menggunakan Zsh yang tersedia di PATH?
Tidak puas, @ jw013 terus menolak dengan melanjutkan argumennya yang belum didukung dengan setidaknya beberapa pernyataan salah:
Anda menggunakan satu file, bukan dua file. Paket [ man sh
direferensikan]
memiliki satu file memodifikasi file lain. Anda memiliki file yang memodifikasi sendiri. Ada perbedaan yang jelas antara kedua kasus ini. File yang mengambil input dan menghasilkan output baik-baik saja. File yang dapat dieksekusi yang berubah sendiri saat dijalankan umumnya merupakan ide yang buruk. Contoh yang Anda tunjuk tidak melakukan itu.
Di tempat pertama:
KODE YANG HANYA DILAKSANAKAN DALAM SEGERA SCRIPT SHELL EXECUTABLE ADALAH #!
SENDIRI
(meskipun bahkan #!
secara resmi tidak ditentukan )
{ cat >|./file
chmod +x ./file
./file
} <<-\FILE
#!/usr/bin/sh
{ ${l=lsof -p} $$
echo "$l \$$" | sh
} | grep \
"COMMAND\|^..*sh\| [0-9]*[wru] "
#END
FILE
##OUTPUT
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
file 8900 mikeserv txt REG 0,33 774976 2148676 /usr/bin/bash
file 8900 mikeserv mem REG 0,30 2148676 /usr/bin/bash (path dev=0,33)
file 8900 mikeserv 0r REG 0,35 108 15496912 /tmp/zshUTTARQ (deleted)
file 8900 mikeserv 1u CHR 136,2 0t0 5 /dev/pts/2
file 8900 mikeserv 2u CHR 136,2 0t0 5 /dev/pts/2
file 8900 mikeserv 255r REG 0,33 108 2134129 /home/mikeserv/file
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
sh 8906 mikeserv txt REG 0,33 774976 2148676 /usr/bin/bash
sh 8906 mikeserv mem REG 0,30 2148676 /usr/bin/bash (path dev=0,33)
sh 8906 mikeserv 0r FIFO 0,8 0t0 15500515 pipe
sh 8906 mikeserv 1w FIFO 0,8 0t0 15500514 pipe
sh 8906 mikeserv 2u CHR 136,2 0t0 5 /dev/pts/2
{ sed -i \
'1c#!/home/mikeserv/file' ./file
./file
sh -c './file ; echo'
grep '#!' ./file
}
##OUTPUT
zsh: too many levels of symbolic links: ./file
sh: ./file: /home/mikeserv/file: bad interpreter: Too many levels of symbolic links
#!/home/mikeserv/file
Sebuah skrip shell hanyalah sebuah file teks - agar dapat memiliki efek sama sekali, ia harus dibaca oleh file executable lain, instruksinya kemudian ditafsirkan oleh file executable lainnya, sebelum akhirnya file executable lainnya kemudian menjalankan interpretasinya terhadap skrip shell. Hal ini tidak mungkin untuk eksekusi file shell script untuk melibatkan kurang dari dua file. Ada pengecualian yang mungkin ada di zsh
kompiler sendiri, tetapi dengan ini saya memiliki sedikit pengalaman dan sama sekali tidak diwakili di sini.
Hashbang skrip shell harus menunjuk ke penerjemah yang dimaksud atau dibuang sebagai tidak relevan.
Shell memiliki dua mode dasar untuk mem-parsing dan menginterpretasikan inputnya: baik input saat ini mendefinisikan <<here_document
atau mendefinisikan { ( command |&&|| list ) ; } &
- dengan kata lain, shell mengartikan token sebagai pembatas untuk perintah yang harus dijalankan setelah ia membacanya di atau sebagai instruksi untuk membuat file dan memetakannya ke file descriptor untuk perintah lain. Itu dia.
Saat menginterpretasikan perintah untuk mengeksekusi token pembatas shell pada sekumpulan kata yang disimpan. Ketika shell menemukan token pembuka itu harus terus membaca dalam daftar perintah sampai daftar baik dibatasi oleh token penutup seperti baris baru - bila berlaku - atau token penutup seperti })
untuk ({
sebelum eksekusi.
Shell membedakan antara perintah sederhana dan perintah majemuk. The perintah senyawa adalah seperangkat perintah yang harus dibaca sebelum eksekusi, tetapi shell tidak melakukan $expansion
apapun dari penyusunnya perintah sederhana sampai secara tunggal mengeksekusi masing-masing.
Jadi, dalam contoh berikut, ;semicolon
kata-kata yang dicadangkan membatasi masing-masing perintah sederhana sedangkan karakter yang tidak luput \newline
membatasi antara dua perintah majemuk:
{ cat >|./file
chmod +x ./file
./file
} <<-\FILE
#!/usr/bin/sh
echo "simple command ${sc=1}" ;\
: > $0 ;\
echo "simple command $((sc+2))" ;\
sh -c "./file && echo hooray"
sh -c "./file && echo hooray"
#END
FILE
##OUTPUT
simple command 1
simple command 3
hooray
Itu adalah penyederhanaan pedoman. Itu menjadi jauh lebih rumit ketika Anda mempertimbangkan shell-builtin, subshell, lingkungan saat ini dan lain-lain, tetapi, untuk tujuan saya di sini, itu sudah cukup.
Dan berbicara tentang built-in dan daftar perintah, a function() { declaration ; }
hanyalah sarana untuk menetapkan perintah majemuk ke perintah sederhana. Shell tidak boleh melakukan apapun $expansions
pada pernyataan deklarasi itu sendiri - untuk memasukkan <<redirections>
- tetapi sebagai gantinya harus menyimpan definisi sebagai string tunggal, literal dan menjalankannya sebagai shell khusus built-in ketika dipanggil.
Jadi fungsi shell yang dideklarasikan dalam skrip shell yang dapat dieksekusi disimpan dalam memori shell interpreting dalam bentuk string literalnya - tidak diperluas untuk menyertakan dokumen-dokumen yang ditambahkan di sini sebagai input - dan dieksekusi secara independen dari file sumbernya setiap kali ia disebut sebagai shell built- selama lingkungan shell saat ini berlangsung.
Operator pengalihan <<
dan <<-
keduanya memungkinkan pengalihan baris yang terkandung dalam file input shell, yang dikenal sebagai dokumen di sini, ke input perintah.
Dokumen di sini harus diperlakukan sebagai satu kata yang dimulai setelah kata berikutnya \newline
dan berlanjut sampai ada garis yang hanya berisi pembatas dan a \newline
, tanpa ada [:blank:]
di antaranya. Kemudian di sini dokumen selanjutnya dimulai, jika ada. Formatnya adalah sebagai berikut:
[n]<<word
here-document
delimiter
... di mana opsional n
mewakili nomor deskriptor file. Jika nomornya dihilangkan, dokumen di sini merujuk pada input standar (file descriptor 0).
for shell in dash zsh bash sh ; do sudo $shell -c '
{ readlink /proc/self/fd/3
cat <&3
} 3<<-FILE
$0
FILE
' ; done
#OUTPUT
pipe:[16582351]
dash
/tmp/zshqs0lKX (deleted)
zsh
/tmp/sh-thd-955082504 (deleted)
bash
/tmp/sh-thd-955082612 (deleted)
sh
Kamu melihat? Untuk setiap shell di atas shell membuat file dan memetakannya ke deskriptor file. Dalam zsh, (ba)sh
shell membuat file biasa /tmp
, membuang output, memetakannya ke deskriptor, lalu menghapus /tmp
file sehingga salinan kernel dari deskriptor adalah yang tersisa. dash
menghindari semua omong kosong itu dan hanya membuang pemrosesan outputnya ke |pipe
file anonim yang ditujukan untuk <<
target pengalihan .
Ini membuat dash
:
cmd <<HEREDOC
$(cmd)
HEREDOC
secara fungsional setara dengan bash
:
cmd <(cmd)
sementara dash
implementasi setidaknya POSIXly portable.
YANG MEMBUAT BEBERAPA FILES
Jadi dalam jawaban di bawah ini ketika saya melakukannya:
{ cat >|./file
chmod +x ./file
./file
} <<\FILE
#!/usr/bin/sh
_fn() { printf '#!' ; command -v zsh ; cat
} <<SCRIPT >$0
[SCRIPT BODY]
SCRIPT
_fn ; exec $0
FILE
Berikut ini terjadi:
Saya pertama kali cat
isi file apapun shell diciptakan untuk FILE
menjadi ./file
, membuatnya menjadi executable, kemudian jalankan.
Kernel menginterpretasikan #!
dan memanggil /usr/bin/sh
dengan <read
deskriptor file yang ditugaskan ./file
.
sh
memetakan string ke dalam memori yang terdiri dari perintah majemuk yang dimulai pada _fn()
dan berakhir pada SCRIPT
.
Ketika _fn
disebut, sh
harus terlebih dahulu menafsirkan kemudian peta untuk deskriptor file didefinisikan dalam <<SCRIPT...SCRIPT
sebelum memohon _fn
sebagai khusus built-in utilitas karena SCRIPT
merupakan _fn
's<input.
String keluaran oleh printf
dan command
ditulis ke _fn
's keluar standar >&1
- yang diarahkan ke shell saat ini ARGV0
- atau $0
.
cat
menyatukan deskriptor <&0
-input file standar - SCRIPT
- atas argumen >
shell saat ini yang terpotong ARGV0
, atau $0
.
Menyelesaikan perintah majemuk saat ini yang sudah dibaca , sh exec
adalah $0
argumen yang dapat dieksekusi - dan baru ditulis ulang - .
Dari saat ./file
dipanggil sampai instruksi yang terkandung menentukan bahwa itu harus exec
d lagi, sh
membacanya dalam perintah majemuk tunggal pada saat dieksekusi, sementara ./file
itu sendiri tidak melakukan apa-apa kecuali dengan senang hati menerima konten baru. File yang sebenarnya sedang bekerja adalah/usr/bin/sh, /usr/bin/cat, /tmp/sh-something-or-another.
TERIMA KASIH, SETELAH SEMUA
Jadi ketika @ jw013 menetapkan bahwa:
File yang mengambil input dan menghasilkan output baik-baik saja ...
... di tengah kritiknya yang keliru tentang jawaban ini, dia sebenarnya tanpa sadar mengampuni satu-satunya metode yang digunakan di sini, yang pada dasarnya hanya berfungsi untuk:
cat <new_file >old_file
MENJAWAB
Semua jawaban di sini baik, tetapi tidak ada yang sepenuhnya benar. Semua orang tampaknya mengklaim Anda tidak dapat secara dinamis dan permanen melacak Anda #!bang
. Berikut ini adalah demonstrasi pengaturan jalur independen shebang:
DEMO
{ cat >|./file
chmod +x ./file
./file
} <<\FILE
#!/usr/bin/sh
_rewrite_me() { printf '#!' ; command -v zsh
${out+cat} ; ${out+:} . /dev/fd/0 >&2
} <<\SCRIPT >|${out-/dev/null}
printf "
\$0 :\t$0
lines :\t$((c=$(wc -l <$0)))
!bang :\t$(sed 1q "$0")
shell :\t"$(printf `ps -o args= -p $$`)\\n\\n
sed -n "1,2{=;p};$((c-1)),\${=;p}" "$0" |
sed -e 'N;s/\n/ >\t/' -e 4a\\...
SCRIPT
_rewrite_me ; out=$0 _rewrite_me ; exec $0
FILE
KELUARAN
$0 : ./file
lines : 13
!bang : #!/usr/bin/sh
shell : /usr/bin/sh
1 > #!/usr/bin/sh
2 > _rewrite_me() { printf '#!' ; command -v zsh
...
12 > SCRIPT
13 > _rewrite_me ; out=$0 _rewrite_me ; exec $0
$0 : /home/mikeserv/file
lines : 8
!bang : #!/usr/bin/zsh
shell : /usr/bin/zsh
1 > #!/usr/bin/zsh
2 > printf "
...
7 > sed -n "1,2{=;p};$((c-1)),\${=;p}" "$0" |
8 > sed -e 'N;s/\n/ >\t/' -e 4a\\...
Kamu melihat? Kami hanya membuat skrip menimpa dirinya sendiri. Dan itu hanya terjadi sekali setelah git
sinkronisasi. Sejak saat itu ada jalan yang benar di baris bang # !.
Sekarang hampir semua itu hanya ada bulu. Untuk melakukan ini dengan aman, Anda perlu:
Fungsi yang didefinisikan di bagian atas dan disebut di bagian bawah yang melakukan penulisan. Dengan cara ini kami menyimpan semua yang kami butuhkan dalam memori dan memastikan seluruh file terbaca sebelum kami mulai menulisnya.
Beberapa cara menentukan jalan apa yang seharusnya. command -v
cukup bagus untuk itu.
Heredocs sangat membantu karena itu adalah file yang sebenarnya. Sementara itu, mereka akan menyimpan skrip Anda. Anda dapat menggunakan string juga tetapi ...
Anda harus memastikan bahwa shell membaca dalam perintah yang menimpa skrip Anda dalam daftar perintah yang sama dengan yang mengeksekusinya.
Lihat:
{ cat >|./file
chmod +x ./file
./file
} <<\FILE
#!/usr/bin/sh
_rewrite_me() { printf '#!' ; command -v zsh
${out+cat} ; ${out+:} . /dev/fd/0 >&2
} <<\SCRIPT >|${out-/dev/null}
printf "
\$0 :\t$0
lines :\t$((c=$(wc -l <$0)))
!bang :\t$(sed 1q "$0")
shell :\t"$(printf `ps -o args= -p $$`)\\n\\n
sed -n "1,2{=;p};$((c-1)),\${=;p}" "$0" |
sed -e 'N;s/\n/ >\t/' -e 4a\\...
SCRIPT
_rewrite_me ; out=$0 _rewrite_me
exec $0
FILE
Perhatikan bahwa saya hanya memindahkan exec
perintah ke satu baris. Sekarang:
#OUTPUT
$0 : ./file
lines : 14
!bang : #!/usr/bin/sh
shell : /usr/bin/sh
1 > #!/usr/bin/sh
2 > _rewrite_me() { printf '#!' ; command -v zsh
...
13 > _rewrite_me ; out=$0 _rewrite_me
14 > exec $0
Saya tidak mendapatkan bagian kedua dari output karena skrip tidak dapat membaca di perintah berikutnya. Tetap saja, karena satu-satunya perintah yang hilang adalah yang terakhir:
cat ./file
#!/usr/bin/zsh
printf "
\$0 :\t$0
lines :\t$((c=$(wc -l <$0)))
!bang :\t$(sed 1q "$0")
shell :\t"$(printf `ps -o args= -p $$`)\\n\\n
sed -n "1,2{=;p};$((c-1)),\${=;p}" "$0" |
sed -e 'N;s/\n/ >\t/' -e 4a\\...
Naskahnya dibuat sebagaimana mestinya - sebagian besar karena semuanya ada di heredoc - tetapi jika Anda tidak merencanakannya dengan benar, Anda dapat memotong filestream Anda, yang merupakan apa yang terjadi pada saya di atas.
env
tidak menggunakan keduanya / bin dan / usr / bin? Cobawhich -a env
konfirmasi.