Was sind die Unterschiede zwischen der Ausführung von Shell-Skripten mit "source file.sh", "./file.sh", "sh file.sh", ". ./file.sh ”?

13

Schauen Sie sich 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"

Mit diesem Code wird die Anzahl der von einem Benutzer auf demselben PC geöffneten Terminals ermittelt. Jetzt sind zwei Benutzer angemeldet, nämlich x und y. Ich bin momentan als y angemeldet und in Benutzer x sind 3 Terminals geöffnet. Wenn ich diesen Code auf verschiedene Arten wie oben beschrieben ausführe, 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 UID 1000 an alle diese ausführbaren Dateien übergeben.

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

Ramana Reddy
quelle
Der Unterschied ist, welche Shell ausgeführt wird. sh is not bash
j0h
2
Die beiden letzten Ausführungen sind ebenfalls unterschiedlich, da Sie im selben Kontext ausführen. Mehr hier
Zaka Elab
Ich versuche, die Anzahl der Bash-Instanzen zu zählen (hier entspricht dies der Anzahl der Terminals), die von dem anderen Benutzer (nicht demselben Benutzer, bei dem wir uns angemeldet haben) geöffnet wurden. Können Sie erklären, warum jeweils eine andere Nummer
eingegeben wurde
@RamanaReddy Der andere Benutzer hat möglicherweise ein Skript ausgeführt oder eine neue Registerkarte gestartet. Wer weiß?
muru

Antworten:

21

Der einzige wesentliche Unterschied besteht in der Beschaffung und Ausführung eines Skripts. source foo.shEs wird als Quelle verwendet, und alle anderen von Ihnen gezeigten Beispiele werden ausgeführt. Ausführlicher:

  1. ./file.sh

    Dadurch wird ein Skript ausgeführt, file.shdas im aktuellen Verzeichnis ( ./) gespeichert ist . Normalerweise commandsucht die Shell beim Ausführen in den Verzeichnissen $PATHnach einer ausführbaren Datei mit dem Namen command. Wenn Sie einen vollständigen Pfad geben, wie /usr/bin/commandoder ./command, dann die $PATHignoriert wird und dass bestimmte Datei ausgeführt wird .

  2. ../file.sh

    Dies ist im Grunde das Gleiche wie, mit der ./file.shAusnahme, dass statt im aktuellen Verzeichnis nach file.shgesucht wird, im übergeordneten Verzeichnis gesucht wird ( ../).

  3. sh file.sh

    Dies entspricht sh ./file.sh, wie oben, der Ausführung des file.shim aktuellen Verzeichnis aufgerufenen Skripts . Der Unterschied besteht darin, dass Sie es explizit mit der shShell ausführen. Auf Ubuntu-Systemen ist das dashund nicht bash. Normalerweise haben Skripte eine Shebang-Zeile , die das Programm angibt , unter dem sie ausgeführt werden sollen. Wenn Sie sie mit einem anderen anrufen, wird dies überschrieben. 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 gibt 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

    shell scriptWenn Sie also ein Skript mit aufrufen, wird die shebang-Zeile (falls vorhanden) überschrieben und das Skript mit der von Ihnen angegebenen Shell ausgeführt.

  4. source file.sh oder . file.sh

    Dies wird überraschenderweise als Beschaffung des Skripts bezeichnet. Das Schlüsselwort sourceist ein Alias ​​für den Shell- .Befehl builtin . Auf diese Weise können Sie das Skript in der aktuellen Shell ausfü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"

    Wenn ich nun die Variable fooauf einen anderen Wert in der übergeordneten Shell setze und dann das Skript ausführe, gibt das Skript einen anderen Wert aus foo(da er auch im Skript festgelegt ist), aber der Wert fooin der übergeordneten 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 das Skript jedoch als Quelle verwende, anstatt es auszuführen, wird es in derselben Shell ausgeführt, sodass der Wert fooim übergeordneten Element geändert wird:

    $ 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 Sourcing in den wenigen Fällen verwendet, in denen ein Skript die Shell beeinflussen soll, von der aus Sie es ausführen. Es wird normalerweise verwendet, um Shell-Variablen zu definieren und nach Abschluss des Skripts verfügbar zu machen.


Der Grund, warum Sie unterschiedliche Antworten erhalten, ist zuallererst, dass Ihr Skript nicht das tut, was Sie denken, dass es es tut. Es zählt, wie oft bashin der Ausgabe von angezeigt wird ps. Dies ist nicht die Anzahl der offenen Terminals , sondern die Anzahl der laufenden Shells (tatsächlich ist es nicht einmal das, aber das ist eine andere Diskussion). Zur Verdeutlichung habe ich Ihr Skript ein wenig vereinfacht:

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

