Comment Linux gère-t-il les scripts shell?

22

Pour cette question, considérons un script shell bash, bien que cette question doive être applicable à tous les types de script shell.

Quand quelqu'un exécute un script shell, Linux charge-t-il tout le script à la fois (en mémoire peut-être) ou lit-il les commandes de script une par une (ligne par ligne)?

En d'autres termes, si j'exécute un script shell et le supprime avant la fin de l'exécution, l'exécution sera-t-elle terminée ou continuera-t-elle telle quelle?

Utilisateur enregistré
la source
3
Essayez-le. (Ça va continuer.)
devnull
1
@devnull il y a en fait une question intéressante ici. Certes, qu'il continue ou non est trivial à tester, mais il existe des différences entre les fichiers binaires (qui sont chargés en mémoire) et les scripts avec une ligne de shebang, ou les scripts sans ligne de shebang.
terdon
1
Vous pouvez être intéressé par cette réponse
terdon
23
Aux fins de votre intention réelle, de supprimer le script shell lors de son exécution, peu importe qu'il soit lu en une seule fois ou ligne par ligne. Sous Unix, un inode n'est pas réellement supprimé (même s'il n'y a aucun lien vers celui-ci à partir de n'importe quel répertoire) jusqu'à ce que le dernier fichier ouvert soit fermé. En d'autres termes, même si votre shell lit dans le script shell ligne par ligne pendant l'exécution, il est toujours sûr de le supprimer. La seule exception est si votre shell est du type qui ferme et rouvre le script shell à chaque fois, mais si c'est le cas, vous avez des problèmes (de sécurité) beaucoup plus importants.
Chris Jester-Young

Réponses:

33

Si vous utilisez, stracevous pouvez voir comment un script shell est exécuté lors de son exécution.

Exemple

Disons que j'ai ce script shell.

$ cat hello_ul.bash 
#!/bin/bash

echo "Hello Unix & Linux!"

L'exécuter en utilisant strace:

$ strace -s 2000 -o strace.log ./hello_ul.bash
Hello Unix & Linux!
$

Un coup d'œil à l'intérieur du strace.logfichier révèle ce qui suit.

...
open("./hello_ul.bash", O_RDONLY)       = 3
ioctl(3, SNDCTL_TMR_TIMEBASE or SNDRV_TIMER_IOCTL_NEXT_DEVICE or TCGETS, 0x7fff0b6e3330) = -1 ENOTTY (Inappropriate ioctl for device)
lseek(3, 0, SEEK_CUR)                   = 0
read(3, "#!/bin/bash\n\necho \"Hello Unix & Linux!\"\n", 80) = 40
lseek(3, 0, SEEK_SET)                   = 0
getrlimit(RLIMIT_NOFILE, {rlim_cur=1024, rlim_max=4*1024}) = 0
fcntl(255, F_GETFD)                     = -1 EBADF (Bad file descriptor)
dup2(3, 255)                            = 255
close(3)     
...

Une fois le fichier lu, il est alors exécuté:

...
read(255, "#!/bin/bash\n\necho \"Hello Unix & Linux!\"\n", 40) = 40
rt_sigprocmask(SIG_BLOCK, NULL, [], 8)  = 0
rt_sigprocmask(SIG_BLOCK, NULL, [], 8)  = 0
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 3), ...}) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fc0b38ba000
write(1, "Hello Unix & Linux!\n", 20)   = 20
rt_sigprocmask(SIG_BLOCK, NULL, [], 8)  = 0
read(255, "", 40)                       = 0
exit_group(0)                           = ?

Dans ce qui précède, nous pouvons clairement voir que l'intégralité du script semble être lu comme une seule entité, puis exécuté par la suite. Il semblerait donc "apparaître" au moins dans le cas de Bash qu'il lit le fichier, puis l'exécute. Vous pensez donc que vous pouvez modifier le script pendant son exécution?

REMARQUE: non! Lisez la suite pour comprendre pourquoi vous ne devriez pas jouer avec un fichier de script en cours d'exécution.

Et les autres interprètes?

Mais votre question est légèrement décalée. Ce n'est pas Linux qui charge nécessairement le contenu du fichier, c'est l'interpréteur qui charge le contenu, donc c'est vraiment à la façon dont l'interprète l'implémente, qu'il charge le fichier entièrement ou en blocs ou en lignes à la fois.

Alors pourquoi ne pouvons-nous pas modifier le fichier?

Si vous utilisez un script beaucoup plus volumineux, vous remarquerez cependant que le test ci-dessus est un peu trompeur. En fait, la plupart des interprètes chargent leurs fichiers en blocs. C'est assez standard avec de nombreux outils Unix où ils chargent des blocs d'un fichier, le traitent, puis chargent un autre bloc. Vous pouvez voir ce comportement avec cette Q&R U&L que j'ai rédigée il y a quelque temps concernant grep: Combien de texte grep / egrep consomme-t-il à chaque fois? .

Exemple

Disons que nous faisons le script shell suivant.

$ ( 
    echo '#!/bin/bash'; 
    for i in {1..100000}; do printf "%s\n" "echo \"$i\""; done 
  ) > ascript.bash;
$ chmod +x ascript.bash

Résultat dans ce fichier:

$ ll ascript.bash 
-rwxrwxr-x. 1 saml saml 1288907 Mar 23 18:59 ascript.bash

Qui contient le type de contenu suivant:

