Jalani jalur independen

20

Saya memiliki skrip yang ingin saya jalankan di dua mesin. Kedua mesin ini mendapatkan salinan skrip dari repositori git yang sama. Skrip harus dijalankan dengan penerjemah yang tepat (mis zsh.).

Sayangnya, keduanya env dan zshtinggal di lokasi yang berbeda di mesin lokal dan jarak jauh:

Mesin jarak jauh

$ which env
/bin/env

$ which zsh
/some/long/path/to/the/right/zsh

Mesin lokal

$ which env
/usr/bin/env

$which zsh
/usr/local/bin/zsh

Bagaimana saya bisa mengatur shebang sehingga menjalankan skrip seperti /path/to/script.shbiasa menggunakan yang Zshtersedia di PATH?

Amelio Vazquez-Reina
sumber
8
Apakah Anda yakin envtidak menggunakan keduanya / bin dan / usr / bin? Coba which -a envkonfirmasi.
grawity

Jawaban:

22

Anda tidak dapat menyelesaikan ini melalui shebang secara langsung, karena shebang sepenuhnya statis. Yang bisa Anda lakukan adalah memiliki beberapa »pengali paling umum« (dari perspektif shell) di shebang dan jalankan kembali skrip Anda dengan shell yang tepat, jika LCM ini tidak zsh. Dengan kata lain: Apakah skrip Anda dieksekusi oleh shell yang ditemukan pada semua sistem, uji untuk zshfitur -hanya dan jika tes ternyata salah, miliki skrip execdengan zsh, di mana tes akan berhasil dan Anda hanya melanjutkan.

Salah satu fitur unik dalam zsh, misalnya, adalah keberadaan $ZSH_VERSIONvariabel:

#!/bin/sh -

[ -z "$ZSH_VERSION" ] && exec zsh - "$0" ${1+"$@"}

# zsh-specific stuff following here
echo "$ZSH_VERSION"

Dalam kasus sederhana ini, skrip pertama kali dieksekusi oleh /bin/sh(semua sistem post-80 seperti Unix memahami #!dan memiliki /bin/sh, baik Bourne atau POSIX tetapi sintaks kami kompatibel untuk keduanya). Jika $ZSH_VERSIONini tidak diatur, script exec's sendiri melalui zsh. Jika $ZSH_VERSIONdiatur (resp. Skrip sudah dijalankan melalui zsh), tes hanya dilewati. Voa.

Ini hanya gagal jika zshtidak ada $PATHsama sekali.

Sunting: Untuk memastikan, Anda hanya execa zshdi tempat biasa, Anda bisa menggunakan sesuatu seperti

for sh in /bin/zsh \
          /usr/bin/zsh \
          /usr/local/bin/zsh; do
    [ -x "$sh" ] && exec "$sh" - "$0" ${1+"$@"}
done

Ini bisa menyelamatkan Anda dari execsesuatu $PATHyang tidak sengaja zshAnda temui.

Andreas Wiese
sumber
Aku upvoted ini untuk keanggunan tetapi tidak, pada prinsipnya, memiliki masalah keamanan / kompatibilitas, jika yang pertama zshdi $PATHbukan yang Anda harapkan.
Ryan Reich
Mencoba mengatasinya. Pertanyaannya adalah, apakah Anda selalu dapat memastikan apakah zshbiner di lokasi standar benar-benar a zsh.
Andreas Wiese
Anda dapat secara dinamis menelusuri garis bang! Anda juga bisa bertanya di zshmana ia berada zsh -c 'whence zsh'. Lebih sederhana, Anda bisa saja command -v zsh. Lihat jawaban saya untuk cara path secara dinamis #!bang.
mikeserv
1
Memanggil zshbiner dari $PATHuntuk mendapatkan jalur zshbiner tidak akan cukup mengatasi masalah yang ditunjukkan @RyanReich, bukan? :)
Andreas Wiese
Tidak jika Anda mengeksekusinya zshsendiri, tidak, saya kira tidak. Tetapi jika Anda menanamkan string yang dihasilkan dalam hash bang Anda dan kemudian mengeksekusi skrip Anda sendiri, setidaknya Anda tahu apa yang Anda dapatkan. Namun, itu akan membuat untuk tes yang lebih sederhana daripada untuk mengulanginya.
mikeserv
7

Selama bertahun-tahun saya telah menggunakan sesuatu yang mirip untuk menangani berbagai lokasi Bash pada sistem yang saya butuhkan untuk menjalankan skrip saya.

Bash / Zsh / dll.

#!/bin/sh

# Determines which OS and then reruns this script with approp. shell interp.
LIN_BASH="/bin/sh";
SOL_BASH="/packages/utilities/bin/sun5/bash";

OS_TYPE=`uname -s`;

if [ $OS_TYPE = "SunOS" ]; then
  $SOL_BASH -c "`sed -n '/\#\#\# BEGIN/,$p' $0`" $0 $*;
elif [ $OS_TYPE = "Linux" ]; then
  $LIN_BASH -c "`sed -n '/\#\#\# BEGIN/,$p' $0`" $0 $*;
