Shebangs niezależne od ścieżki

20

Mam skrypt, który chcę uruchomić na dwóch komputerach. Te dwie maszyny pobierają kopie skryptu z tego samego repozytorium git. Skrypt musi działać z odpowiednim tłumaczem (np zsh.).

Niestety, zarówno env i zshmieszkają w różnych lokalizacjach na komputerach lokalnych i zdalnych:

Zdalna maszyna

$ which env
/bin/env

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

Maszyna lokalna

$ which env
/usr/bin/env

$which zsh
/usr/local/bin/zsh

Jak skonfigurować shebang, aby uruchomienie skryptu jak /path/to/script.shzawsze używa Zshdostępnych w PATH?

Amelio Vazquez-Reina
źródło
8
Czy jesteś pewien, że envnie ma go w katalogu / bin i / usr / bin? Spróbuj which -a envpotwierdzić.
grawity

Odpowiedzi:

22

Nie można rozwiązać tego bezpośrednio poprzez shebang, ponieważ shebang jest czysto statyczny. To, co możesz zrobić, to mieć trochę »najmniej wspólnego mnożnika« (z perspektywy powłoki) w shebang i ponownie uruchomić skrypt z właściwą powłoką, jeśli ten LCM nie jest zsh. Innymi słowy: czy skrypt wykonywany przez shell znaleźć na wszystkich systemach testowych dla zsh-tylko funkcji i jeśli zakrętach przetestować fałszywe, mieć skrypt execz zsh, gdzie test będzie sukces i po prostu kontynuować.

Jedną wyjątkową cechą jest zshna przykład obecność $ZSH_VERSIONzmiennej:

#!/bin/sh -

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

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

W tym prostym przypadku skrypt jest najpierw wykonywany przez /bin/sh(wszystkie systemy uniksopodobne po latach 80. rozumieją #!i mają /bin/shalbo Bourne'a, albo POSIXA, ale nasza składnia jest kompatybilna z obydwoma). Jeśli nie$ZSH_VERSION jest ustawiony, sam skrypt przechodzi przez . Jeśli jest ustawiony (odpowiednio, skrypt jest już uruchamiany ), test jest po prostu pomijany. Voilà.execzsh$ZSH_VERSIONzsh

To się nie udaje, jeśli zshw $PATHogóle nie ma.

Edycja: Aby upewnić się, to tylko w zwykłych miejscach, można użyć coś podobnegoexeczsh

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

Może to uchronić Cię przed przypadkowym execzaistnieniem czegoś w twoim ciele, czego $PATHnie zshoczekujesz.

Andreas Wiese
źródło
Poparłem to za elegancję, ale w zasadzie ma problemy z bezpieczeństwem / kompatybilnością, jeśli pierwszy zshz nich $PATHnie jest tym, którego się spodziewasz.
Ryan Reich
Próbowałem to rozwiązać. Pytanie brzmi, czy zawsze możesz być pewien, czy plik zshbinarny w standardowych lokalizacjach jest naprawdę zsh.
Andreas Wiese
Możesz dynamicznie śledzić linię! Bang. Możesz także zadać zshsobie pytanie , gdzie to jest zsh -c 'whence zsh'. Mówiąc prościej, możesz po prostu command -v zsh. Zobacz moją odpowiedź, jak dynamicznie śledzić #!bang.
mikeserv
1
Wywołanie pliku zshbinarnego z w $PATHcelu uzyskania ścieżki zshpliku binarnego nie rozwiązałoby problemu wskazanego przez @RyanReich, prawda? :)
Andreas Wiese
Nie, jeśli zshsam się zabijesz , nie, chyba nie. Ale jeśli osadzisz wynikowy ciąg w haszu mieszania, a następnie wykonasz własny skrypt, przynajmniej wiesz, co dostajesz. Nadal byłby to prostszy test niż zapętlenie.
mikeserv
7

Przez lata korzystałem z czegoś podobnego do radzenia sobie z różnymi lokalizacjami Bash w systemach, które wymagały uruchomienia moich skryptów.

Bash / Zsh / itp.

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

Powyższe można łatwo dostosować dla różnych tłumaczy. Kluczowe jest to, że ten skrypt początkowo działa jako powłoka Bourne'a. Następnie rekurencyjnie nazywa się po raz drugi, ale analizuje wszystko powyżej komentarza ### BEGINza pomocą sed.