$ head -3 ascript.bash ; echo "..."; tail -3 ascript.bash 
#!/bin/bash
echo "1"
echo "2"
...
echo "99998"
echo "99999"
echo "100000"

Maintenant, lorsque vous exécutez cela en utilisant la même technique ci-dessus avec strace:

$ strace -s 2000 -o strace_ascript.log ./ascript.bash
...    
read(255, "#!/bin/bash\necho \"1\"\necho \"2\"\necho \"3\"\necho \"4\"\necho \"5\"\necho \"6\"\necho \"7\"\necho \"8\"\necho \"9\"\necho \"10\"\necho 
...
...
\"181\"\necho \"182\"\necho \"183\"\necho \"184\"\necho \"185\"\necho \"186\"\necho \"187\"\necho \"188\"\necho \"189\"\necho \"190\"\necho \""..., 8192) = 8192

Vous remarquerez que le fichier est lu par incréments de 8 Ko, donc Bash et les autres shells ne chargeront probablement pas un fichier dans son intégralité, ils les lisent plutôt par blocs.

Les références

slm
la source
@terdon - oui, je me souviens avoir déjà vu cette Q&R.
slm
5
Avec un script de 40 octets, bien sûr, il est lu dans un bloc. Essayez avec un script> 8 Ko.
Gilles 'SO- arrête d'être méchant'
Je n'ai jamais essayé, mais je pense que la suppression de fichiers n'est pas effectuée avant que tous les processus ne ferment le descripteur de fichier associé au fichier supprimé, donc bash pourrait continuer à lire à partir du fichier supprimé.
Farid Nouri Neshat
@ Gilles - oui j'ai ajouté un exemple, j'y arrivais.
slm
2
Ce comportement dépend de la version. J'ai testé avec bash version 3.2.51 (1) -release, et j'ai constaté qu'il ne tamponnait pas au-delà de la ligne actuelle (voir cette réponse stackoverflow ).
Gordon Davisson
11

Cela dépend plus du shell que du système d'exploitation.

Selon la version, kshlisez le script à la demande par bloc de 8k ou 64k octets.

bashlire le script ligne par ligne. Cependant, étant donné que les lignes de faits peuvent être de longueur arbitraire, il lit à chaque fois 8176 octets depuis le début de la ligne suivante à analyser.

C'est pour les constructions simples, c'est-à-dire une suite de commandes simples.

Si les commandes structurées du shell sont utilisées ( un cas auquel la réponse acceptée ne tient pas compte ) comme une for/do/doneboucle, un case/esaccommutateur, un document ici, un sous-shell entouré de parenthèses, une définition de fonction, etc. et toute combinaison des éléments ci-dessus, les interpréteurs du shell lisent à la fin de la construction pour s'assurer d'abord qu'il n'y a pas d'erreur de syntaxe.

Ceci est quelque peu inefficace car le même code peut être lu maintes et maintes fois un grand nombre de fois, mais atténué par le fait que ce contenu est normalement mis en cache.

Quel que soit l'interpréteur de shell, il est très imprudent de modifier un script de shell pendant son exécution car le shell est libre de relire n'importe quelle partie du script, ce qui peut entraîner des erreurs de syntaxe inattendues s'il n'est pas synchronisé.

Notez également que bash peut se bloquer avec une violation de segmentation lorsqu'il est incapable de stocker une construction de script trop volumineuse que ksh93 peut lire sans problème.

jlliagre
la source
7

Cela dépend du fonctionnement de l'interpréteur exécutant le script. Tout ce que fait le noyau, c'est de remarquer que le fichier à exécuter commence par #!, exécute essentiellement le reste de la ligne en tant que programme et lui donne l'exécutable en argument. Si l'interpréteur qui y est répertorié lit ce fichier ligne par ligne (comme le font les shells interactifs avec ce que vous tapez), c'est ce que vous obtenez (mais les structures de boucles multi-lignes sont lues et conservées pour être répétées); si l'interpréteur glisse le fichier dans la mémoire, le traite (peut-être le compile en une représentation intermédiaire, comme Perl et Pyton), le fichier est lu en entier avant de s'exécuter.

Si vous supprimez le fichier entre-temps, le fichier n'est pas supprimé jusqu'à ce que l'interprète le ferme (comme toujours, les fichiers disparaissent lorsque la dernière référence, que ce soit une entrée de répertoire ou un processus le gardant ouvert) disparaît.

vonbrand
la source
4

Le fichier 'x':

cat<<'dog' >xyzzy
LANG=C
T=`tty`
( sleep 2 ; ls -l xyzzy >$T ) &
( sleep 4 ; rm -v xyzzy >$T ) &
( sleep 4 ; ls -l xyzzy >$T ) &
echo alive. ; sleep 1
echo alive. ; sleep 1
echo alive. ; sleep 1
echo alive. ; sleep 1
echo alive. ; sleep 1
echo alive. ; sleep 1
echo alive. ; sleep 1
echo alive. ; sleep 1
dog

sh xyzzy

La course:

~/wrk/tmp$ sh x
alive.
alive.
alive.
-rw-r--r-- 1 yeti yeti 287 Mar 23 16:57 xyzzy
alive.
removed `xyzzy'
ls: cannot access xyzzy: No such file or directory
alive.
alive.
alive.
alive.
~/wrk/tmp$ _

IIRC un fichier n'est pas supprimé tant qu'un processus le maintient ouvert. La suppression supprime simplement le DIRENT donné.


la source