J'ai une question générale, qui pourrait être le résultat d'une mauvaise compréhension de la façon dont les processus sont gérés sous Linux.
Pour mes besoins, je vais définir un «script» comme un extrait de code bash enregistré dans un fichier texte avec les autorisations d'exécution activées pour l'utilisateur actuel.
J'ai une série de scripts qui s'appellent en tandem. Par souci de simplicité, je les appellerai les scripts A, B et C. Le script A exécute une série d'instructions puis s'arrête, il exécute ensuite le script B, puis il s'arrête, puis il exécute le script C. En d'autres termes, la série des étapes est quelque chose comme ceci:
Exécutez le script A:
- Série de déclarations
- Pause
- Exécuter le script B
- Pause
- Exécuter le script C
Je sais par expérience que si j'exécute le script A jusqu'à la première pause, puis que j'effectue des modifications dans le script B, ces modifications se reflètent dans l'exécution du code lorsque je l'autorise à reprendre. De même, si j'apporte des modifications au script C alors que le script A est toujours en pause, puis que je le laisse continuer après avoir enregistré les modifications, ces modifications se reflètent dans l'exécution du code.
Voici la vraie question alors, existe-t-il un moyen de modifier le script A alors qu'il est toujours en cours d'exécution? Ou l'édition est-elle impossible une fois son exécution commencée?
la source
Réponses:
Sous Unix, la plupart des éditeurs travaillent en créant un nouveau fichier temporaire contenant le contenu modifié. Lorsque le fichier modifié est enregistré, le fichier d'origine est supprimé et le fichier temporaire renommé avec le nom d'origine. (Il existe, bien sûr, diverses protections pour empêcher la perte de données.) Il s'agit, par exemple, du style utilisé par
sed
ouperl
lorsqu'il est invoqué avec le-i
drapeau ("en place"), qui n'est pas vraiment "en place" du tout. Il aurait dû être appelé "nouvel endroit avec un ancien nom".Cela fonctionne bien car unix garantit (au moins pour les systèmes de fichiers locaux) qu'un fichier ouvert continue d'exister jusqu'à sa fermeture, même s'il est "supprimé" et qu'un nouveau fichier portant le même nom est créé. (Ce n'est pas une coïncidence si l'appel système Unix pour "supprimer" un fichier est en fait appelé "délier".) Donc, de manière générale, si un interpréteur de shell a un fichier source ouvert et que vous "éditez" le fichier de la manière décrite ci-dessus , le shell ne verra même pas les modifications car il a toujours le fichier d'origine ouvert.
[Remarque: comme pour tous les commentaires normalisés, ce qui précède est sujet à de multiples interprétations et il existe différents cas d'angle, tels que NFS. Les pédants sont invités à remplir les commentaires avec des exceptions.]
Il est bien sûr possible de modifier directement des fichiers; ce n'est tout simplement pas très pratique à des fins d'édition, car même si vous pouvez écraser des données dans un fichier, vous ne pouvez pas supprimer ou insérer sans déplacer toutes les données suivantes, ce qui impliquerait beaucoup de réécriture. De plus, pendant que vous effectuiez ce déplacement, le contenu du fichier serait imprévisible et les processus qui avaient ouvert le fichier en souffriraient. Pour vous en sortir (comme avec les systèmes de base de données, par exemple), vous avez besoin d'un ensemble sophistiqué de protocoles de modification et de verrous distribués; des choses qui sont bien au-delà de la portée d'un utilitaire d'édition de fichiers typique.
Donc, si vous souhaitez éditer un fichier en cours de traitement par un shell, vous avez deux options:
Vous pouvez ajouter au fichier. Cela devrait toujours fonctionner.
Vous pouvez remplacer le fichier par un nouveau contenu exactement de la même longueur . Cela peut ou non fonctionner, selon que le shell a déjà lu cette partie du fichier ou non. Étant donné que la plupart des E / S de fichiers impliquent des tampons de lecture et que tous les shells que je connais lisent une commande composée entière avant de l'exécuter, il est peu probable que vous puissiez vous en sortir. Ce ne serait certainement pas fiable.
Je ne connais aucun libellé dans la norme Posix qui nécessite en fait la possibilité de l'ajouter à un fichier de script pendant que le fichier est en cours d'exécution, donc cela pourrait ne pas fonctionner avec chaque shell compatible Posix, et encore moins avec l'offre actuelle de presque- et des coques parfois compatibles posix. Alors YMMV. Mais pour autant que je sache, cela fonctionne de manière fiable avec bash.
Pour preuve, voici une implémentation "sans boucle" du fameux programme de 99 bouteilles de bière en bash, qui utilise
dd
pour écraser et ajouter (l'écrasement est probablement sûr car il remplace la ligne en cours d'exécution, qui est toujours la dernière ligne de la fichier, avec un commentaire exactement de la même longueur; je l'ai fait pour que le résultat final puisse être exécuté sans le comportement d'auto-modification.)la source
export beer=100
avant d'exécuter le script, cela fonctionne comme prévu.bash
va un long chemin pour s'assurer qu'il lit les commandes juste avant de les exécuter.Par exemple dans:
Le shell lira le script par blocs, il est donc probable qu'il lise les deux commandes, interprète la première, puis revienne à la fin du
cmd1
script et relise le script pour le lirecmd2
et l'exécuter.Vous pouvez facilement le vérifier:
(bien qu'en regardant la
strace
sortie à ce sujet, il semble que cela fasse des choses plus fantaisistes (comme lire les données plusieurs fois, chercher en arrière ...) que lorsque j'ai essayé la même chose il y a quelques années, donc ma déclaration ci-dessus à propos de la recherche de retour peut ne s'applique plus aux versions plus récentes).Si toutefois vous écrivez votre script comme:
Le shell devra lire jusqu'à la fermeture
}
, le stocker en mémoire et l'exécuter. À cause deexit
, le shell ne lira plus du script afin que vous puissiez le modifier en toute sécurité pendant que le shell l'interprète.Sinon, lors de la modification du script, assurez-vous d'écrire une nouvelle copie du script. Le shell continuera de lire l'original (même s'il est supprimé ou renommé).
Pour ce faire, renommez-
the-script
lethe-script.old
, copiez-the-script.old
lethe-script
et modifiez-le.la source
Il n'y a vraiment aucun moyen sûr de modifier le script pendant son exécution car le shell peut utiliser la mise en mémoire tampon pour lire le fichier. De plus, si le script est modifié en le remplaçant par un nouveau fichier, les shells ne liront généralement le nouveau fichier qu'après avoir effectué certaines opérations.
Souvent, lorsqu'un script est modifié pendant son exécution, le shell finit par signaler des erreurs de syntaxe. Cela est dû au fait que, lorsque le shell ferme et rouvre le fichier de script, il utilise le décalage d'octet dans le fichier pour se repositionner au retour.
la source
Vous pouvez contourner ce problème en définissant un piège sur votre script, puis en utilisant
exec
pour récupérer le nouveau contenu du script. Notez cependant que l'exec
appel démarre le script à partir de zéro et non à partir de l'endroit où il a atteint dans le processus en cours, et donc le script B sera appelé (et ainsi de suite).Cela continuera à afficher la date à l'écran. Je pourrais ensuite modifier mon script et passer
date
àecho "Date: $(date)"
. En écrivant cela, le script en cours d'exécution affiche toujours la date. Cependant, si j'envoie le signal que j'ai définitrap
pour capturer, le scriptexec
(remplace le processus en cours d'exécution par la commande spécifiée) qui est la commande$CMD
et les arguments$@
. Vous pouvez le faire en émettantkill -1 PID
- où PID est le PID du script en cours d'exécution - et la sortie change pour s'afficherDate:
avant ladate
sortie de la commande.Vous pouvez stocker "l'état" de votre script dans un fichier externe (par exemple / tmp), et lire le contenu pour savoir où "reprendre" lorsque le programme sera réexécuté. Vous pouvez ensuite ajouter une terminaison d'interruptions supplémentaire (SIGINT / SIGQUIT / SIGKILL / SIGTERM) pour effacer ce fichier tmp. Ainsi, lorsque vous redémarrerez après avoir interrompu le "Script A", il recommencera depuis le début. Une version avec état serait quelque chose comme:
la source
$0
et$@
au début du script et en utilisant ces variables à laexec
place.