Perl

Oto podobna sztuczka dla Perla:

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

Ta metoda wykorzystuje zdolność Perla, gdy dany plik do uruchomienia przeanalizuje ten plik, pomijając wszystkie wiersze znajdujące się przed nim #! perl.

slm
źródło
Istnieje wiele problemów: brakujące cytaty, użycie $*zamiast "$@", bezużyteczne użycie eval, status wyjścia niezgłoszony (nie używałeś execdla pierwszego), brak -/ --, komunikaty o błędach nie na stderr, 0 status wyjścia dla warunków błędu , używając / bin / sh dla LIN_BASH, bezużyteczny średnik (kosmetyczny), używając wszystkich wielkich liter dla zmiennych innych niż env. uname -sjest jak uname(uname jest dla nazwy Uniksa). Zapomniałeś wspomnieć, że pomijanie jest uruchamiane przez -xopcję do perl.
Stéphane Chazelas
4

UWAGA: @ jw013 zgłasza następujący nieobsługiwany sprzeciw w komentarzach poniżej:

Wadą jest to, że kod samodmodyfikujący jest ogólnie uważany za złą praktykę. W dawnych czasach małych programów asemblacyjnych był to sprytny sposób na ograniczenie gałęzi warunkowych i poprawę wydajności, ale obecnie zagrożenia bezpieczeństwa przeważają nad zaletami. Twoje podejście nie zadziałałoby, gdyby użytkownik, który uruchomił skrypt, nie miał uprawnień do zapisu na skrypcie.

Odpowiedziałem mu zarzutów zabezpieczeń, wskazując, że jakieś specjalne uprawnienia są wymagane tylko raz na instalacji / aktualizacji działań w celu instalacji / aktualizacji na samodzielne instalowanie skryptu - co ja osobiście nazywam całkiem bezpieczne. Wskazałem mu także man shodniesienie do osiągania podobnych celów za pomocą podobnych środków. W tym czasie nie zadałem sobie trudu, aby wskazać, że niezależnie od wad bezpieczeństwa lub ogólnie nieadekwatnych praktyk, które mogą, ale nie muszą być reprezentowane w mojej odpowiedzi, były one bardziej zakorzenione w samym pytaniu niż w mojej odpowiedzi na to:

Jak skonfigurować shebang, aby uruchamianie skryptu jako /path/to/script.sh zawsze używa Zsh dostępnego w PATH?

Niezadowolony @ jw013 nadal sprzeciwił się, przekazując swój dotychczas nieobsługiwany argument co najmniej kilkoma błędnymi stwierdzeniami:

Używasz jednego pliku, a nie dwóch plików. [ man shOdniesienie] Opakowanie ma jeden plik modyfikacji innego pliku. Plik się modyfikuje. Istnieje wyraźna różnica między tymi dwoma przypadkami. Plik, który pobiera dane wejściowe i generuje dane wyjściowe, jest w porządku. Plik wykonywalny, który zmienia się w trakcie działania, jest ogólnie złym pomysłem. Przykład, na który wskazałeś, tego nie robi.

Na pierwszym miejscu:

JEDYNY WYKONALNY KOD W KAŻDYM WYKONALNYM SKRIPIE ŁUSKI JEST TO #!SAM

