Jakie są różnice między wykonywaniem skryptów powłoki za pomocą „source file.sh”, „./file.sh”, „sh file.sh”, „. ./file.sh ”?

13

Spójrz na kod:

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

Ten kod służy do sprawdzania liczby terminali otwartych przez użytkownika na tym samym komputerze. Teraz jest zalogowanych dwóch użytkowników, powiedzmy xiy. Jestem aktualnie zalogowany jako y, a użytkownik x ma otwarte 3 terminale. Jeśli wykonam ten kod w y przy użyciu innych sposobów, jak wspomniano powyżej, wyniki są następujące:

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

Uwaga: przekazałem 1 i UID 1000 do wszystkich tych plików wykonywalnych.

Czy możesz teraz wyjaśnić różnice między nimi wszystkimi?

Ramana Reddy
źródło
różnica polega na tym, która powłoka jest wykonywana. sh nie jest bash
j0h
2
Dwa ostatnie wykonania są również różne, ponieważ wykonujesz je w tym samym kontekście. Więcej tutaj
Zaka Elab
Próbuję policzyć liczbę wystąpień bash (tutaj jest to liczba terminali) otwartych przez innego użytkownika (nie tego samego użytkownika, z którego się zalogowaliśmy) i czy możesz wyjaśnić, dlaczego w każdym przypadku pojawiła się inna liczba
Ramana Reddy
@RamanaReddy inny użytkownik mógł uruchomić skrypt lub uruchomić nową kartę. Kto wie?
muru

Odpowiedzi:

21

Jedyną istotną różnicą jest pozyskiwanie i wykonywanie skryptu. source foo.shźródło, a wszystkie inne przykłady, które pokazujesz, są wykonywane. Bardziej szczegółowo:

  1. ./file.sh

    Spowoduje to wykonanie skryptu o nazwie file.shznajdującej się w bieżącym katalogu ( ./). Zwykle po uruchomieniu commandpowłoka przeszukuje katalogi w $PATHposzukiwaniu pliku wykonywalnego o nazwie command. Jeśli podasz pełną ścieżkę, taką jak /usr/bin/commandlub ./command, wówczas $PATHjest ona ignorowana, a konkretny plik jest wykonywany.

  2. ../file.sh

    Jest to w zasadzie to samo, ./file.shz wyjątkiem tego, że zamiast szukać w bieżącym katalogu file.sh, szuka w katalogu nadrzędnym ( ../).

  3. sh file.sh

    Jest to odpowiednik sh ./file.sh, jak powyżej, uruchomi skrypt wywoływany file.shw bieżącym katalogu. Różnica polega na tym, że jawnie uruchamiasz go za pomocą shpowłoki. W systemach Ubuntu tak jest dashi nie bash. Zwykle skrypty mają linię shebang, która daje program, który powinny być uruchomione jako. Wywoływanie ich za pomocą innego przesłania to. Na przykład:

    $ 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

    Ten skrypt po prostu wypisze nazwę powłoki używanej do jej uruchomienia. Zobaczmy, co zwraca, gdy zostanie wywołany na różne sposoby:

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

    Wywołanie wywołania skryptu shell scriptzastąpi linię shebang (jeśli jest obecna) i uruchom skrypt z dowolną powłoką, którą mu powiesz.

  4. source file.sh lub . file.sh

    Zaskakująco nazywa się to pozyskiwaniem skryptu. Słowo kluczowe sourceto alias do wbudowanego .polecenia powłoki . Jest to sposób na wykonanie skryptu w bieżącej powłoce. Zwykle, gdy skrypt jest wykonywany, jest uruchamiany we własnej powłoce, która jest inna niż bieżąca. Ilustrować:

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

    Teraz, jeśli ustawię zmienną foona coś innego w powłoce nadrzędnej, a następnie uruchomię skrypt, skrypt wydrukuje inną wartość foo(ponieważ jest on również ustawiony w skrypcie), ale wartość foow powłoce nadrzędnej pozostanie niezmieniona:

    $ 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

    Jednakże, jeśli skrytuję skrypt zamiast go uruchamiać, zostanie on uruchomiony w tej samej powłoce, więc wartość fooparametru nadrzędnego zostanie zmieniona:

    $ 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

    Tak więc pozyskiwanie jest używane w kilku przypadkach, w których skrypt ma wpływać na powłokę, z której go uruchamiasz. Zwykle służy do definiowania zmiennych powłoki i udostępniania ich po zakończeniu skryptu.


