Quelles sont les différences entre l'exécution de scripts shell à l'aide de "source file.sh", "./file.sh", "sh file.sh", ". ./fichier.sh "?

13

Jetez un œil au code:

#!/bin/bash
read -p "Eneter 1 for UID and 2 for LOGNAME" choice
if [ $choice -eq 1 ]
then
        read -p "Enter UID:  " uid
        logname=`cat /etc/passwd | grep $uid | cut -f1 -d:`
else
        read -p "Enter Logname:  " logname
fi
not=`ps -au$logname | grep -c bash`
echo  "The number of terminals opened by $logname are $not"

Ce code permet de connaître le nombre de terminaux ouverts par un utilisateur sur le même PC. Maintenant, deux utilisateurs sont connectés, disons x et y. Je suis actuellement connecté en tant que y et il y a 3 terminaux ouverts dans l'utilisateur x. Si j'exécute ce code en y de différentes manières, comme mentionné ci-dessus, les résultats sont les suivants:

$ ./file.sh
The number of terminals opened by x are 3

$ bash file.sh
The number of terminals opened by x are 5

$ sh file.sh
The number of terminals opened by x are 3

$ source file.sh
The number of terminals opened by x are 4

$ . ./file.sh
The number of terminals opened by x are 4

Remarque: j'ai passé 1 et uid 1000 à tous ces exécutables.

Pouvez-vous maintenant expliquer les différences entre tous ces éléments?

Ramana Reddy
la source
la différence est quel shell est exécuté. sh n'est pas bash
j0h
2
Les deux dernières exécutions sont également différentes car vous exécutez dans le même contexte. Plus ici
Zaka Elab
J'essaie de compter le nombre d'instances bash (ici, cela équivaut à un nombre de terminaux) ouvertes par l'autre utilisateur (pas le même utilisateur où nous nous sommes connectés) et pourriez-vous expliquer pourquoi un nombre différent est venu dans chaque cas
Ramana Reddy
@RamanaReddy, l'autre utilisateur peut avoir exécuté un script ou ouvert un nouvel onglet. Qui sait?
muru

Réponses:

21

La seule différence majeure est entre l'approvisionnement et l'exécution d'un script. source foo.shle source et tous les autres exemples que vous montrez sont en cours d'exécution. Plus en détail:

  1. ./file.sh

    Cela exécutera un script appelé file.shqui se trouve dans le répertoire courant ( ./). Normalement, lorsque vous exécutez command, le shell recherchera dans les répertoires de votre $PATHfichier exécutable appelé command. Si vous donnez un chemin complet, tel que /usr/bin/commandou ./command, alors le $PATHest ignoré et ce fichier spécifique est exécuté.

  2. ../file.sh

    C'est fondamentalement la même chose que ./file.shsauf qu'au lieu de chercher dans le répertoire courant file.sh, il cherche dans le répertoire parent ( ../).

  3. sh file.sh

    Cet équivalent à sh ./file.sh, comme ci-dessus, exécutera le script appelé file.shdans le répertoire courant. La différence est que vous l'exécutez explicitement avec le shshell. Sur les systèmes Ubuntu, c'est le cas dashet non bash. Habituellement, les scripts ont une ligne shebang qui donne le programme sous lequel ils doivent être exécutés. Les appeler avec un autre l'emporte. Par exemple:

    $ cat foo.sh
    #!/bin/bash  
    ## The above is the shebang line, it points to bash
    ps h -p $$ -o args='' | cut -f1 -d' '  ## This will print the name of the shell

    Ce script affichera simplement le nom du shell utilisé pour l'exécuter. Voyons ce qu'il retourne lorsqu'il est appelé de différentes manières:

    $ bash foo.sh
    bash
    $ sh foo.sh 
    sh
    $ zsh foo.sh
    zsh

    Donc, appeler appeler un script avec shell scriptremplacera la ligne shebang (si elle est présente) et exécutera le script avec le shell que vous lui direz.

  4. source file.sh ou . file.sh

    Cela s'appelle, de façon assez surprenante, l' approvisionnement du script. Le mot source- clé est un alias de la .commande intégrée du shell . C'est une façon d'exécuter le script dans le shell courant. Normalement, lorsqu'un script est exécuté, il est exécuté dans son propre shell, différent de celui en cours. Pour illustrer:

    $ cat foo.sh
    #!/bin/bash
    foo="Script"
    echo "Foo (script) is $foo"

    Maintenant, si je définis la variable foosur quelque chose d'autre dans le shell parent et que j'exécute ensuite le script, le script affichera une valeur différente de foo(car elle est également définie dans le script) mais la valeur de foodans le shell parent sera inchangée:

    $ foo="Parent"
    $ bash foo.sh 
    Foo (script) is Script  ## This is the value from the script's shell
    $ echo "$foo"          
    Parent                  ## The value in the parent shell is unchanged

    Cependant, si je source le script au lieu de l'exécuter, il sera exécuté dans le même shell donc la valeur de foodans le parent sera modifiée:

    $ source ./foo.sh 
    Foo (script) is Script   ## The script's foo
    $ echo "$foo" 
    Script                   ## Because the script was sourced, 
                             ## the value in the parent shell has changed

    Ainsi, le sourcing est utilisé dans les rares cas où vous souhaitez qu'un script affecte le shell à partir duquel vous l'exécutez. Il est généralement utilisé pour définir des variables shell et les rendre disponibles une fois le script terminé.


