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 zsh
vivent 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.sh
utilise toujours le Zsh
disponible dans PATH
?
env
ne pas être dans / bin et / usr / bin? Essayezwhich -a env
de confirmer.Réponses:
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
zsh
seule fonctionnalité et si le test s'avère faux, ayez le scriptexec
aveczsh
, où le test réussira et vous continuerez.Une caractéristique unique dans
zsh
, par exemple, est la présence de la$ZSH_VERSION
variable: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_VERSION
n'est pas défini, le scriptexec
est 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$PATH
tout.Edit: Pour vous assurer, vous ne
exec
unzsh
dans les endroits habituels, vous pouvez utiliser quelque chose commeCela pourrait vous éviter de mettre accidentellement
exec
quelque chose dans votre$PATH
qui n'est pas celuizsh
que vous attendez.la source
zsh
en$PATH
est pas celui que vous attendez.zsh
binaire dans les emplacements standard est vraiment unzsh
.zsh
où il se trouvezsh -c 'whence zsh'
. Plus simplement, vous pouvez simplementcommand -v zsh
. Voir ma réponse pour savoir comment suivre dynamiquement le fichier#!bang
.zsh
binaire de$PATH
pour obtenir le chemin duzsh
binaire ne résoudrait pas tout à fait le problème signalé par @RyanReich, n'est-ce pas? :)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.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.
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
### BEGIN
utilisantsed
.Perl
Voici une astuce similaire pour Perl:
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
.la source
$*
au lieu de"$@"
, utilisation inutile d'eval, état de sortie non signalé (vous ne l'avez pas utiliséexec
pour 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 -s
est commeuname
(uname est pour le nom Unix). Vous avez oublié de mentionner que le saut est déclenché par l'-x
option toperl
.REMARQUE: @ jw013 émet l' objection non prise en charge suivante dans les commentaires ci-dessous:
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 sh
ré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: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:
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 )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
zsh
le 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_document
soit 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
$expansion
sur 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é\newline
délimite entre les deux commandes composées: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$expansions
instruction 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-DOCUMENT
EST UN FICHIER EN LIGNETu vois? Pour chaque shell au-dessus du shell crée un fichier et le mappe à un descripteur de fichier. Dans
zsh, (ba)sh
le shell, crée un fichier normal/tmp
, décharge la sortie, le mappe à un descripteur, puis supprime le/tmp
fichier 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|pipe
fichier anonyme destiné à la<<
cible de redirection .Cela fait
dash
:fonctionnellement équivalent à
bash
:tandis que
dash
l'implémentation est au moins POSIXly portable.QUI FAIT PLUSIEURS FICHIERS
Donc, dans la réponse ci-dessous quand je le fais:
Les événements suivants se produisent:
J'ai d' abord
cat
le contenu de tout fichier créé pour le shellFILE
dans./file
, le rendre exécutable, puis l' exécuter.Le noyau interprète les
#!
appels et/usr/bin/sh
avec un<read
descripteur de fichier affecté à./file
.sh
mappe une chaîne en mémoire composée de la commande composée commençant à_fn()
et se terminant àSCRIPT
.Quand
_fn
est appelé, ilsh
faut d' abord la carte puis à interpréter compte un descripteur du fichier défini dans<<SCRIPT...SCRIPT
avant d' invoquer_fn
comme très spécial , car l' utilitéSCRIPT
est_fn
« s<input.
La sortie des chaînes par
printf
etcommand
sont écrites dans_fn
l » standard out>&1
- qui est redirigé vers la coquille de courantARGV0
- ou$0
.cat
concatène son descripteur de fichier d'<&0
entrée standard -SCRIPT
- sur l' argument>
du shell courant tronquéARGV0
, ou$0
.Complétant sa commande composée actuelle déjà lue ,
sh exec
s l'$0
argument exécutable - et nouvellement réécrit - .Du moment où il
./file
est appelé jusqu'à ce que ses instructions contenues spécifient qu'il doit être àexec
nouveau d, lesh
lit dans une seule commande composée à la fois pendant qu'il les exécute, tandis que./file
lui - 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:
... 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:
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
PRODUCTION
Tu vois? Nous faisons juste écraser le script lui-même. Et cela ne se produit qu'une seule fois après une
git
synchronisation. À 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é:
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.
Une façon de déterminer quel devrait être le chemin.
command -v
est assez bon pour ça.Les Heredocs aident vraiment car ce sont de vrais fichiers. Ils entreposeront votre script en attendant. Vous pouvez également utiliser des chaînes mais ...
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:
Notez que je n'ai déplacé la
exec
commande que d'une ligne. Maintenant: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:
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.
la source
man command
pour une opinion contradictoire.man command
pour une opinion contradictoire - ne pas en trouver un. Pouvez-vous m'orienter vers la section ou le paragraphe dont vous parliez?man sh
- recherchez 'commande -v'. Je savais que c'était dans l'une desman
pages que je regardais l'autre jour.command -v
man sh
Voici une façon d'avoir un script auto-modifiant qui corrige son shebang. Ce code doit être ajouté à votre script actuel.
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/sh
n'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.
la source
/bin/sh
point est bon - mais dans ce cas avez-vous besoin d'un prémodifié#!
? Et n'est-il pasawk
aussi susceptible d'être faux que l'zsh
est?sed -i
fait de toute façon. Personnellement, je pense que le$PATH
problè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, maintenantgetconf
pourrait être faux, mais les chances sont presque nulles, comme c'était le cas pourzsh
etawk.
$(getconf PATH)
n'est pas Bourne.cp $0 $0.old
est la syntaxe zsh. L'équivalent de Bourne serait cecp "$0" "$0.old"
que vous voudriezcp -- "$0" "$0.old"