Généralement, si vous éditez un scrpit, toutes les utilisations du script en cours sont sujettes aux erreurs.
Autant que je sache, bash (les autres shells aussi?) Lisent le script de manière incrémentielle. Par conséquent, si vous modifiez le fichier de script en externe, il commence à lire le mauvais contenu. Y a-t-il un moyen de le prévenir?
Exemple:
sleep 20
echo test
Si vous exécutez ce script, bash lira la première ligne (disons 10 octets) et s'endormira. À la reprise, le script peut contenir différents contenus commençant au 10ème octet. Je suis peut-être au milieu d'une ligne dans le nouveau script. Ainsi, le script en cours sera brisé.
\n
ferait l'affaire? Peut-être qu'un sous-shell()
fera l'affaire? Je ne suis pas très expérimenté, aidez-moi!sleep 20 ;\n echo test ;\n sleep 20
et que je commence à l'éditer, il risque de mal se comporter. Par exemple, bash pourrait lire les 10 premiers octets du script, comprendre lasleep
commande et se mettre en veille. Après la reprise, le fichier contiendrait un contenu différent commençant à 10 octets.Réponses:
Oui, les shells, et
bash
en particulier, veillent à lire le fichier ligne par ligne, de sorte qu'il fonctionne de la même manière que lorsque vous l'utilisez de manière interactive.Vous remarquerez que lorsque le fichier ne peut pas être recherché (comme un tube),
bash
même lit un octet à la fois pour être sûr de ne pas lire après le\n
caractère. Lorsque le fichier peut être recherché, il optimise en lisant des blocs entiers à la fois, mais en recherchant après\n
.Cela signifie que vous pouvez faire des choses comme:
Ou écrivez des scripts qui se mettent à jour. Ce que vous ne pourriez pas faire si cela ne vous donnait pas cette garantie.
Maintenant, il est rare que vous souhaitiez faire de telles choses et, comme vous l'avez découvert, cette fonctionnalité a tendance à gêner plus souvent qu'elle n'est utile.
Pour éviter cela, vous pouvez essayer de vous assurer que vous ne modifiez pas le fichier sur place (par exemple, modifiez une copie et déplacez la copie sur place (comme
sed -i
ouperl -pi
et certains éditeurs le font par exemple)).Ou vous pouvez écrire votre script comme:
(Notez qu'il est important que le
exit
soit sur la même ligne que}
, bien que vous puissiez aussi le mettre entre les accolades juste avant le dernier).ou:
Le shell devra lire le script jusqu’à
exit
avant de commencer à faire quoi que ce soit. Cela garantit que le shell ne lira plus le script.Cela signifie que tout le script sera stocké dans la mémoire.
Cela peut également affecter l'analyse du script.
Par exemple, dans
bash
:Produirait que U + 00E9 codé en UTF-8. Cependant, si vous le changez en:
La
\ue9
volonté sera développée dans le jeu de caractères qui était en vigueur au moment de l'analyse de la commande et qui, dans ce cas, est antérieure à l'export
exécution de la commande.Notez également que si la commande
source
aka.
est utilisée, avec certains shells, vous aurez le même genre de problème pour les fichiers sourcés.Ce n'est pas le cas de
bash
bien dont lasource
commande lit le fichier complètement avant de l'interpréter. Si vous écrivezbash
spécifiquement, vous pouvez en faire usage en ajoutant au début du script:(Je ne m'appuierais pas là-dessus, même si, comme vous pouvez l'imaginer, les versions futures
bash
pourraient changer ce comportement qui peut actuellement être considéré comme une limitation (bash et AT & T ksh sont les seuls shells de type POSIX qui se comportent comme ça autant que l'on peut en juger) et l'already_sourced
astuce est un peu fragile car elle suppose que la variable n'est pas dans l'environnement, sans parler du fait qu'elle affecte le contenu de la variable BASH_SOURCE)la source
}; exec true
. De cette façon, il n'y a aucune exigence sur les nouvelles lignes à la fin du fichier, ce qui convient à certains éditeurs (comme emacs). Tous les tests auxquels je pourrais penser fonctionnent correctement}; exec true
}; exit
? Vous perdez également le statut de sortie.. script
) est utilisée.Vous devez simplement supprimer le fichier (c'est-à-dire le copier, le supprimer, renommer la copie en son nom d'origine). En fait, de nombreux éditeurs peuvent être configurés pour le faire à votre place. Lorsque vous modifiez un fichier et que vous enregistrez un tampon modifié dans celui-ci, au lieu de le remplacer, il renomme l'ancien fichier, en crée un nouveau et place le nouveau contenu dans le nouveau fichier. Par conséquent, tout script en cours devrait continuer sans problèmes.
En utilisant un système de contrôle de version simple tel que RCS, qui est facilement disponible pour vim et emacs, vous bénéficiez du double avantage de disposer d'un historique de vos modifications. Le système de paiement devrait par défaut supprimer le fichier actuel et le recréer avec les modes appropriés. (Méfiez-vous des liens durs avec de tels fichiers bien sûr).
la source
La solution la plus simple:
De cette façon, bash lira l’ensemble du
{}
bloc avant de l’exécuter et laexit
directive s’assurera que rien ne sera lu en dehors du bloc de code.Si vous ne voulez pas "exécuter" le script, mais plutôt pour le "trouver", vous avez besoin d'une solution différente. Cela devrait fonctionner alors:
Ou si vous souhaitez contrôler directement le code de sortie:
Voilà! Ce script peut être édité, source et exécuté en toute sécurité. Vous devez toujours vous assurer de ne pas le modifier en quelques millisecondes lors de sa lecture initiale.
la source
Preuve de concept. Voici un script qui se modifie lui-même:
nous voyons la version modifiée imprimer
En effet, bash chargements conserve un descripteur de fichier à ouvrir au script, de sorte que les modifications apportées au fichier sont immédiatement visibles.
Si vous ne souhaitez pas mettre à jour la copie en mémoire, dissociez le fichier d'origine et remplacez-le.
Une façon de faire est d'utiliser sed -i.
preuve de concept
Si vous utilisez un éditeur pour modifier le script, il suffit peut-être d'activer la fonctionnalité "Conserver une copie de sauvegarde" pour que l'éditeur écrive la version modifiée dans un nouveau fichier au lieu de remplacer le fichier existant.
la source
bash
n'ouvre pas le fichier avecmmap()
. Veillez simplement à lire une ligne à la fois selon vos besoins, comme lorsque vous recevez les commandes d'un terminal lorsqu'il est interactif.Envelopper votre script dans un bloc
{}
est probablement la meilleure option mais nécessite de changer vos scripts.serait la deuxième meilleure option (en supposant que tmpfs ) l’inconvénient est qu’elle casse $ 0 si vos scripts l’utilisent.
utiliser quelque chose comme
F=test.sh; tail -n $(cat "$F" | wc -l) "$F" | bash
est moins idéal car il doit garder tout le fichier en mémoire et casser 0 $.il faut éviter de toucher au fichier d'origine afin que, la dernière fois modifiée, les verrous de lecture et les liens physiques ne soient pas perturbés. De cette façon, vous pouvez laisser un éditeur ouvert pendant l’exécution du fichier et rsync ne contrôlera pas inutilement le fichier pour les sauvegardes et les liens physiques fonctionneront comme prévu.
Le remplacement du fichier en mode édition fonctionnerait, mais il est moins robuste car il n’est pas opposable à d’autres scripts / utilisateurs /. Et encore, cela romprait les liens durs.
la source
tac test.sh | tac | bash