Shebangs indépendants du chemin

20

J'ai un script que je veux pouvoir exécuter sur deux machines. Ces deux machines obtiennent des copies du script à partir du même référentiel git. Le script doit s'exécuter avec le bon interprète (par exemple zsh).

Malheureusement, les deux env et zshvivent dans des endroits différents sur les machines locales et distantes:

Machine à distance

$ which env
/bin/env

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

Machine locale

$ which env
/usr/bin/env

$which zsh
/usr/local/bin/zsh

Comment puis-je configurer le shebang pour que l'exécution du script /path/to/script.shutilise toujours le Zshdisponible dans PATH?

Amelio Vazquez-Reina
la source
8
Êtes-vous sûr de envne pas être dans / bin et / usr / bin? Essayez which -a envde confirmer.
user1686

Réponses:

22

Vous ne pouvez pas résoudre ce problème directement via shebang, car le shebang est purement statique. Ce que vous pourriez faire, c'est d'avoir un «multiplicateur le moins commun» (du point de vue du shell) dans le shebang et réexécuter votre script avec le bon shell, si ce LCM n'est pas zsh. En d'autres termes: faites exécuter votre script par un shell sur tous les systèmes, testez une zshseule fonctionnalité et si le test s'avère faux, ayez le script execavec zsh, où le test réussira et vous continuerez.

Une caractéristique unique dans zsh, par exemple, est la présence de la $ZSH_VERSIONvariable:

#!/bin/sh -

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

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

Dans ce cas simple, le script est d'abord exécuté par /bin/sh(tous les systèmes de type Unix post-80 comprennent #!et ont un /bin/sh, Bourne ou POSIX mais notre syntaxe est compatible avec les deux). Si $ZSH_VERSIONn'est pas défini, le script execest lui-même traversé zsh. Si$ZSH_VERSION est défini (resp. Le script est déjà exécuté zsh), le test est simplement ignoré. Voilà.

Cela échoue uniquement si zsh n'est pas du $PATHtout.

Edit: Pour vous assurer, vous ne execun zshdans les endroits habituels, vous pouvez utiliser quelque chose comme

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

Cela pourrait vous éviter de mettre accidentellement execquelque chose dans votre $PATHqui n'est pas celui zshque vous attendez.

Andreas Wiese
la source
J'upvoted cette élégance mais elle, en principe, les questions de sécurité / compatibilité, si le premier zshen $PATHest pas celui que vous attendez.
Ryan Reich
J'ai essayé d'y répondre. La question est de savoir si vous pouvez toujours être sûr qu'un zshbinaire dans les emplacements standard est vraiment un zsh.
Andreas Wiese
Vous pouvez suivre dynamiquement la ligne! Bang. Vous pouvez également vous demander zshoù il se trouve zsh -c 'whence zsh'. Plus simplement, vous pouvez simplement command -v zsh. Voir ma réponse pour savoir comment suivre dynamiquement le fichier #!bang.
mikeserv
1
Appeler le zshbinaire de $PATHpour obtenir le chemin du zshbinaire ne résoudrait pas tout à fait le problème signalé par @RyanReich, n'est-ce pas? :)
Andreas Wiese
Pas si vous vous exécutez zsh, non, je suppose que non. Mais si vous intégrez la chaîne résultante dans votre hachage, puis exécutez votre propre script, vous savez au moins ce que vous obtenez. Pourtant, cela ferait un test plus simple que de le boucler.
mikeserv
7

Pendant des années, j'ai utilisé quelque chose de similaire pour gérer les différents emplacements de Bash sur les systèmes dont j'avais besoin pour exécuter mes scripts.

Bash / Zsh / etc.

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

Ce qui précède peut être facilement adapté pour une variété d'interprètes. L'élément clé est que ce script s'exécute initialement en tant que shell Bourne. Il s'appelle alors récursivement une deuxième fois, mais analyse tout au-dessus du commentaire en ### BEGINutilisantsed .

