Dlaczego „ps ax” nie znajduje działającego skryptu bash bez „#!” nagłówek?


13

Kiedy uruchamiam ten skrypt, zamierzam go uruchomić aż do zabicia ...

# foo.sh

while true; do sleep 1; done

... Nie mogę go znaleźć za pomocą ps ax:

>./foo.sh

// In a separate shell:
>ps ax | grep foo.sh
21110 pts/3    S+     0:00 grep --color=auto foo.sh

... ale jeśli po prostu dodam wspólny #!nagłówek " " do skryptu ...

#! /usr/bin/bash
# foo.sh

while true; do sleep 1; done

... wtedy skrypt można znaleźć za pomocą tego samego pspolecenia ...

>./foo.sh

// In a separate shell:
>ps ax | grep foo.sh
21319 pts/43   S+     0:00 /usr/bin/bash ./foo.sh
21324 pts/3    S+     0:00 grep --color=auto foo.sh

Dlaczego tak jest?
To może być powiązane pytanie: Myślałem, że „ #” to tylko przedrostek komentarza, a jeśli tak, to „ #! /usr/bin/bash” sam w sobie jest jedynie komentarzem. Ale czy „ #!” ma jakieś znaczenie większe niż tylko komentarz?


Jakiego Uniksa używasz?
Kusalananda

@Kusalananda - Linux linuxbox 3.11.10-301.fc20.x86_64 # 1 SMP czw. Gru 5 14:01:17 UTC 2013 x86_64 x86_64 x86_64 GNU / Linux
StoneThrow

Odpowiedzi:


13

Gdy bieżąca interaktywna powłoka jest bashuruchomiona, a ty uruchomisz skrypt bez #!-line, wtedy bashuruchom skrypt. Proces pojawi się na ps axwyjściu jako sprawiedliwy bash.

$ cat foo.sh
# foo.sh

echo "$BASHPID"
while true; do sleep 1; done

$ ./foo.sh
55411

W innym terminalu:

$ ps -p 55411
  PID TT  STAT       TIME COMMAND
55411 p2  SN+     0:00.07 bash

Związane z:


Odpowiednie sekcje tworzą bashinstrukcję:

Jeśli wykonanie nie powiedzie się, ponieważ plik nie jest w formacie wykonywalnym, a plik nie jest katalogiem, zakłada się , że jest to skrypt powłoki , plik zawierający polecenia powłoki. Powstaje podpowłoka, aby ją wykonać. Ta podpowłoka inicjuje się ponownie, tak że efekt jest taki, jakby wywołano nową powłokę do obsługi skryptu , z tym wyjątkiem, że lokalizacje poleceń zapamiętanych przez rodzica (patrz skrót poniżej WBUDOWANE POLECENIA POWŁOKI) są zachowywane przez dziecko.

Jeśli program jest plikiem zaczynającym się od #!, reszta pierwszego wiersza określa interpreter dla programu. Powłoka wykonuje określony interpreter w systemach operacyjnych, które same nie obsługują tego formatu wykonywalnego. [...]

Oznacza to, że uruchamianie ./foo.shw wierszu poleceń, gdy foo.shnie ma #!-linii, jest takie samo, jak uruchamianie poleceń w pliku w podpowłoce, tj.

$ ( echo "$BASHPID"; while true; do sleep 1; done )

Przy odpowiedniej linii #!wskazującej na np /bin/bash. Robi to tak samo

$ /bin/bash foo.sh

Myślę, że podążam, ale to, co mówisz, jest również prawdziwe w drugim przypadku: bash uruchamia również skrypt w drugim przypadku, co można zaobserwować, gdy psskrypt działa jako „ /usr/bin/bash ./foo.sh”. Więc w pierwszym przypadku, jak mówisz, bash uruchomi skrypt, ale czy ten skrypt nie musiałby zostać „przekazany” do pliku wykonywalnego bash rozwidlonego, jak w drugim przypadku? (a jeśli tak, to wyobrażam sobie, że można go znaleźć przy pomocy fajki do grep ...?)
StoneThrow

@StoneThrow Zobacz zaktualizowaną odpowiedź.
Kusalananda

„... oprócz tego, że otrzymujesz nowy proces” - cóż, otrzymujesz nowy proces w obie strony, z tym wyjątkiem, że $$nadal wskazuje na stary proces w przypadku podpowłoki ( echo $BASHPID/ bash -c 'echo $PPID').
Michael Homer,

@MichaelHomer Ah, dzięki za to! Zaktualizuję.
Kusalananda

12

Kiedy zaczyna się skrypt powłoki #!, ten pierwszy wiersz jest komentarzem w odniesieniu do powłoki. Jednak pierwsze dwa znaki mają znaczenie dla innej części systemu: jądra. Dwie postacie #!nazywane są shebang . Aby zrozumieć rolę shebang, musisz zrozumieć, jak program jest wykonywany.

Wykonywanie programu z pliku wymaga działania z jądra. Odbywa się to w ramach execvewywołania systemowego. Jądro musi zweryfikować uprawnienia do pliku, zwolnić zasoby (pamięć itp.) Związane z plikiem wykonywalnym aktualnie uruchomionym w procesie wywoływania, przydzielić zasoby dla nowego pliku wykonywalnego i przenieść kontrolę do nowego programu (i więcej rzeczy, które Nie wspomnę). execveWywołanie systemowe zastępuje kod aktualnie uruchomionego procesu; istnieje osobne wywołanie systemowe, forkaby utworzyć nowy proces.

