Zasada wywoływania podpowłoki w Bash?

24

Wydaje mi się, że źle rozumiem zasadę Bash dotyczącą tworzenia podpowłoki. Myślałem, że nawiasy zawsze tworzą podpowłokę, która działa jako własny proces.

Wydaje się jednak, że tak nie jest. W fragmencie kodu A (poniżej) drugie sleeppolecenie nie działa w osobnej powłoce (określonej przez pstreeinny terminal). Jednak we fragmencie kodu B drugie sleeppolecenie działa w osobnej powłoce. Jedyną różnicą między fragmentami jest to, że drugi fragment zawiera dwa polecenia w nawiasach.

Czy ktoś mógłby wyjaśnić tę zasadę podczas tworzenia podpowłoki?

KOD SNIPPET A:

sleep 5
(
sleep 5
)

KOD SNIPPET B:

sleep 5
(
x=1
sleep 5
)
wstydliwy
źródło

Odpowiedzi:

20

Nawiasy zawsze rozpoczynają podpowłokę. Dzieje się tak, że bash wykrywa, że sleep 5jest to ostatnie polecenie wykonane przez tę podpowłokę, więc wywołuje execzamiast fork+ exec. sleepKomenda zastępuje powłoki w tle w tym samym procesie.

Innymi słowy, podstawowym przypadkiem jest:

  1. ( … )utwórz podpowłokę. Oryginalne wywołania procesów forki wait. W podprocesie, który jest podpowłoką:
    1. sleepjest zewnętrznym poleceniem, które wymaga podprocesu podprocesu. Wywołania podpowłoki forki wait. W podprocesie:
      1. Podproces wykonuje polecenie zewnętrzne → exec.
      2. Ostatecznie polecenie kończy się → exit.
    2. wait uzupełnia się w podpowłoce.
  2. wait kończy się w oryginalnym procesie.

Optymalizacja to:

  1. ( … )utwórz podpowłokę. Oryginalne wywołania procesów forki wait. W podprocesie, który jest podpowłoką, dopóki nie wywoła exec:
    1. sleep jest poleceniem zewnętrznym i jest ostatnią rzeczą, którą ten proces musi zrobić.
    2. Podproces wykonuje polecenie zewnętrzne → exec.
    3. Ostatecznie polecenie kończy się → exit.
  2. wait kończy się w oryginalnym procesie.

Po dodaniu czegoś innego po wywołaniu sleepnależy zachować podpowłokę, aby ta optymalizacja nie mogła się zdarzyć.

Kiedy dodasz coś jeszcze przed wywołaniem sleep, można dokonać optymalizacji (i robi to ksh), ale bash tego nie robi (przy tej optymalizacji jest bardzo konserwatywny).

Gilles „SO- przestań być zły”
źródło
Subshell jest tworzony przez wywołanie, forka proces potomny jest tworzony (w celu wykonywania poleceń zewnętrznych) przez wywołaniefork + exec . Ale twój pierwszy para sugeruje, że fork + execjest to również wymagane dla podpowłoki. Co się tutaj mylę?
haccks,
1
@haccks fork+ execnie jest wywoływany dla podpowłoki, jest wywoływany dla polecenia zewnętrznego. Bez jakiejkolwiek optymalizacji istnieje forkwywołanie podpowłoki i kolejne polecenie zewnętrzne. Do mojej odpowiedzi dodałem szczegółowy opis przepływu.
Gilles „SO- przestań być zły”
Wielkie dzięki za aktualizację. Teraz to wyjaśnia lepiej. Mogę z tego wywnioskować, że w przypadku (...)(w przypadku podstawowym) może istnieć wywołanie execzależne od tego, czy podpowłoka ma jakieś zewnętrzne polecenie do wykonania, podczas gdy w przypadku wykonania jakiegokolwiek zewnętrznego polecenia musi być fork + exec.
haccks,
Jeszcze jedno pytanie: czy ta optymalizacja działa tylko dla podpowłoki, czy może być wykonana dla polecenia jak datew powłoce?
haccks
@haccks Nie rozumiem pytania. Ta optymalizacja polega na wywołaniu zewnętrznego polecenia jako ostatniej czynności wykonywanej przez powłokę. Nie ogranicza się to do podpowłoki: porównaj strace -f -e clone,execve,write bash -c 'date'istrace -f -e clone,execve,write bash -c 'date; true'
Gilles 'SO- przestań być zły'
4