Perl

Voici une astuce similaire pour 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...

Cette méthode utilise la capacité de Perl quand on lui donne un fichier à exécuter analysera ledit fichier en sautant toutes les lignes qui sont avant la ligne #! perl.

slm
la source
Un certain nombre de problèmes: citations manquantes, utilisation de $*au lieu de "$@", utilisation inutile d'eval, état de sortie non signalé (vous ne l'avez pas utilisé execpour le premier), manquant -/ --, messages d'erreur absents de stderr, 0 état de sortie pour les conditions d'erreur , en utilisant / bin / sh pour LIN_BASH, point-virgule inutile (cosmétique), en utilisant toutes les majuscules pour les variables non env. uname -sest comme uname(uname est pour le nom Unix). Vous avez oublié de mentionner que le saut est déclenché par l' -xoption to perl.
Stéphane Chazelas
4

REMARQUE: @ jw013 émet l' objection non prise en charge suivante dans les commentaires ci-dessous:

Le vote négatif est dû au fait que le code auto-modifiant est généralement considéré comme une mauvaise pratique. À l'époque des petits programmes d'assemblage, c'était un moyen intelligent de réduire les branches conditionnelles et d'améliorer les performances, mais de nos jours les risques de sécurité l'emportent sur les avantages. Votre approche ne fonctionnerait pas si l'utilisateur qui a exécuté le script n'avait pas de privilèges d'écriture sur le script.

J'ai répondu à ses objections de sécurité en soulignant que toutes les autorisations spéciales ne sont requises qu'une seule fois par action d' installation / mise à jour afin d' installer / mettre à jour le script auto-installable - que j'appellerais personnellement assez sécurisé. Je lui ai également fait man shréférence à une référence à la réalisation d'objectifs similaires par des moyens similaires. À l'époque, je n'ai pas pris la peine de souligner que, quelles que soient les failles de sécurité ou les autres pratiques généralement déconseillées qui peuvent ou non être représentées dans ma réponse, elles étaient plus probablement enracinées dans la question elle-même que dans ma réponse:

Comment puis-je configurer le shebang pour que l'exécution du script en tant que /path/to/script.sh utilise toujours le Zsh disponible dans PATH?

Non satisfait, @ jw013 a continué de s'opposer en approfondissant son argument encore non pris en charge avec au moins quelques déclarations erronées:

Vous utilisez un seul fichier, pas deux fichiers. Le package [ man shréférencé] a un fichier modifier un autre fichier. Vous avez un fichier qui se modifie. Il existe une nette différence entre ces deux cas. Un fichier qui prend une entrée et produit une sortie est bien. Un fichier exécutable qui se modifie en cours d'exécution est généralement une mauvaise idée. L'exemple que vous avez cité ne fait pas cela.

En premier lieu:

LE SEUL CODE EXÉCUTABLE DANS TOUT SCRIPT EXECUTABLE SHELL EST LE#! LUI MÊME