Führen Sie es auf verschiedene Arten aus, wobei nur ein einziges Terminal geöffnet ist:

  1. Direkter Start, ./foo.sh.

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

    Hier verwenden Sie die Shebang-Linie. Dies bedeutet, dass das Skript direkt von dem ausgeführt wird, was dort eingestellt ist. Dies wirkt sich darauf aus, wie das Skript in der Ausgabe von angezeigt wird ps. Anstatt als aufgeführt zu werden bash foo.sh, wird nur angezeigt, foo.shwas bedeutet, dass Sie grepes verpassen werden. Es werden tatsächlich 3 Bash-Instanzen ausgeführt: der übergeordnete Prozess, die Bash, die das Skript ausführt, und eine andere, die den psBefehl ausführt . Letzteres ist wichtig, da beim Starten eines Befehls mit Befehlsersetzung ( `command`oder $(command)) eine Kopie der übergeordneten Shell gestartet wird, die den Befehl ausführt. Hier wird jedoch aufgrund der Art und Weise, in der psdie Ausgabe erfolgt , keine davon angezeigt .

  2. Direktes Starten mit expliziter (bash) Shell

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

    Hier wird bash foo.shdie Ausgabe von psangezeigt bash foo.shund gezählt , weil Sie mit ausgeführt werden. Hier haben wir also den übergeordneten Prozess, das bashAusführen des Skripts und die geklonte Shell (Ausführen der ps), die alle angezeigt werden, da jetzt psjeder von ihnen angezeigt wird, da Ihr Befehl das Wort enthält bash.

  3. Direktes Starten mit einer anderen Shell ( sh)

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

    Dies ist anders, da Sie das Skript mit shund nicht mit ausführen bash. Daher ist die einzige bashInstanz die übergeordnete Shell, in der Sie Ihr Skript gestartet haben. Alle anderen oben genannten Shells werden shstattdessen von ausgeführt.

  4. Beschaffung (entweder durch .oder sourcedasselbe)

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

    Wie ich oben erklärt habe, wird ein Skript bei der Beschaffung in derselben Shell wie der übergeordnete Prozess ausgeführt. Es wird jedoch eine separate Subshell gestartet, um den psBefehl zu starten. Dadurch wird die Summe auf zwei erhöht .


Als letzte Anmerkung ist die richtige Art, laufende Prozesse zu zählen, nicht zu analysieren, pssondern zu verwenden 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 Nummer ausgibt, ist (beachten Sie, dass keine Befehlsersetzung erfolgt):

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

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

Das gibt 1 zurück, wenn es bezogen wird, und 2 (da eine neue Bash gestartet wird, um das Skript auszuführen) für alle anderen Arten des Startens. Beim Starten mit wird immer noch 1 zurückgegeben, shda dies beim untergeordneten Prozess nicht der Fall ist bash.

terdon
quelle
Wenn Sie sagen, dass die Befehlsersetzung eine Kopie der übergeordneten Shell startet, wie unterscheidet sich diese Kopie von einer Sub-Shell, wenn Sie das Skript mit ./foo.sh ausführen?
Didier A.
Und wenn Sie pgrep ohne Befehlsersetzung ausführen, gehe ich davon aus, dass es in derselben Shell ausgeführt wird, in der das Skript ausgeführt wird? So ähnlich wie beim Sourcing?
Didier A.
@didibus Ich bin mir nicht sicher, was du meinst. Die Befehlsersetzung wird in einer Subshell ausgeführt. ./foo.shLäuft in einer neuen Shell, die keine Kopie des übergeordneten Elements ist. Wenn Sie beispielsweise foo="bar"in Ihrem Terminal festlegen, dass ein Skript ausgeführt wird echo $foo, wird eine leere Zeile angezeigt , da die Shell des Skripts den Wert der Variablen nicht geerbt hat. pgrepist eine separate Binärdatei und wird von dem Skript ausgeführt, das Sie ausführen.
Terdon
Grundsätzlich muss klargestellt werden: "Beachten Sie das Fehlen einer Befehlsersetzung". Warum fügt das Ausführen der pgrep-Binärdatei über ein Skript keine zusätzliche Shell hinzu, das Ausführen der ps-Binärdatei mit Befehlsersetzung jedoch? Zweitens brauche ich eine Erläuterung zu "Kopie der übergeordneten Shell". Ist das wie eine Unterschale, in die die Shell-Variablen der übergeordneten Shell auf die untergeordneten Shell kopiert werden? Warum macht die Befehlsersetzung das?
Didier A.