(chociaż nawet oficjalnie nie#! jest określony )

{   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

Skrypt powłoki jest tylko plik tekstowy - aby mogła ona mieć żadnego wpływu na wszystko należy czytać przez innego pliku wykonywalnego, jego instrukcje następnie interpretowane przez tego innego pliku wykonywalnego, zanim wreszcie inny plik wykonywalny następnie wykonuje swoją interpretację z następujących Skrypt powłoki. Jest to możliwe za wykonanie skryptu powłoki, aby zaangażować mniej niż dwa pliki. Istnieje możliwy wyjątek we zshwłasnym kompilatorze, ale dzięki temu mam niewielkie doświadczenie i nie ma tu żadnej reprezentacji.

Hashang skryptu powłoki musi wskazywać na zamierzonego interpretera lub zostać odrzucony jako nieistotny.

Skorupa'S TOKEN UZNANIE / WYKONANIE zachowanie jest zdefiniowane NORMY

Powłoka ma dwa podstawowe tryby parsowania i interpretacji danych wejściowych: albo bieżące dane wejściowe definiują a, <<here_documentalbo definiują { ( command |&&|| list ) ; } &- innymi słowy, powłoka interpretuje token jako ogranicznik polecenia, które powinno wykonać po przeczytaniu w lub jako instrukcje, aby utworzyć plik i zamapować go na deskryptorze pliku dla innego polecenia. Otóż ​​to.

Podczas interpretacji poleceń w celu wykonania powłoki rozgraniczają tokeny na zestawie słów zastrzeżonych. Gdy powłoka napotyka otwór żeton musi czytać dalej na liście poleceń aż lista jest albo ograniczona przez zamknięciem tokena takie jak nowej linii - w stosownych przypadkach - czy zamknięcie tokena jak })na ({przed egzekucją.

Powłoka rozróżnia proste i złożone polecenie. Polecenie złożone jest zbiorem poleceń, które należy odczytać przed wykonaniem, ale powłoka nie wykonuje $expansionżadnej z prostych składowych poleceń, dopóki nie wykona pojedynczo każdego z nich.

Tak więc w poniższym przykładzie ;semicolon słowa zastrzeżone ograniczają poszczególne proste polecenia, podczas gdy \newlineznak, który nie jest klawiszem ucieczki, rozgranicza między dwoma poleceniami złożonymi:

{   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

Jest to uproszczenie wytycznych. Staje się znacznie bardziej skomplikowane, gdy weźmie się pod uwagę wbudowane powłoki, podpowłoki, bieżące środowisko itp., Ale dla moich celów tutaj wystarczy.

Mówiąc o wbudowanych i listach poleceń, a function() { declaration ; }jest jedynie sposobem na przypisanie polecenia złożonego do prostego polecenia. Powłoka nie może wykonywać żadnych $expansionsinstrukcji w samej deklaracji - aby ją uwzględnić <<redirections>- ale musi przechowywać definicję jako pojedynczy, dosłowny ciąg znaków i wykonywać ją jako specjalną powłokę wbudowaną po wywołaniu.

Tak więc funkcja powłoki zadeklarowana w wykonywalnym skrypcie powłoki jest przechowywana w pamięci powłoki tłumaczącej w postaci dosłownej - nie jest rozszerzana o dołączone tutaj dokumenty jako dane wejściowe - i wykonywana niezależnie od pliku źródłowego za każdym razem, gdy jest wywoływana jako wbudowana powłoka - tak długo, jak trwa obecne środowisko powłoki.

<<HERE-DOCUMENTJEST inline FILE

Operatory przekierowania <<i <<-oba pozwalają na przekierowanie wierszy zawartych w pliku wejściowym powłoki, znanym jako dokument tutaj, na wejście polecenia.

Niniejszy dokument należy traktować jako pojedyncze słowo, które zaczyna się po następnym \newlinei trwa do momentu, aż pojawi się wiersz zawierający tylko ogranicznik i literę „ a” \newline, bez [:blank:]znaku s pomiędzy nimi. Następnie rozpoczyna się następny dokument tutaj , jeśli taki istnieje. Format jest następujący:

[n]<<word
    here-document 
delimiter

... gdzie opcjonalny nreprezentuje numer deskryptora pliku. Jeśli numer zostanie pominięty, dokument tutaj odnosi się do standardowego wejścia (deskryptor pliku 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

Zobaczysz? Dla każdej powłoki powyżej powłoki tworzy plik i mapuje go na deskryptor pliku. W zsh, (ba)shpowłoce tworzy się zwykły plik /tmp, zrzuca dane wyjściowe, mapuje je na deskryptor, a następnie usuwa /tmpplik, aby kopia deskryptora jądra pozostała. dashunika tych bzdur i po prostu upuszcza przetwarzanie danych wyjściowych do anonimowego |pipepliku skierowanego na <<cel przekierowania .

To sprawia dash, że:

cmd <<HEREDOC
    $(cmd)
HEREDOC

funkcjonalnie równoważny do bash:

cmd <(cmd)

podczas gdy dashimplementacja jest przynajmniej przenośna POSIXly.

KTÓRE TWORZĄ KILKA PLIKÓW

Więc w odpowiedzi poniżej, kiedy to robię:

{    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

Zdarza się:

  1. Po raz pierwszy catzawartość co plik powłoka utworzona na FILEdo ./filesprawiają, że plik wykonywalny, a następnie uruchom go.

  2. Jądro interpretuje #!wywołania i /usr/bin/shz <read deskryptorem pliku przypisanym do ./file.

  3. shodwzorowuje ciąg do pamięci składający się z polecenia złożonego rozpoczynającego się _fn()i kończącego się na SCRIPT.

  4. Kiedy _fnnazywa, shmusi najpierw interpretują następnie mapę do deskryptora pliku zdefiniowanego w <<SCRIPT...SCRIPT przed powołując _fnjako specjalny wbudowane narzędzie, ponieważ SCRIPTjest _fn„s<input.

  5. Wyjście przez ciągi printfi commandsą wypisywane do _fn„s Standard-out >&1 - który jest przekierowany do prądu powłoki użytkownika ARGV0- albo $0.

  6. catłączy swój deskryptor pliku ze <&0 standardowym wejściem - SCRIPT- >z ARGV0argumentem obciętej bieżącej powłoki , lub $0.

  7. Wypełnianie już wczytanej bieżącej komendy złożonej , sh execjest wykonywalnym - i nowo przepisanym - $0argumentem.

Od czasu ./filewywołania, aż zawarte w nim instrukcje określają, że powinien on być execponownie, shodczytuje je pojedynczym złożonym poleceniem naraz, gdy je wykonuje, podczas gdy ./filesam nic nie robi, poza radością przyjmując jego nową zawartość. Pliki, które faktycznie działają, są/usr/bin/sh, /usr/bin/cat, /tmp/sh-something-or-another.

DZIĘKI PO WSZYSTKIM

Więc kiedy @ jw013 określa, że:

Plik, który pobiera dane wejściowe i generuje dane wyjściowe, jest w porządku ...

... wśród swojej błędnej krytyki tej odpowiedzi tak naprawdę nieświadomie akceptuje jedyną zastosowaną tutaj metodę, która w zasadzie polega na:

cat <new_file >old_file

ODPOWIEDŹ

Wszystkie odpowiedzi tutaj są dobre, ale żadna z nich nie jest w pełni poprawna. Wydaje się, że wszyscy twierdzą, że nie można dynamicznie i trwale podążać ścieżką #!bang. Oto demonstracja utworzenia niezależnego shebang od ścieżki:

PRÓBNY

{   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

WYNIK

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

Zobaczysz? Sprawiamy, że skrypt sam się nadpisuje. I zdarza się to tylko raz po gitsynchronizacji. Od tego momentu ma właściwą ścieżkę w linii #! Bang.

Teraz prawie wszystko to po prostu puch. Aby to zrobić bezpiecznie, potrzebujesz:

  1. Funkcja zdefiniowana u góry i wywołana u dołu, która wykonuje zapis. W ten sposób przechowujemy wszystko, czego potrzebujemy w pamięci i upewniamy się, że cały plik został wczytany, zanim zaczniemy nad nim zapisywać.

  2. Jakiś sposób na określenie ścieżki. command -vjest całkiem dobry do tego.

  3. Heredocs naprawdę pomagają, ponieważ są to rzeczywiste pliki. W międzyczasie będą przechowywać Twój skrypt. Możesz także używać ciągów, ale ...

  4. Musisz upewnić się, że powłoka odczytuje polecenie, które zastępuje skrypt na tej samej liście poleceń, co ta, która go wykonuje.

Popatrz:

{   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

Zauważ, że przesunąłem execpolecenie tylko o jedną linię. Teraz:

#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

Nie dostaję drugiej połowy wyniku, ponieważ skrypt nie może odczytać następnego polecenia. Mimo to, ponieważ jedynym brakującym poleceniem było ostatnie:

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

Skrypt przyszedł tak, jak powinien - głównie dlatego, że wszystko było w heredoc - ale jeśli nie zaplanujesz go poprawnie, możesz obciąć strumień plików, co przydarzyło mi się powyżej.

mikeserv
źródło
Wadą jest to, że kod samodmodyfikujący jest ogólnie uważany za złą praktykę. W dawnych czasach małych programów asemblacyjnych był to sprytny sposób na ograniczenie gałęzi warunkowych i poprawę wydajności, ale obecnie zagrożenia bezpieczeństwa przeważają nad zaletami. Twoje podejście nie zadziałałoby, jeśli użytkownik, który uruchomił skrypt, nie miał uprawnień do zapisu na skrypcie.
jw013
@ jw013 Oczywiście moje podejście do instalowania lub aktualizowania skryptu wykonywalnego nie zadziałałoby, gdyby osoba próbująca zainstalować lub zaktualizować skrypt nie miała uprawnień do instalacji lub aktualizacji skryptu. w rzeczywistości to właśnie sprawia, że ​​ta odpowiedź jest lepsza niż każda inna odpowiedź tutaj - może zapewnić dokładną linię #! bang w razie potrzeby i potrzebuje tylko specjalnych uprawnień, aby to zrobić przy pierwszym wywołaniu - podczas instalacji. I znowu, nie będę ci wierzył, że samodmodyfikujący kod jest złą praktyką - proszę zobaczyć man commandsprzeczną opinię.
mikeserv
zobacz man commandsprzeczną opinię - nie znajdując takiej. Czy możesz skierować mnie do konkretnej sekcji / akapitu, o której mówiłeś?
jw013
@ jw013 - mój błąd, jest w man sh- wyszukaj „command -v”. Wiedziałem, że to na jednej ze manstron, na które patrzyłem innego dnia.
mikeserv
Zakładam, że jest to command -vprzykład, o którym mówiłeś man sh. Jest to normalnie wyglądający skrypt instalatora, a nie samodomodujący . Nawet samodzielne instalatory zawierają tylko dane wejściowe przed modyfikacją i wysyłają swoje modyfikacje gdzie indziej. Nie przepisują się tak, jak polecasz.
jw013
1

Oto jeden ze sposobów na posiadanie skryptu samomodyfikującego, który naprawia jego shebang. Ten kod należy dołączyć do rzeczywistego skryptu.

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

Niektóre komentarze:

  • Powinien zostać uruchomiony raz przez osobę, która ma uprawnienia do tworzenia i usuwania plików w katalogu, w którym znajduje się skrypt.

  • Używa tylko starszej składni powłoki bourne, ponieważ pomimo powszechnego przekonania /bin/shnie jest gwarantowana, że ​​jest powłoką POSIX, nawet w systemach operacyjnych zgodnych z POSIX.

  • Ustawiono PATH na zgodny z POSIX, a następnie listę możliwych lokalizacji zsh, aby uniknąć wybrania „fałszywego” zsh.

  • Jeśli z jakiegoś powodu skrypt samomodyfikujący nie jest mile widziany, trywialne byłoby rozpowszechnianie dwóch skryptów zamiast jednego, pierwszy to ten, który chcesz załatać, a drugi, ten, który zasugerowałem nieco zmodyfikowany, aby przetworzyć pierwszy.

jlliagre
źródło
/bin/shPunkt jest jeden dobry - ale w tym przypadku potrzebny jest premodified #!w ogóle? I czy nie jest awktak prawdopodobne, że będzie tak fałszywy, jak zshjest?
mikeserv
@mikeserv Odpowiedź zaktualizowana w celu wywołania awk POSIX. Wstępnie zmodyfikowany shebang ma zapobiegać interpretacji skryptu przez powłokę niezgodną z bourne, gdyby była powłoką logowania.
jlliagre
Ma sens. Głosowałem za tym, ponieważ działa, jest przylegający do książki i pokazuje solidne zrozumienie możliwego środowiska powłoki / obsługi plików - szczególnie plików kopii zapasowych, których używasz, co sed -izresztą robi wszystko GNU . Osobiście uważam, że $PATHproblem zanotowany w komentarzach do innej odpowiedzi, który rozwiązujesz tak bezpiecznie, jak mogę to tutaj przedstawić w kilku wierszach, lepiej jest rozwiązać poprzez proste i jednoznaczne zdefiniowanie zależności i / lub rygorystyczne i jawne testowanie - na przykład teraz getconfmoże być fałszywe, ale szanse są bliskie zeru, tak samo jak dla zshiawk.
mikeserv
@mikeserv, skrypt zmodyfikowany w celu zmniejszenia ryzyka wywołania fałszywego getconf.
jlliagre
$(getconf PATH)nie jest Bourne. cp $0 $0.oldjest składnią zsh. Odpowiednik Bourne'a byłby cp "$0" "$0.old"jednak cp -- "$0" "$0.old"
pożądany