(bien que même #!est officiellement non précisée )

{   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

Un script shell est juste un fichier texte - pour qu'il ait un quelconque effet, il doit être lu par un autre fichier exécutable, ses instructions ensuite interprétées par cet autre fichier exécutable, avant que finalement l'autre fichier exécutable n'exécute son interprétation de la script shell. Il n'est pas possible que l'exécution d'un fichier de script shell implique moins de deux fichiers. Il existe une exception possible dans zshle propre compilateur de, mais avec cela j'ai peu d'expérience et il n'est en aucune façon représenté ici.

Le hashbang d'un script shell doit pointer vers son interpréteur prévu ou être rejeté comme non pertinent.

LE COMPORTEMENT DE RECONNAISSANCE / EXÉCUTION DU JETON DE LA COQUILLE EST DÉFINI PAR LES NORMES

Le shell a deux modes de base pour analyser et interpréter son entrée: soit son entrée actuelle définit un <<here_documentsoit il définit un { ( command |&&|| list ) ; } &- en d'autres termes, le shell interprète un jeton comme un délimiteur pour une commande qu'il doit exécuter une fois qu'il l'a lu dans ou comme instructions pour créer un fichier et le mapper à un descripteur de fichier pour une autre commande. C'est ça.

Lors de l'interprétation des commandes à exécuter, le shell délimite les jetons sur un ensemble de mots réservés. Lorsque le shell rencontre un jeton d'ouverture, il doit continuer à lire dans une liste de commandes jusqu'à ce que la liste soit délimitée par un jeton de fermeture tel qu'une nouvelle ligne - le cas échéant - ou le jeton de fermeture comme })pour({ avant l' exécution.

Le shell fait la distinction entre une commande simple et une commande composée. La commande composée est l'ensemble des commandes qui doivent être lues avant l'exécution, mais le shell ne s'exécute $expansionsur aucune de ses commandes simples constitutives tant qu'il n'exécute pas chacune individuellement.

Ainsi, dans l'exemple suivant, les ;semicolon mots réservés délimitent des commandes simples individuelles tandis que le caractère non échappé \newlinedélimite entre les deux commandes composées:

{   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

Il s'agit d'une simplification des lignes directrices. Cela devient beaucoup plus compliqué lorsque vous considérez les commandes intégrées au shell, les sous-coquilles, l'environnement actuel , etc., mais, pour mes besoins ici, c'est suffisant.

Et en parlant de commandes intégrées et de listes de commandes, a function() { declaration ; }est simplement un moyen d'assigner une commande composée à une commande simple. Le shell ne doit exécuter aucune $expansionsinstruction sur la déclaration elle-même - à inclure <<redirections>- mais doit à la place stocker la définition sous la forme d'une chaîne littérale unique et l'exécuter comme un shell spécial intégré lorsqu'il est appelé.

Ainsi, une fonction shell déclarée dans un script shell exécutable est stockée dans la mémoire du shell interpréteur sous sa forme de chaîne littérale - non développée pour inclure les documents joints ici en entrée - et exécutée indépendamment de son fichier source chaque fois qu'elle est appelée en tant que shell intégré - aussi longtemps que dure l'environnement actuel du shell.

A <<HERE-DOCUMENTEST UN FICHIER EN LIGNE

Les opérateurs de redirection <<et les <<-deux permettent la redirection des lignes contenues dans un fichier d'entrée shell, appelé ici-document, vers l'entrée d'une commande.

Le document ici doit être traité comme un seul mot qui commence après le suivant \newlineet continue jusqu'à ce qu'il y ait une ligne contenant uniquement le délimiteur et un \newline, sans [:blank:]s entre les deux. Ensuite, le prochain document ici commence, s'il y en a un. Le format est le suivant:

[n]<<word
    here-document 
delimiter

... où l'option nreprésente le numéro de descripteur de fichier. Si le nombre est omis, le document ici fait référence à une entrée standard (descripteur de fichier 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

Tu vois? Pour chaque shell au-dessus du shell crée un fichier et le mappe à un descripteur de fichier. Dans zsh, (ba)shle shell, crée un fichier normal /tmp, décharge la sortie, le mappe à un descripteur, puis supprime le /tmpfichier afin que la copie du noyau du descripteur soit tout ce qui reste. dashévite toutes ces absurdités et laisse simplement son traitement de sortie dans un |pipefichier anonyme destiné à la <<cible de redirection .

Cela fait dash:

cmd <<HEREDOC
    $(cmd)
HEREDOC

fonctionnellement équivalent à bash:

cmd <(cmd)

tandis que dashl'implémentation est au moins POSIXly portable.

QUI FAIT PLUSIEURS FICHIERS

Donc, dans la réponse ci-dessous quand je le fais:

{    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

Les événements suivants se produisent:

  1. J'ai d' abord catle contenu de tout fichier créé pour le shell FILEdans ./file, le rendre exécutable, puis l' exécuter.

  2. Le noyau interprète les #!appels et /usr/bin/shavec un <read descripteur de fichier affecté à ./file.

  3. shmappe une chaîne en mémoire composée de la commande composée commençant à _fn()et se terminant à SCRIPT.

  4. Quand _fnest appelé, il shfaut d' abord la carte puis à interpréter compte un descripteur du fichier défini dans <<SCRIPT...SCRIPT avant d' invoquer _fncomme très spécial , car l' utilité SCRIPTest _fn« s<input.

  5. La sortie des chaînes par printfet commandsont écrites dans _fnl » standard out >&1 - qui est redirigé vers la coquille de courant ARGV0- ou $0.

  6. catconcatène son descripteur de fichier d' <&0 entrée standard - SCRIPT- sur l' argument >du shell courant tronqué ARGV0, ou $0.

  7. Complétant sa commande composée actuelle déjà lue , sh execs l' $0argument exécutable - et nouvellement réécrit - .

Du moment où il ./fileest appelé jusqu'à ce que ses instructions contenues spécifient qu'il doit être à execnouveau d, le shlit dans une seule commande composée à la fois pendant qu'il les exécute, tandis que ./filelui - même ne fait rien du tout, sauf accepte avec plaisir son nouveau contenu. Les fichiers qui sont réellement au travail sont/usr/bin/sh, /usr/bin/cat, /tmp/sh-something-or-another.

MERCI, APRÈS TOUT

Ainsi, lorsque @ jw013 spécifie que:

Un fichier qui prend une entrée et produit une sortie est très bien ...

... au milieu de sa critique erronée de cette réponse, il approuve en fait involontairement la seule méthode utilisée ici, qui fonctionne essentiellement comme suit:

cat <new_file >old_file

RÉPONDRE

Toutes les réponses ici sont bonnes, mais aucune d'entre elles n'est entièrement correcte. Tout le monde semble prétendre que vous ne pouvez pas suivre votre chemin de manière dynamique et permanente #!bang. Voici une démonstration de la configuration d'un shebang indépendant du chemin:

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

PRODUCTION

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

Tu vois? Nous faisons juste écraser le script lui-même. Et cela ne se produit qu'une seule fois après une gitsynchronisation. À partir de là, il a le bon chemin dans la ligne #! Bang.

Maintenant, presque tout cela n'est que duvet. Pour ce faire, vous avez besoin en toute sécurité:

  1. Une fonction définie en haut et appelée en bas qui fait l'écriture. De cette façon, nous stockons tout ce dont nous avons besoin en mémoire et nous assurons que le fichier entier est lu avant de commencer à l'écrire.

  2. Une façon de déterminer quel devrait être le chemin. command -vest assez bon pour ça.

  3. Les Heredocs aident vraiment car ce sont de vrais fichiers. Ils entreposeront votre script en attendant. Vous pouvez également utiliser des chaînes mais ...

  4. Vous devez vous assurer que le shell lit dans la commande qui écrase votre script dans la même liste de commandes que celle qui l'exécute.

Regardez:

{   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

Notez que je n'ai déplacé la execcommande que d'une ligne. Maintenant:

#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

Je n'obtiens pas la seconde moitié de la sortie car le script ne peut pas lire dans la commande suivante. Pourtant, parce que la seule commande manquante était la dernière:

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

Le script est apparu comme il se doit - principalement parce que tout était dans l'hérédoc - mais si vous ne le planifiez pas correctement, vous pouvez tronquer votre flux de fichiers, ce qui m'est arrivé ci-dessus.

mikeserv
la source
Le vote négatif est dû au fait que le code auto-modifiant est généralement considéré comme une mauvaise pratique. À l'époque des petits programmes d'assemblage, c'était un moyen intelligent de réduire les branches conditionnelles et d'améliorer les performances, mais de nos jours les risques de sécurité l'emportent sur les avantages. Votre approche ne fonctionnerait pas si l'utilisateur qui a exécuté le script n'avait pas de privilèges d'écriture sur le script.
jw013
@ jw013 Évidemment, mon approche pour installer ou mettre à jour un script exécutable ne fonctionnerait pas si la personne qui tentait d' installer ou de mettre à jour le script n'avait pas les autorisations pour installer ou mettre à jour le script. en fait, c'est précisément ce qui rend cette réponse meilleure que toutes les autres réponses ici - elle peut fournir une ligne #! bang précise si nécessaire et n'a besoin que d'autorisations spéciales pour le faire lors de la première invocation - lors de l' installation. Et, encore une fois, je ne vais pas simplement croire sur parole que le code d'auto-modification est une mauvaise pratique - veuillez consulter man commandpour une opinion contradictoire.
mikeserv
s'il vous plaît voir man commandpour une opinion contradictoire - ne pas en trouver un. Pouvez-vous m'orienter vers la section ou le paragraphe dont vous parliez?
jw013
@ jw013 - mon erreur, c'est dans man sh- recherchez 'commande -v'. Je savais que c'était dans l'une des manpages que je regardais l'autre jour.
mikeserv
Je suppose que c'est l' exemple que vous parliez de . Il s'agit d'un script d'installation d'apparence normale, et non d'un script auto-modifiable . Même les installateurs autonomes ne contiennent que des entrées de pré-modification et produisent leurs modifications ailleurs. Ils ne se réécrivent pas comme vous le recommandez. command -vman sh
jw013
1

Voici une façon d'avoir un script auto-modifiant qui corrige son shebang. Ce code doit être ajouté à votre script actuel.

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

Certains commentaires:

  • Il doit être exécuté une fois par une personne autorisée à créer et supprimer des fichiers dans le répertoire où se trouve le script.

  • Il n'utilise que la syntaxe héritée du shell bourne car, malgré la croyance populaire, il /bin/shn'est pas garanti qu'il s'agit d'un shell POSIX, même en OS compatibles POSIX.

  • Il a défini le PATH sur un emplacement compatible POSIX suivi d'une liste d'emplacements zsh possibles pour éviter de choisir un zsh "bidon".

  • Si, pour une raison quelconque, un script auto-modifiant n'est pas le bienvenu, il serait trivial de distribuer deux scripts au lieu d'un, le premier étant celui que vous souhaitez patcher et le second, celui que j'ai suggéré légèrement modifié pour traiter le premier.

jlliagre
la source
Le /bin/shpoint est bon - mais dans ce cas avez-vous besoin d'un prémodifié #!? Et n'est-il pas awkaussi susceptible d'être faux que l' zshest?
mikeserv
@mikeserv Réponse mise à jour pour appeler le awk POSIX. Le shebang prémodifié est là pour empêcher le script d'être interprété par un shell non bourne compatible s'il s'agit de votre shell de connexion.
jlliagre
Logique. Je l'ai voté parce que cela fonctionne, il s'en tient au livre et il démontre une bonne compréhension de l'environnement shell possible / de la gestion des fichiers - en particulier des fichiers de sauvegarde que vous utilisez, ce que GNU sed -ifait de toute façon. Personnellement, je pense que le $PATHproblème noté dans les commentaires sur une autre réponse et que vous abordez avec autant de sécurité que je peux le comprendre ici en quelques lignes est mieux géré en définissant simplement et explicitement les dépendances et / ou des tests rigoureux et explicites - par exemple, maintenant getconfpourrait être faux, mais les chances sont presque nulles, comme c'était le cas pour zshetawk.
mikeserv
@mikeserv, script modifié pour réduire le risque d'appeler un faux getconf.
jlliagre
$(getconf PATH)n'est pas Bourne. cp $0 $0.oldest la syntaxe zsh. L'équivalent de Bourne serait ce cp "$0" "$0.old"que vous voudriezcp -- "$0" "$0.old"
Stéphane Chazelas