Yol bağımsız shebangs


20

İki makinede çalıştırmak istediğim bir senaryom var. Bu iki makine aynı git deposundan betiğin kopyalarını alır. Komut dosyasının doğru yorumlayıcıyla çalışması gerekir (örn. zsh).

Ne yazık ki, her ikisi de env ve zshyerel ve uzak makinelerde farklı yerlerde yaşamak:

Uzak makine

$ which env
/bin/env

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

Yerel makine

$ which env
/usr/bin/env

$which zsh
/usr/local/bin/zsh

Nasıl olarak komut dosyası çalıştıran böylece shebang ayarlayabilirsiniz /path/to/script.shdaima kullanır Zshkullanılabilir in PATH?


8
envHem / bin hem de / usr / bin'de olmadığından emin misiniz ? which -a envOnaylamaya çalışın .
Grawity

Yanıtlar:


22

Bunu doğrudan shebang aracılığıyla çözemezsiniz, çünkü shebang tamamen statiktir. Yapabileceğiniz şey, bir »en az ortak çarpanı« (kabuk perspektifinden) sahip olmak ve bu LCM zsh değilse, komut dosyanızı doğru kabuk ile yeniden çalıştırmaktır. Başka bir deyişle: senaryonuz bir kabuk, bir için, tüm sistemlerde testi buldum tarafından yürütülen mü zshbatılı yok edip Test dönüşler -sadece özelliği ve eğer senaryoyu sahip execolan zshbir test başarılı olur ve sadece devam.

zshÖrneğin, benzersiz bir özellik $ZSH_VERSIONdeğişkenin varlığıdır :

#!/bin/sh -

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

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

Bu basit durumda, komut dosyası ilk önce /bin/sh(Unix benzeri tüm 80s sistemleri Bourne veya POSIX'i anlar #!ve kullanır /bin/shancak sözdizimimiz her ikisiyle de uyumludur) tarafından yürütülür . Eğer $ZSH_VERSIONedilir değil set, script exec'kendisini içinden s zsh. Eğer $ZSH_VERSIONayarlanır (solunum. Komut dosyası zaten aracılığıyla çalıştırılır zsh), deney basitçe atlanır. Voilà.

Bu yalnızca başarısız zshdeğil $PATHhiç.

Düzenleme: emin olmak için, yalnızca execbir zshzamanki yerlerde, gibi bir şey kullanabilirsiniz

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

Bu sizi yanlışlıkla execiçinde beklediğiniz $PATHolmayan bir şey yapmaktan kurtarabilir zsh.


Ben zerafet için bu upvoted ama ilk eğer, güvenlik / uyumluluk sorunları var, ilke olarak, does zshiçinde $PATHbeklediğiniz biri değildir.
Ryan Reich

Bu sorunu çözmeye çalıştı. Soru, her zaman zshstandart yerlerde bir ikili olup olmadığını emin olup olmadığını bir zsh.
Andreas Wiese

! Bang satırını dinamik olarak yönlendirebilirsiniz. zshKendisine nerede olduğunu da sorabilirsiniz zsh -c 'whence zsh'. Daha basitçe yapabilirsiniz command -v zsh. Dinamik olarak nasıl yönlendirileceği ile ilgili cevabımı görün #!bang.
mikeserv

1
Arama zshden ikili $PATHyolunu almak için zsh, bunu oldukça dikkat çekti @RyanReich sorunu çözmek olmaz ikili olur? :)
Andreas Wiese

Eğer zshkendini yürütürsen hayır, hayır, sanırım değil Ancak, ortaya çıkan dizeyi karma patlamanıza gömüp kendi komut dosyanızı çalıştırırsanız, en azından ne elde ettiğinizi bilirsiniz. Yine de, döngüden daha basit bir test yapacaktır.
mikeserv

7

Yıllarca Bash'in çeşitli konumlarıyla başa çıkmak için scriptlerimin çalıştırılması gereken bir şey kullandım.

Bash / Zsh / vb.

#!/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...

Yukarıdakiler çeşitli tercümanlar için kolayca uyarlanabilir. Anahtar parça, bu komut dosyasının başlangıçta Bourne kabuğu olarak çalışmasıdır. Daha sonra kendini tekrar tekrar ikinci kez çağırır, ancak yorumun üzerindeki her şeyi ### BEGINkullanarak ayrıştırır sed.

Perl