Z Przewodnika programowania zaawansowanego Bash :

„Ogólnie rzecz biorąc, zewnętrzne polecenie w skrypcie odrzuca podproces, podczas gdy wbudowane Bash nie. Z tego powodu wbudowane polecenia wykonują się szybciej i zużywają mniej zasobów systemowych niż ich zewnętrzne odpowiedniki”.

I nieco dalej:

„Lista poleceń osadzona między nawiasami działa jako podpowłoka”.

Przykłady:

[root@talara test]# echo $BASHPID
10792
[root@talara test]# (echo $BASHPID)
4087
[root@talara test]# (echo $BASHPID)
4088
[root@talara test]# (echo $BASHPID)
4089

Przykład użycia kodu OPs (z krótszymi snami, ponieważ jestem niecierpliwy):

echo $BASHPID

sleep 2
(
    echo $BASHPID
    sleep 2
    echo $BASHPID
)

Wyjście:

[root@talara test]# bash sub_bash
6606
6608
6608
Tim
źródło
2
Dzięki za odpowiedź Tim. Nie jestem jednak pewien, czy w pełni odpowiada na moje pytanie. Ponieważ „Lista poleceń osadzona między nawiasami działa jako podpowłoka”, oczekiwałbym, że druga sleepbędzie działała w podpowłoce (być może w procesie podpowłoki, ponieważ jest to wbudowana, a nie podproces podpowłoki). W każdym razie spodziewałbym się, że istnieje podpowłoka, tj. Podproces Bash w ramach nadrzędnego procesu Bash. W przypadku powyższego fragmentu B wydaje się, że tak nie jest.
wstydliwy
Korekta: Ponieważ sleepnie wydaje się być wbudowanym, oczekiwałbym, że drugie sleepwywołanie w obu fragmentach będzie działało w podprocesie procesu podpowłoki.
wstydliwy
@ bać się, że mogłem włamać się do twojego kodu za pomocą mojej $BASHPIDzmiennej. Niestety sposób, w jaki to robiłeś, nie dawał ci całej historii, w którą wierzę. Zobacz moje dodane wyniki w odpowiedzi.
Tim
4

Dodatkowa uwaga do odpowiedzi @Gilles.

Jak powiedział Gilles: The parentheses always start a subshell.

Jednak liczby, które ma taka podpowłoka, mogą się powtarzać:

$ (echo "$BASHPID and $$"; sleep 1)
2033 and 31679
$ (echo "$BASHPID and $$"; sleep 1)
2040 and 31679
$ (echo "$BASHPID and $$"; sleep 1)
2047 and 31679

Jak widać, $$ ciągle się powtarza, i to zgodnie z oczekiwaniami, ponieważ (wykonaj to polecenie, aby znaleźć poprawną man bashlinię):

$ LESS=+/'^ *BASHPID' man bash

BASHPID
Rozwija się do identyfikatora procesu bieżącego procesu bash. Różni się to od $$ w pewnych okolicznościach, takich jak podpowłoki, które nie wymagają ponownej inicjalizacji bash.

To znaczy: Jeśli powłoka nie zostanie ponownie zainicjowana, $$ jest taka sama.

Lub z tym:

$ LESS=+/'^ *Special Parameters' man bash

Parametry specjalne
$ Rozwija się do identyfikatora procesu powłoki. W podpowłoce () rozwija się do identyfikatora procesu bieżącej powłoki, a nie podpowłoki.

Jest $$to identyfikator bieżącej powłoki (nie podpowłoki).


źródło
1
Fajna sztuczka do otwierania strony podręcznika bash w konkretnej sekcji
Daniel Serodio