Frage Was sind die Unterschiede zwischen dem Ausführen von Shell-Skripten mit "source file.sh", "./file.sh", "sh file.sh", ". ./file.sh "?


Schau dir den Code an:

#!/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"

Dieser Code wird verwendet, um die Anzahl der Terminals zu ermitteln, die von einem Benutzer auf demselben PC geöffnet wurden. Jetzt sind zwei Benutzer angemeldet, sagen wir x und y. Ich bin derzeit als y angemeldet und es sind 3 Terminals in Benutzer x geöffnet. Wenn ich diesen Code in y auf verschiedene Arten wie oben erwähnt ausführen, sind die Ergebnisse:

$ ./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

Hinweis: Ich habe 1 und 1000 an alle diese ausführbaren Dateien übergeben.

Können Sie jetzt bitte die Unterschiede zwischen all diesen erklären?


11
2018-03-25 10:59


Ursprung


Der Unterschied ist, welche Shell ausgeführt wird. Sh ist nicht Bash - j0h
Die beiden letzten Ausführungen unterscheiden sich auch, weil Sie im selben Kontext ausgeführt werden. Mehr Hier - Zaka Elab
Ich versuche die Anzahl der Bash-Instanzen (hier ist es gleich Anzahl der Terminals) zu zählen, die vom anderen Benutzer geöffnet wurden (nicht vom selben Benutzer, in dem wir uns angemeldet haben) und könntest du erklären, warum in jedem Fall eine andere Nummer gekommen ist - Ramana Reddy
@RamanaReddy der andere Benutzer möglicherweise ein Skript ausgeführt oder eine neue Registerkarte gestartet. Wer weiß? - muru


Antworten:


Der einzige große Unterschied besteht zwischen der Beschaffung und der Ausführung eines Skripts. source foo.sh wird es liefern und alle anderen Beispiele, die du zeigst, werden ausgeführt. Ausführlicher:

  1. ./file.sh

    Dies führt ein Skript namens file.sh das ist im aktuellen Verzeichnis (./). Normalerweise, wenn du rennst command, wird die Shell durch die Verzeichnisse in Ihrem suchen $PATH für eine ausführbare Datei namens command. Wenn Sie einen vollständigen Pfad angeben, z /usr/bin/command oder ./command, dann ist die $PATH wird ignoriert und diese spezifische Datei wird ausgeführt.

  2. ../file.sh

    Dies ist im Grunde das Gleiche wie ./file.sh außer dass man im aktuellen Verzeichnis nach sucht file.shsucht es im übergeordneten Verzeichnis (../).

  3. sh file.sh

    Dies entspricht sh ./file.sh, wie oben wird es das Skript aufgerufen file.sh im aktuellen Verzeichnis. Der Unterschied ist, dass Sie es explizit mit dem ausführen sh Schale. Auf Ubuntu-Systemen dash und nicht bash. Normalerweise haben Skripte ein Shebang-Linie das gibt das Programm, das sie ausführen sollen. Sie mit einem anderen zu benennen, überschreibt das. Beispielsweise:

    $ 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
    

    Dieses Skript druckt einfach den Namen der Shell aus, mit der es ausgeführt wurde. Mal sehen, was es zurückgibt, wenn es auf verschiedene Arten aufgerufen wird:

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

    Rufen Sie also ein Skript mit an shell script überschreibt die Shebang-Zeile (falls vorhanden) und führt das Skript mit der von Ihnen angegebenen Shell aus.

  4. source file.sh oder . file.sh

    Dies wird überraschend genannt, Beschaffung das Skript. Das Schlüsselwort source ist ein Alias ​​für die eingebaute Shell . Befehl. Dies ist eine Möglichkeit, das Skript innerhalb der aktuellen Shell auszuführen. Wenn ein Skript ausgeführt wird, wird es normalerweise in einer eigenen Shell ausgeführt, die sich von der aktuellen unterscheidet. Um zu veranschaulichen:

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

    Jetzt, wenn ich die Variable setze foo zu etwas anderem in der Parent-Shell und dann das Skript ausführen, wird das Skript einen anderen Wert von ausgeben foo (weil es auch innerhalb des Skripts gesetzt ist), aber der Wert von foo in der Parent-Shell bleibt unverändert:

    $ 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
    

    Wenn ich jedoch das Skript entwerfe, anstatt es auszuführen, wird es in der gleichen Shell ausgeführt, also der Wert von foo im Elternteil wird geändert:

    $ 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
    

    Daher wird das Sourcing in den wenigen Fällen verwendet, in denen ein Skript die Shell beeinflussen soll, von der es ausgeführt wird. Es wird normalerweise verwendet, um Shell-Variablen zu definieren und nach dem Skript verfügbar zu haben.