else
  echo "UNKNOWN OS_TYPE, $OS_TYPE";
  exit 1;
fi
exit 0;

### BEGIN

...script goes here...

Di atas dapat dengan mudah disesuaikan untuk berbagai penerjemah. Kuncinya adalah bahwa skrip ini awalnya berjalan sebagai Bourne shell. Itu kemudian secara rekursif menyebut dirinya untuk kedua kalinya, tetapi mem-parsing semuanya di atas ### BEGINmenggunakan komentar sed.

Perl

Berikut trik serupa untuk Perl:

#!/bin/sh

LIN_PERL="/usr/bin/perl";
SOL_PERL="/packages/perl/bin/perl";

OS_TYPE=`uname -s`;

if [ $OS_TYPE = "SunOS" ]; then
  eval 'exec $SOL_PERL -x -S $0 ${1+"$@"}';
elif [ $OS_TYPE = "Linux" ]; then
  eval 'exec $LIN_PERL -x -S $0 ${1+"$@"}';
else
  echo "$OS_TYPE: UNSUPORRTED OS/PLATFORM";
  exit 0;
fi
exit 0;

#!perl

...perl script goes here...

Metode ini memanfaatkan kemampuan Perl ketika diberi file untuk dijalankan akan menguraikan file yang dilewati semua baris yang ada sebelum baris #! perl.

slm
sumber
Sejumlah masalah di sana: kutipan yang hilang, penggunaan $*alih - alih "$@", penggunaan eval yang tidak berguna, status keluar tidak dilaporkan (Anda tidak menggunakan execyang pertama), hilang -/ --, pesan kesalahan tidak pada stderr, 0 status keluar untuk kondisi kesalahan , menggunakan / bin / sh untuk LIN_BASH, titik koma yang tidak berguna (kosmetik), menggunakan semua huruf besar untuk variabel non-env. uname -sseperti uname(uname adalah untuk nama Unix). Anda lupa menyebutkan bahwa lompatan dipicu oleh -xopsi untuk perl.
Stéphane Chazelas
4

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 shreferensi 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 shdireferensikan] 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 zshkompiler 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.

PENGAKUAN TOKEN SHELL'S / PERLUASAN EKSEKUSI ADALAH DITETAPKAN STANDAR

Shell memiliki dua mode dasar untuk mem-parsing dan menginterpretasikan inputnya: baik input saat ini mendefinisikan <<here_documentatau 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 $expansionapapun 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 \newlinemembatasi 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 $expansionspada 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.

Sebuah <<HERE-DOCUMENTIS AN INLINE FILE

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 \newlinedan 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 nmewakili 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)shshell membuat file biasa /tmp, membuang output, memetakannya ke deskriptor, lalu menghapus /tmpfile sehingga salinan kernel dari deskriptor adalah yang tersisa. dashmenghindari semua omong kosong itu dan hanya membuang pemrosesan outputnya ke |pipefile anonim yang ditujukan untuk <<target pengalihan .

Ini membuat dash:

cmd <<HEREDOC
    $(cmd)
HEREDOC

secara fungsional setara dengan bash:

cmd <(cmd)

sementara dashimplementasi 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:

  1. Saya pertama kali catisi file apapun shell diciptakan untuk FILEmenjadi ./file, membuatnya menjadi executable, kemudian jalankan.

  2. Kernel menginterpretasikan #!dan memanggil /usr/bin/shdengan <read deskriptor file yang ditugaskan ./file.

  3. shmemetakan string ke dalam memori yang terdiri dari perintah majemuk yang dimulai pada _fn()dan berakhir pada SCRIPT.

  4. Ketika _fndisebut, shharus terlebih dahulu menafsirkan kemudian peta untuk deskriptor file didefinisikan dalam <<SCRIPT...SCRIPT sebelum memohon _fnsebagai khusus built-in utilitas karena SCRIPTmerupakan _fn's<input.

  5. String keluaran oleh printfdan commandditulis ke _fn's keluar standar >&1 - yang diarahkan ke shell saat ini ARGV0- atau $0.

  6. catmenyatukan deskriptor <&0 -input file standar - SCRIPT- atas argumen >shell saat ini yang terpotong ARGV0, atau $0.

  7. Menyelesaikan perintah majemuk saat ini yang sudah dibaca , sh execadalah $0argumen yang dapat dieksekusi - dan baru ditulis ulang - .

Dari saat ./filedipanggil sampai instruksi yang terkandung menentukan bahwa itu harus execd lagi, shmembacanya dalam perintah majemuk tunggal pada saat dieksekusi, sementara ./fileitu 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 gitsinkronisasi. Sejak saat itu ada jalan yang benar di baris bang # !.

Sekarang hampir semua itu hanya ada bulu. Untuk melakukan ini dengan aman, Anda perlu:

  1. 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.

  2. Beberapa cara menentukan jalan apa yang seharusnya. command -vcukup bagus untuk itu.

  3. Heredocs sangat membantu karena itu adalah file yang sebenarnya. Sementara itu, mereka akan menyimpan skrip Anda. Anda dapat menggunakan string juga tetapi ...

  4. 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 execperintah 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.