Mając to na uwadze, powodem, dla którego otrzymujesz różne odpowiedzi, jest przede wszystkim to, że twój skrypt nie robi tego, co myślisz. Zlicza ile razy bashpojawia się na wyjściu ps. To nie jest liczba otwartych terminali , to liczba uruchomionych powłok (w rzeczywistości tak nie jest, ale to kolejna dyskusja). Aby to wyjaśnić, uprościłem nieco twój skrypt:

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

I uruchom go na różne sposoby z otwartym tylko jednym terminalem:

  1. Bezpośrednie uruchomienie, ./foo.sh.

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

    Tutaj używasz linii shebang. Oznacza to, że skrypt jest wykonywany bezpośrednio przez cokolwiek tam ustawione. Wpływa to na sposób wyświetlania skryptu na wyjściu ps. Zamiast być wymienionym jako bash foo.sh, zostanie pokazany tylko jako, foo.shco oznacza, że grepgo przegapisz. W rzeczywistości działają 3 instancje bash: proces nadrzędny, bash uruchamiający skrypt i kolejna, która uruchamia pspolecenie . To ostatnie jest ważne, uruchomienie polecenia z podstawieniem polecenia ( `command`lub $(command)) powoduje uruchomienie kopii powłoki nadrzędnej i uruchomienie polecenia. Tutaj jednak żadne z nich nie jest pokazane ze względu na sposób, w jaki pspokazuje on swoje wyniki.

  2. Uruchamianie bezpośrednie za pomocą jawnej powłoki (bash)

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

    Tutaj, ponieważ biegasz z bash foo.sh, wynik pswyświetli się bash foo.shi zostanie policzony. Mamy tutaj proces nadrzędny, bashuruchomienie skryptu i sklonowaną powłokę (uruchomienie ps), ponieważ teraz pspokaże każdy z nich, ponieważ twoje polecenie będzie zawierać słowo bash.

  3. Uruchamianie bezpośrednie za pomocą innej powłoki ( sh)

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

    Jest inaczej, ponieważ uruchamiasz skrypt za pomocą shi nie bash. Dlatego jedyną bashinstancją jest powłoka nadrzędna, w której uruchomiono skrypt. shZamiast tego uruchamiane są wszystkie pozostałe wyżej wymienione powłoki .

  4. Pozyskiwanie (przez .lub to sourcesamo)

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

    Jak wyjaśniono powyżej, pozyskiwanie skryptu powoduje, że działa on w tej samej powłoce co proces nadrzędny. Jednak oddzielna podpowłoka jest uruchamiana w celu uruchomienia pspolecenia, co daje całkowitą liczbę dwóch.


Na koniec poprawnym sposobem zliczania uruchomionych procesów nie jest parsowanie, psale użycie pgrep. Wszystkich tych problemów można by uniknąć, gdybyś po prostu biegł

pgrep -cu terdon bash

Tak więc działającą wersją skryptu, która zawsze drukuje prawidłową liczbę, jest (zauważ brak zastępowania poleceń):

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

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

To zwróci 1 po źródle i 2 (ponieważ zostanie uruchomiony nowy bash, aby uruchomić skrypt) dla wszystkich innych sposobów uruchamiania. Nadal zwróci 1 po uruchomieniu, shponieważ proces potomny nie jest bash.

terdon
źródło
Kiedy mówisz, że podstawienie polecenia uruchamia kopię powłoki nadrzędnej, czym różni się ta kopia od podpowłoki, tak jak po uruchomieniu skryptu z ./foo.sh?
Didier A.,
A kiedy uruchamiasz pgrep bez podstawiania poleceń, zakładam, że działa z tej samej powłoki, w której działa skrypt? Tak podobny do pozyskiwania?
Didier A.,
@didibus Nie jestem pewien, co masz na myśli. Podstawianie poleceń działa w podpowłoce; ./foo.shdziała w nowej powłoce, która nie jest kopią elementu nadrzędnego. Na przykład, jeśli ustawisz foo="bar"w swoim terminalu, a następnie uruchomisz skrypt echo $foo, który zostanie wykonany , otrzymasz pustą linię, ponieważ powłoka skryptu nie odziedziczy wartości zmiennej. pgrepjest osobnym plikiem binarnym i tak, jest uruchamiany przez uruchamiany skrypt.
terdon
Zasadniczo potrzebuję wyjaśnienia: „zauważ brak zastępowania poleceń”. Dlaczego uruchomienie pliku binarnego pgrep ze skryptu nie dodaje dodatkowej powłoki, ale uruchomienie pliku binarnego ps z podstawieniem poleceń? Po drugie, potrzebuję wyjaśnienia na temat „kopii powłoki rodzica”, czy to jest jak podpowłoka, w której zmienne powłoki rodzica są kopiowane do dziecka? Dlaczego to robi?
Didier A.,