In Anbetracht dessen ist der Grund, warum Sie unterschiedliche Antworten erhalten, vor allem, dass Ihr Skript nicht das tut, was Sie denken. Es zählt die Anzahl der Male basherscheint in der Ausgabe von ps. Dies ist nicht die Anzahl der offenen Terminalses ist die Zahl von Laufende Muscheln (Tatsächlich ist es nicht einmal das, aber das ist eine andere Diskussion). Zur Verdeutlichung habe ich dein Skript ein wenig darauf vereinfacht:

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

Und führen Sie es auf verschiedene Arten mit nur einem geöffneten Terminal aus:

  1. Direktstart, ./foo.sh.

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

    Hier benutzt du die Shebang-Linie. Dies bedeutet, dass das Skript direkt von dem ausgeführt wird, was dort eingestellt ist. Dies wirkt sich auf die Art und Weise aus, in der das Skript in der Ausgabe von angezeigt wird ps. Anstatt wie aufgelistet zu sein bash foo.shEs wird nur als angezeigt foo.sh was bedeutet, dass dein grep werde es vermissen. Es laufen tatsächlich 3 Bash-Instanzen: der Elternprozess, die Bash, die das Skript ausführt und ein anderer, der die ps Befehl. Letzteres ist wichtig, um einen Befehl mit Befehlsersetzung zu starten (`command` oder $(command)) führt zu einer Kopie der übergeordneten Shell, die gestartet wird und die den Befehl ausführt. Hier wird jedoch keiner von ihnen wegen der Art gezeigt, dass ps zeigt seine Ausgabe an.

  2. Direkter Start mit expliziter (bash) Shell

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

    Hier, weil du mit rennst bash foo.sh, die Ausgabe von ps wird zeigen bash foo.sh und gezählt werden. Also, hier haben wir den Elternprozess, den bash Ausführen des Skripts und die geklonte Schale (läuft die ps) alle gezeigt, weil jetzt ps wird jeden von ihnen zeigen, weil Ihr Befehl das Wort enthält bash.

  3. Direktstart mit einer anderen Shell (sh)

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

    Dies ist anders, weil Sie das Skript mit ausführen sh und nicht bash. Daher das einzige bash instance ist die Parent-Shell, in der Sie Ihr Skript gestartet haben. Alle anderen oben genannten Shells werden ausgeführt sh stattdessen.

  4. Beschaffung (entweder durch . oder source, gleiche Sache)

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

    Wie ich oben erklärt habe, führt das Sourcing eines Skripts dazu, dass es in derselben Shell wie der Elternprozess ausgeführt wird. Es wird jedoch eine separate Subshell gestartet, um das Programm zu starten ps Befehl und das bringt die Summe auf zwei.


Als letzte Anmerkung ist das korrekte Zählen laufender Prozesse nicht zu analysieren ps aber zu benutzen pgrep. All diese Probleme wären vermieden worden, wenn Sie nur gelaufen wären

pgrep -cu terdon bash

Eine funktionierende Version Ihres Skripts, die immer die richtige Zahl ausgibt, ist (beachten Sie das Fehlen der Befehlsersetzung):

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

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

Das wird 1 zurückgeben, wenn es aussortiert wird, und 2 (weil ein neuer Bash gestartet wird, um das Skript auszuführen) für alle anderen Arten des Starts. Es wird immer noch 1 zurückgeben, wenn es gestartet wird sh da der Kindprozess nicht ist bash.


16
2018-03-25 13:41



Gut erklärt, vielen Dank - Ramana Reddy
Wenn Sie sagen, dass die Befehlsersetzung eine Kopie der übergeordneten Shell startet, wie unterscheidet sich diese Kopie von einer Subshell, wie wenn Sie das Skript mit ./foo.sh ausführen? - Didier A.
Und wenn Sie pgrep ohne Befehlsersetzung ausführen, nehme ich an, dass es in derselben Shell ausgeführt wird, in der das Skript ausgeführt wird? So ähnlich wie Sourcing? - Didier A.
@didibus Ich bin mir nicht sicher, was du meinst. Befehlsersetzung wird in einer Untershell ausgeführt; ./foo.sh Wird in einer neuen Shell ausgeführt, die keine Kopie des übergeordneten Elements ist. Zum Beispiel, wenn Sie festlegen foo="bar" in Ihrem Terminal und führen Sie dann ein Skript aus, das ausgeführt wird echo $foo, erhalten Sie eine leere Zeile, da die Shell des Skripts den Wert der Variablen nicht übernommen hat. pgrep ist eine separate Binärdatei, und ja, es wird von dem Skript ausgeführt, das Sie ausführen. - terdon♦
Grundsätzlich brauche ich eine Klarstellung zu: "notiere die Abwesenheit der Befehlsersetzung". Warum fügt das Ausführen der Pgrep-Binärdatei aus einem Skript keine zusätzliche Shell hinzu, aber das Ausführen der Ps-Binärdatei mit Befehlsersetzung funktioniert? Zweitens brauche ich eine Klärung über "Kopie der Parent-Shell", ist das wie eine Sub-Shell, wo die Shell-Variablen des Elternteils auf das Kind kopiert werden? Warum macht die Befehlsersetzung das? - Didier A.