Avec tout cela à l'esprit, la raison pour laquelle vous obtenez des réponses différentes est, tout d'abord, que votre script ne fait pas ce que vous pensez qu'il fait. Il compte le nombre de fois qui bashapparaît dans la sortie de ps. Ce n'est pas le nombre de terminaux ouverts , c'est le nombre de shells en cours d' exécution (en fait, ce n'est même pas ça, mais c'est une autre discussion). Pour clarifier, j'ai un peu simplifié votre script:

#!/bin/bash
logname=terdon
not=`ps -au$logname | grep -c bash`
echo  "The number of shells opened by $logname is $not"

Et exécutez-le de différentes manières avec un seul terminal ouvert:

  1. Lancement direct, ./foo.sh.

    $ ./foo.sh
    The number of shells opened by terdon is 1

    Ici, vous utilisez la ligne shebang. Cela signifie que le script est exécuté directement par tout ce qui y est défini. Cela affecte la façon dont le script est affiché dans la sortie de ps. Au lieu d'être répertorié comme bash foo.sh, il ne sera affiché que foo.shce qui signifie que vous greple manquerez. Il y a en fait 3 instances bash en cours d'exécution: le processus parent, le bash exécutant le script et un autre qui exécute la pscommande . Ce dernier est important, le lancement d'une commande avec substitution de commande ( `command`ou $(command)) entraîne le lancement d'une copie du shell parent et l'exécution de la commande. Ici, cependant, aucun de ces éléments n'est affiché en raison de la façon dont il psaffiche sa sortie.

  2. Lancement direct avec shell explicite (bash)

    $ bash foo.sh 
    The number of shells opened by terdon is 3

    Ici, parce que vous utilisez avec bash foo.sh, la sortie de pssera affichée bash foo.shet sera comptée. Donc, ici, nous avons le processus parent, l' bashexécution du script et le shell cloné (exécution de ps) tous affichés parce que maintenant psaffichera chacun d'eux parce que votre commande inclura le mot bash.

  3. Lancement direct avec un autre shell ( sh)

    $ sh foo.sh
    The number of shells opened by terdon is 1

    C'est différent parce que vous exécutez le script avec shet non bash. Par conséquent, la seule bashinstance est le shell parent où vous avez lancé votre script. Tous les autres obus mentionnés ci-dessus sont gérés par à la shplace.

  4. Sourcing (soit par .ou source, même chose)

    $ . ./foo.sh 
    The number of shells opened by terdon is 2

    Comme je l'ai expliqué ci-dessus, l'approvisionnement d'un script le fait s'exécuter dans le même shell que le processus parent. Cependant, un sous-shell séparé est démarré pour lancer la pscommande et cela porte le total à deux.


Enfin, la bonne façon de compter les processus en cours n'est pas d'analyser psmais d'utiliser pgrep. Tous ces problèmes auraient été évités si vous veniez de courir

pgrep -cu terdon bash

Ainsi, une version de travail de votre script qui imprime toujours le bon numéro est (notez l'absence de substitution de commande):

#!/usr/bin/env bash
user="terdon"

printf "Open shells:"
pgrep -cu "$user" bash

Cela renverra 1 lors de la source et 2 (car un nouveau bash sera lancé pour exécuter le script) pour toutes les autres façons de lancer. Il renverra toujours 1 lors du lancement avec shpuisque le processus enfant ne l'est pas bash.

terdon
la source
Lorsque vous dites que la substitution de commandes lance une copie du shell parent, en quoi cette copie diffère-t-elle d'un sous-shell comme lorsque vous exécutez le script avec ./foo.sh?
Didier A.
Et lorsque vous exécutez pgrep sans substitution de commande, je suppose qu'il s'exécute à partir du même shell que le script s'exécute? Si similaire à l'approvisionnement?
Didier A.
@didibus Je ne sais pas ce que tu veux dire. La substitution de commandes s'exécute dans un sous-shell; ./foo.shs'exécute dans un nouveau shell qui n'est pas une copie du parent. Par exemple, si vous définissez foo="bar"dans votre terminal puis exécutez un script qui s'exécute echo $foo, vous obtiendrez une ligne vide car le shell du script n'aura pas hérité de la valeur de la variable. pgrepest un binaire distinct, et oui, il est exécuté par le script que vous exécutez.
terdon
Fondamentalement, j'ai besoin d'éclaircissements sur: "notez l'absence de substitution de commande". Pourquoi l'exécution du binaire pgrep à partir d'un script n'ajoute-t-elle pas un shell supplémentaire, mais l'exécution du binaire ps avec la substitution de commandes le fait? Deuxièmement, j'ai besoin d'éclaircissements sur la "copie du shell parent", est-ce comme un sous-shell où les variables shell du parent sont copiées sur l'enfant? Pourquoi la substitution de commandes fait-elle cela?
Didier A.