İşte Perl için benzer bir numara:

#!/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...

Bu yöntem, Perl'in çalıştırılacak bir dosya verildiğinde, söz konusu dosyayı satırdan önceki tüm satırları atlayarak ayrıştıracağı yeteneğinden faydalanır #! perl.


Orada bir dizi sorun: eksik alıntılar, $*bunun yerine "$@", değerlendirmenin yararsız kullanımı, rapor edilmemiş çıkış durumu ( execilkini kullanmadınız ), eksik -/ --, stderr'de olmayan hata mesajları, hata koşulları için 0 çıkış durumu , LIN_BASH için / bin / sh, yararsız noktalı virgül (kozmetik) kullanarak, env olmayan değişkenler için tüm büyük harfleri kullanır. uname -sgibidir uname(uname Unix adı içindir). Atlamanın-x seçenek tarafından tetiklendiğini belirtmeyi unuttunuz perl.
Stéphane Chazelas

4

NOT: @ jw013, aşağıdaki yorumlarda aşağıdaki desteklenmeyen itirazları yapmaktadır:

Aşağı oy, kendi kendini değiştiren kodun genellikle kötü uygulama olarak kabul edilmesidir. Küçük montaj programlarının eski günlerinde, koşullu dalları azaltmanın ve performansı artırmanın akıllı bir yoluydu, ancak günümüzde güvenlik riskleri avantajlardan daha ağır basmaktadır. Komut dosyasını çalıştıran kullanıcının komut dosyasında yazma ayrıcalıkları yoksa yaklaşımınız işe yaramaz.

Bunu işaret ederek güvenlik itirazlar cevap herhangi özel izinler olan sadece bir kez gerekli başına yükleme / güncelleme amacıyla eylem / install güncelleme kendinden yüklenen şahsen güvenli oldukça çağrı olur - Senaryoyu. Ben de ona man shbenzer yollarla benzer hedeflere ulaşma başvurusuna işaret ettim . O zamanlar, cevabımda temsil edilebilecek ya da gösterilemeyecek güvenlik kusurları ya da genel olarak tavsiye edilmeyen uygulamalar ne olursa olsun , sorunun cevabında olduğundan daha büyük bir olasılıkla sorunun kendisinden kaynaklandığını belirtmek için uğraşmadım :

Komut dosyasını /path/to/script.sh olarak çalıştırmanın her zaman PATH'de bulunan Zsh'ı kullanması için shebang'ı nasıl ayarlayabilirim?

Memnun değilim, @ jw013 henüz desteklenmeyen argümanını en az birkaç hatalı ifadeyle ileri sürerek itiraz etmeye devam etti :

İki dosya değil, tek bir dosya kullanırsınız. [ man shBaşvurulan] paketi tek bir dosya başka bir dosya değiştirme sahiptir. Kendini değiştiren bir dosyanız var. Bu iki durum arasında belirgin bir fark vardır. Girdi alan ve çıktı üreten bir dosya iyidir. Çalışırken kendini değiştiren yürütülebilir bir dosya genellikle kötü bir fikirdir. İşaret ettiğiniz örnek bunu yapmaz.

İlk başta:

SADECE EXECUTABLE HERHANGİ KOD EXECUTABLE kabuk yazısıdır #!KENDİNİ