mikeserv
sumber
Downvote adalah karena kode modifikasi diri umumnya dianggap praktik buruk. Kembali ke 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.
jw013
@ jw013 Jelas pendekatan saya untuk menginstal atau memperbarui skrip yang dapat dieksekusi tidak akan berfungsi jika orang yang mencoba untuk menginstal atau memperbarui skrip tidak memiliki izin untuk menginstal atau memperbarui skrip. sebenarnya, itulah yang secara khusus membuat jawaban ini lebih baik daripada setiap jawaban lain di sini - ini dapat memberikan garis bang #! yang akurat sebagaimana diperlukan dan hanya memerlukan izin khusus untuk melakukannya pada permintaan pertama - selama instalasi. Dan, sekali lagi, saya tidak hanya akan mengambil kata-kata Anda untuk itu bahwa kode modifikasi diri adalah praktik yang buruk - silakan lihat man commandpendapat yang bertentangan.
mikeserv
silakan lihat man commandpendapat yang bertentangan - tidak menemukan satu. Bisakah Anda mengarahkan saya ke bagian / paragraf spesifik yang Anda bicarakan?
jw013
@ jw013 - kesalahan saya, ini ada di man sh- cari 'command -v'. Saya tahu itu di salah satu manhalaman yang saya lihat di hari lain.
mikeserv
Saya berasumsi ini adalah command -vcontoh yang Anda bicarakan man sh. Itu adalah skrip pemasang yang tampak normal, dan bukan skrip modifikasi sendiri . Bahkan installer mandiri hanya mengandung input pra-modifikasi, dan output modifikasinya di tempat lain. Mereka tidak menulis ulang sendiri seperti yang Anda rekomendasikan.
jw013
1

Berikut adalah salah satu cara untuk memiliki skrip modifikasi diri yang memperbaiki shebang-nya. Kode ini harus diawali dengan skrip Anda yang sebenarnya.

#!/bin/sh
# unpatched

PATH=`PATH=/bin:/usr/bin:$PATH getconf PATH`
if [ "`awk 'NR==2 {print $2;exit;}' $0`" = unpatched ]; then
  [ -z "`PATH=\`getconf PATH\`:/usr/local/bin:/some/long/path/to/the/right:$PATH command -v zsh`" ] && { echo "zsh not found"; exit 1; }
  cp -- "$0" "$0.org" || exit 1
  mv -- "$0" "$0.old" || exit 1
  (
    echo "#!`PATH=\`getconf PATH\`:$PATH command -v zsh`" 
    sed -n '/^##/,$p' $0.old
  ) > $0 || exit
  chmod +x $0
  rm $0.old
  sync
  exit
fi
## Original script starts here

Beberapa komentar:

  • Ini harus dijalankan sekali oleh seseorang yang memiliki hak untuk membuat dan menghapus file di direktori tempat skrip berada.

  • Ia hanya menggunakan sintaksis shell bourne legacy karena, meskipun diyakini banyak orang, /bin/shtidak dijamin menjadi shell POSIX, bahkan en OSIX yang sesuai dengan POSIX.

  • Ini mengatur PATH ke POSIX compliant diikuti oleh daftar lokasi zsh yang mungkin untuk menghindari memilih zsh "palsu".

  • Jika karena alasan tertentu, skrip modifikasi sendiri tidak diinginkan, akan sepele untuk mendistribusikan dua skrip bukan satu, yang pertama adalah yang ingin Anda tambal dan yang kedua, yang saya sarankan sedikit dimodifikasi untuk memproses yang pertama.

Jlliagre
sumber
The /bin/shpoint adalah salah satu yang baik - tetapi dalam kasus yang Anda perlu premodified #!sama sekali? Dan bukankah awkhanya palsu seperti zshitu?
mikeserv
@mikeserv Jawaban dimutakhirkan untuk memanggil POSIX awk. Shebang yang telah dipraodifikasi ada untuk mencegah skrip diinterpretasikan oleh shell yang tidak kompatibel dengan bourne jika itu adalah shell login Anda.
jlliagre
Masuk akal. Saya memutakhirkannya karena berfungsi, tetap menempel pada buku dan menunjukkan pemahaman suara tentang kemungkinan penanganan shell / file - terutama file cadangan yang Anda gunakan, yang semua GNU sed -ilakukan. Saya pribadi berpikir $PATHmasalah yang dicatat dalam komentar pada jawaban lain dan yang Anda atasi dengan aman seperti yang saya bayangkan di sini dalam beberapa baris lebih baik ditangani dengan mendefinisikan dependensi secara sederhana dan eksplisit dan / atau pengujian yang ketat dan eksplisit - misalnya, sekarang getconfmungkin palsu, tetapi kemungkinannya hampir nol, sama seperti untuk zshdanawk.
mikeserv
@ mikeserv, skrip dimodifikasi untuk mengurangi risiko memanggil getconf palsu.
jlliagre
$(getconf PATH)bukan Bourne. cp $0 $0.oldadalah sintaks zsh. Setara dengan Bourne akan seperti yang cp "$0" "$0.old"Anda inginkancp -- "$0" "$0.old"
Stéphane Chazelas