Aby to zrobić, jądro musi obsługiwać format pliku wykonywalnego. Ten plik musi zawierać kod maszynowy zorganizowany w sposób zrozumiały dla jądra. Skrypt powłoki nie zawiera kodu maszynowego, więc nie można go wykonać w ten sposób.

Mechanizm shebang pozwala jądru na odroczenie zadania interpretacji kodu do innego programu. Kiedy jądro widzi, że zaczyna się plik wykonywalny #!, odczytuje kilka kolejnych znaków i interpretuje pierwszy wiersz pliku (minus #!spację wiodącą i opcjonalną) jako ścieżkę do innego pliku (plus argumenty, których nie będę tutaj omawiać ). Kiedy jądro ma wykonać plik /my/scripti widzi, że plik zaczyna się od linii #!/some/interpreter, jądro wykonuje się /some/interpreterz argumentem /my/script. Następnie /some/interpreternależy zdecydować, że /my/scriptjest to plik skryptu, który powinien wykonać.

Co jeśli plik nie zawiera natywnego kodu w formacie zrozumiałym dla jądra i nie zaczyna się od shebang? Cóż, wtedy plik nie jest wykonywalny, a execvewywołanie systemowe kończy się niepowodzeniem z kodem błędu ENOEXEC(błąd formatu wykonywalnego).

To może być koniec historii, ale większość pocisków implementuje funkcję awaryjną. Jeśli jądro powróci ENOEXEC, powłoka sprawdza zawartość pliku i sprawdza, czy wygląda jak skrypt powłoki. Jeśli powłoka uważa, że ​​plik wygląda jak skrypt powłoki, wykonuje go samodzielnie. Szczegóły tego, jak to robi, zależą od powłoki. Możesz zobaczyć, co się dzieje, dodając ps $$do skryptu, a więcej, obserwując proces, w strace -p1234 -f -eprocessktórym 1234 to PID powłoki.

W bash ten mechanizm rezerwowy jest realizowany przez wywołanie, forkale nie execve. Proces potomnego bash sam usuwa stan wewnętrzny i otwiera nowy plik skryptu, aby go uruchomić. Dlatego proces, który uruchamia skrypt, nadal używa oryginalnego obrazu kodu bash, a oryginalne argumenty wiersza poleceń przekazane przy pierwszym wywołaniu bash. ATT ksh zachowuje się w ten sam sposób.

% bash --norc
bash-4.3$ ./foo.sh 
  PID TTY      STAT   TIME COMMAND
21913 pts/2    S+     0:00 bash --norc

Dash natomiast reaguje na ENOEXECwywołanie /bin/shze ścieżką do skryptu przekazaną jako argument. Innymi słowy, wykonanie skryptu bez kreski z kreski zachowuje się tak, jakby skrypt zawierał linię shebang #!/bin/sh. Mksh i zsh zachowują się w ten sam sposób.

% dash
$ ./foo.sh
  PID TTY      STAT   TIME COMMAND
21427 pts/2    S+     0:00 /bin/sh ./foo.sh

Świetna, kompleksowa odpowiedź. Jedno pytanie RE: implementacja zastępcza, którą wyjaśniłeś: Przypuszczam, że ponieważ dziecko bashjest rozwidlone, ma dostęp do tej samej argv[]tablicy, co jego rodzic, dzięki czemu wie, że „oryginalne argumenty wiersza poleceń zostały przekazane przy pierwszym wywołaniu bash”, i jeśli dlatego właśnie dziecku nie przekazano oryginalnego skryptu jako jawnego argumentu (a więc dlaczego grep nie może go znaleźć) - czy to jest dokładne?
StoneThrow

1
Możesz faktycznie wyłączyć zachowanie shebang jądra ( BINFMT_SCRIPTmoduł to kontroluje i może być usunięte / zmodularyzowane, chociaż zwykle jest statycznie połączone z jądrem), ale nie rozumiem, dlaczego byś chciał, chyba że w systemie osadzonym . Aby obejść tę możliwość, bashma flagę konfiguracji ( HAVE_HASH_BANG_EXEC) w celu kompensacji!
ErikF,

2
@StoneThrow Nie tyle bash potomny „zna oryginalne argumenty wiersza poleceń”, ile ich nie modyfikuje. Program może modyfikować psraporty jako argumenty wiersza poleceń, ale tylko do pewnego momentu: musi zmodyfikować istniejący bufor pamięci, nie może go powiększyć. Więc jeśli bash spróbuje zmodyfikować go, argvaby dodać nazwę skryptu, nie zawsze będzie działać. Dziecko nie „przekazało argumentu”, ponieważ nigdy nie ma execvewywołania systemowego w nim. To tylko ten sam obraz procesu bash, który działa.
Gilles 'SO - przestań być zły'

-1

W pierwszym przypadku skrypt jest uruchamiany przez rozwidlone dziecko z bieżącej powłoki.

Najpierw powinieneś uruchomić, echo $$a następnie spojrzeć na powłokę, która ma identyfikator procesu powłoki jako identyfikator procesu nadrzędnego.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.