(gerçi bile #!olduğunu resmen belirtilmemiş )

{   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

Buna gereken hiç bir etkisi olması için sırayla - Bir kabuk komut dosyası sadece bir metin dosyasıdır okumak onun talimatları sonra başka yürütülebilir dosya tarafından yorumlanır önce nihayet diğer yürütülebilir dosya daha sonra, bu diğer yürütülebilir dosya tarafından kendi yorumunu yürütür arasında kabuk betiği. Öyle mümkün değildir bir kabuk komut dosyası yürütme için ikiden az dosya içerir. zshKendi derleyicisinde olası bir istisna vardır , ancak bununla çok az deneyimim var ve burada hiçbir şekilde temsil edilmiyor.

Bir kabuk betiğinin hashbang amaçlanan yorumlayıcısına işaret etmeli veya ilgisiz olarak atılmalıdır.

KABUK'S TOKEN TANIMA / UYGULAMA DAVRANIŞ STANDARTLARI-tanımlanır

Kabuk, girdisini ayrıştırmak ve yorumlamak için iki temel moda sahiptir: ya geçerli girdisi a'yı tanımlamaktadır <<here_documentya da a'yı tanımlamaktadır { ( command |&&|| list ) ; } &- başka bir deyişle, kabuk ya bir anahtarı okuduktan sonra yürütmesi gereken bir komut için sınırlayıcı olarak yorumlar . bir dosya oluşturmak ve başka bir komut için bir dosya tanımlayıcıya eşlemek için talimatlar olarak. Bu kadar.

Kabuğu yürütmek için komutları yorumlarken, ayrılmış sözcük kümesindeki belirteçleri sınırlar . Gibi belirteci ya da kapatma - uygulanabilir olduğunda - kabuk liste, belirteç bir kapatma ile sınırlanmış kadar böyle bir yeni satır olarak bir komut listesinde okumaya devam gerekir belirteci bir açıklık karşılaştığında })için ({yürütme önce.

Kabuk, basit bir komut ile bileşik komut arasında ayrım yapar . Bileşik komut yürütme önce okunmalıdır komutlar kümesi, ancak kabuk gerçekleştirmez $expansionoluşturucu herhangi basit komutlar bu tek başına her biri yürütür kadar.

Bu nedenle, aşağıdaki örnekte, ;semicolon ayrılmış kelimeler bireysel sınırlayan basit komutlar olmayan kaçan ise \newlineiki ile karakter sınırlayan birleşik komut:

{   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

Bu kılavuz ilkelerin basitleştirilmesidir. Kabuk yapılıları, alt kabukları, mevcut ortamı vb. Düşündüğünüzde çok daha karmaşık hale gelir , ancak buradaki amacım için yeterlidir.

Ve konuşma yerleşik ins ve komut listeleri, bir function() { declaration ; }salt bir atama bir araçtır bileşik komutu a basit komutla. Kabuk $expansions, bildirim deyiminin üzerinde herhangi bir işlem yapmamalı - içermek için <<redirections>- bunun yerine tanımı tek, değişmez bir dize olarak saklamalı ve çağrıldığında yerleşik özel bir kabuk olarak yürütmelidir.

Dolayısıyla, yürütülebilir bir kabuk komut dosyasında bildirilen bir kabuk işlevi, yorumlama kabuğunun belleğinde, burada ekli belgeleri giriş olarak içerecek şekilde genişletilmeyen ve kabuk oluşturulmuş olarak her çağrıldığında kaynak dosyasından bağımsız olarak yürütülen yorumlanmış dize biçiminde depolanır. Kabuğun mevcut ortamı devam ettiği sürece.

A <<HERE-DOCUMENTINLINE DOSYADIR

Yeniden yönlendirme işleçleri <<ve <<-her ikisi de, burada belge olarak bilinen kabuk girdi dosyasında bulunan satırların komut girişine yeniden yönlendirilmesine izin verir .

Bu belgede , bir sonrakinden sonra başlayan \newlineve aralarında s olmayan sadece sınırlayıcı ve a'yı içeren bir çizgi olana kadar devam eden tek bir kelime olarak ele alınacaktır . Sonra bir sonraki belge başlar, eğer varsa. Biçim aşağıdaki gibidir:\newline[:blank:]

[n]<<word
    here-document 
delimiter

... burada isteğe bağlı ndosya tanımlayıcı numarasını temsil eder. Numara atlanırsa, buradaki belge standart girdiyi ifade eder (dosya tanımlayıcı 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

Anlıyorsun? Kabuğun üzerindeki her kabuk için bir dosya oluşturur ve bir dosya tanımlayıcıyla eşler. Gelen zsh, (ba)shkabuğunda düzenli dosya oluşturur /tmp, çıkış döker, bir tanımlayıcı bunu eşler, daha sonra siler /tmptanımlayıcısı çekirdeğin kopyası tüm kalıntılar o yüzden dosyayı. dashtüm bu saçmalıklardan kaçınır ve çıktı işlemini |pipeyönlendirme <<hedefine yönelik anonim bir dosyaya bırakır .

Bu dashşunları yapar :

cmd <<HEREDOC
    $(cmd)
HEREDOC

işlevsel olarak bash's eşdeğer :

cmd <(cmd)

while dashuygulaması en azından POSIXly taşınabilir.

BİRİNCİ DOSYALARI HANGİ YAPAN

Bu yüzden aşağıdaki cevapta:

{    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

Aşağıdakiler olur:

  1. Önce catkabuğun FILEiçine hangi dosyayı oluşturduysa ./fileonu çalıştırılabilir hale getirip yürütürüm.

  2. Çekirdek, atanmış olan bir dosya tanımlayıcıyla yorumlanır #!ve çağrılır ./usr/bin/sh<read ./file

  3. shdizeyi başında başlayan ve biten bileşik komutundan oluşan belleğe eşler ._fn()SCRIPT

  4. Ne zaman _fndenir, shilk önce bir tanımlayıcı tanımlanan dosyayı map yorumlanacağıyla gerekir <<SCRIPT...SCRIPT önce çağırma _fnçünkü yerleşik yarar desteğiyle, özel olarak SCRIPTise _fn'ın<input.

  5. Tarafından dizeleri çıktı printfve commanddışarı yazılır _fn'ın çıkış standardı >&1 - Geçerli kabuk en yönlendirilir hangi ARGV0- ya $0.

  6. cat<&0 standart girdi dosya tanımlayıcısını - SCRIPT- >kesilmiş geçerli kabuğun ARGV0argümanı üzerinde birleştirir veya $0.

  7. Bunu daha önce akım okuma işleminin tamamlanması bileşik komutu , sh execve yeni yeniden yazılmış - - yürütülebilir s $0değişken.

Zaman itibaren ./fileonun içerdiği talimatlar o gerektiğini belirtmek dek denir execyeniden d, shtek onu okur bileşik komutu onları çalıştırır olarak ise, bir anda ./filekendisini hiç bir şey yapmaz mutlu yeni içeriğini kabul hariç. Aslında işte olan dosyalar/usr/bin/sh, /usr/bin/cat, /tmp/sh-something-or-another.

TÜM SONRASI TEŞEKKÜRLER

Yani @ jw013 şunu belirtirse:

Girdi alan ve çıktı üreten bir dosya gayet iyi ...

... bu cevaba yönelik hatalı eleştirisinin ortasında, aslında burada kullanılan tek yöntemi istemeden kınıyor ve temelde şu şekilde çalışıyor:

cat <new_file >old_file

CEVAP

Buradaki tüm cevaplar iyi, ama hiçbiri tam olarak doğru değil. Herkes dinamik ve kalıcı olarak yol açamadığınızı iddia ediyor gibi görünüyor #!bang. İşte yoldan bağımsız bir sapma kurmanın bir gösterimi:

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

ÇIKTI

        $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\\...

Anlıyorsun? Sadece betiğin üzerine yazılmasını sağlıyoruz. Ve bir gitsenkronizasyondan sonra sadece bir kez olur . Bu noktadan sonra #! Bang çizgisinde doğru yol var.

Şimdi hemen hemen hepsi kabartmak var. Bunu güvenli bir şekilde yapmak için ihtiyacınız olan:

  1. Üst kısımda tanımlanan ve alt kısımda yazmayı yapan bir işlev. Bu şekilde, ihtiyacımız olan her şeyi belleğe kaydeder ve üzerine yazmaya başlamadan önce tüm dosyanın okunduğundan emin oluruz.

  2. Yolun ne olması gerektiğini belirlemenin bir yolu. command -vbunun için oldukça iyi.

  3. Yorumlu metinler gerçekten yardımcı olur çünkü gerçek dosyalardır. Bu arada senaryonuzu saklayacaklar. Dizeleri de kullanabilirsiniz ama ...

  4. Kabuğun, komut dosyanızı çalıştıran komutla aynı komut listesinde yazan komutu okuduğundan emin olmalısınız.

Bak:

{   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

execKomutu yalnızca bir satır aşağı taşıdığımı fark ettim . Şimdi:

#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

Komut dosyasının bir sonraki komutta okuyamaması nedeniyle çıktının ikinci yarısını alamıyorum. Yine de, eksik olan tek komut sonuncusu olduğu için:

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

Senaryo olması gerektiği gibi geldi - çoğunlukla heredoc içinde olduğu için - ama doğru planlamıyorsanız, filistinizi kısaltabilirsiniz, bu da bana olan şeydi.


Aşağı oy, kendi kendini değiştiren kodun genellikle kötü uygulama olarak kabul edilmesidir. Küçük montaj programlarının eski günlerinde, koşullu dalları azaltmanın ve performansı artırmanın akıllı bir yoluydu, ancak günümüzde güvenlik riskleri avantajlardan daha ağır basmaktadır. Komut dosyasını çalıştıran kullanıcının komut dosyasında yazma ayrıcalıkları yoksa yaklaşımınız işe yaramaz.
jw013

@ jw013 Açıkçası , betiği yüklemeye veya güncellemeye çalışan kişinin betiği yükleme veya güncelleme izinleri yoksa, yürütülebilir bir betiği yükleme veya güncelleme yaklaşımım işe yaramaz . aslında, bu cevabı burada diğer tüm cevaplardan daha iyi yapan şey budur - gerektiği gibi doğru bir #! bang hattı sağlayabilir ve kurulum sırasında sadece ilk çağrışımda bunu yapmak için özel izinlere ihtiyaç duyar . Ve, yine, sadece kendi kendini değiştiren kodun kötü uygulama olduğu için sözünüzü almayacağım - lütfen çelişkili bir görüş için bakın . man command
mikeserv

lütfen man commandçelişkili bir görüş için bkz . Beni bahsettiğiniz bölüme / paragrafa yönlendirebilir misiniz?
jw013

@ jw013 - benim hatam, içinde man sh- 'komut -v' araması yapın . manGeçen gün baktığım sayfalardan birinde olduğunu biliyordum .
mikeserv

Ben varsayalım bu olduğunu command -vsizden bahsediyorduk örnek man sh. Bu normal görünümlü bir yükleyici komut dosyasıdır, kendini değiştiren bir komut dosyası değildir . Kendi kendine yeten montajcılar bile yalnızca ön değişiklik girdisi içerir ve değişikliklerini başka bir yerde çıkarır. Tavsiye ettiğiniz şekilde kendilerini yeniden yazmazlar.
jw013

1

İşte kendi shebang düzeltmek kendini değiştiren bir komut dosyası için bir yoludur. Bu kod, gerçek komut dosyanızın başına eklenmelidir.

#!/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

Bazı yorumlar:

  • Komut dosyasının bulunduğu dizinde dosya oluşturma ve silme haklarına sahip biri tarafından bir kez çalıştırılmalıdır.

  • Popüler inanca rağmen /bin/shPOSIX kabuğu, hatta POSIX uyumlu işletim sistemleri olduğu garanti edilmediğinden , yalnızca eski bourne kabuk sözdizimini kullanır .

  • PATH'yi "sahte" bir zsh seçmekten kaçınmak için POSIX uyumlu birine ve ardından olası zsh konumlarının bir listesine ayarlar.

  • Bazı nedenlerden dolayı, kendini değiştiren bir komut dosyası kabul edilmezse, biri yerine iki komut dosyası dağıtmak önemsizdir, ilki yama yapmak istediğiniz ve ikincisi, eskisini işlemek için biraz değiştirilmesini önerdiğim komut.


/bin/shNokta iyi biridir - ama bu durumda bir ön modifiye gerekiyor #!hiç? Ve değil awkgibi muhtemel olması sahte olanı olarak zshnedir?
mikeserv

@mikeserv Yanıt POSIX awk'yi çağırmak için güncellendi. Premodified shebang komut dosyası giriş kabuğu olmayan bourne uyumlu olmayan bir kabuk tarafından yorumlanmasını önlemek için orada.
jlliagre

Mantıklı. Çalıştığını, kitaba yapıştığını ve olası kabuk ortamının / dosya işlemenin sağlam bir şekilde anlaşıldığını - özellikle de kullandığınız tüm GNU'ların yaptığı yedekleme dosyalarını gösterir sed -i. Şahsen $PATHbaşka bir cevabın yorumlarında belirtilen ve burada birkaç satırda anlayabildiğim kadar güvenli bir şekilde ele aldığınız sorunun, bağımlılıkları ve / veya titiz ve açık testleri tanımlayarak daha iyi ele alındığını düşünüyorum - örneğin, şimdi getconfolabilir sahte, ama şans nil yakın, olduğu gibi zshveawk.
mikeserv

@ mikeserv, sahte getconf çağırma riskini azaltmak için komut dosyası değiştirildi.
jlliagre

$(getconf PATH)Bourne değil. cp $0 $0.oldzsh sözdizimidir. Bourne eşdeğer olacak cp "$0" "$0.old"İstediğiniz ediyorum gerçicp -- "$0" "$0.old"
Stéphane Chazelas
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.