Чому "ps ax" не знайде запущений скрипт bash без "#!" заголовок?


13

Коли я запускаю цей сценарій, призначений для запуску, поки не вб'є ...

# foo.sh

while true; do sleep 1; done

... я не в змозі знайти його за допомогою ps ax:

>./foo.sh

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

... але якщо я просто додаю загальний " #!" заголовок до сценарію ...

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

while true; do sleep 1; done

... тоді сценарій стає придатним для цієї ж psкоманди ...

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

Чому це так?
Це може бути пов’язане питання: я вважав, що " #" є просто префіксом коментаря, і якщо так, " #! /usr/bin/bash" сам по собі є не що інше, як коментар. Але чи " #!" має якесь значення більше, ніж просто коментар?


Який Unix ви використовуєте?
Kusalananda

@Kusalananda - Linux linuxbox 3.11.10-301.fc20.x86_64 # 1 SMP Чт 5 грудня 14:01:17 UTC 2013 x86_64 x86_64 x86_64 GNU / Linux
StoneThrow

Відповіді:


13

Якщо поточна інтерактивна оболонка є bash, і ви запускаєте скрипт з no #!-line, тоді bashбуде запущений сценарій. Процес відображатиметься у ps axвисновку як просто bash.

$ cat foo.sh
# foo.sh

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

$ ./foo.sh
55411

В іншому терміналі:

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

Пов'язані:


Відповідні розділи складають bashпосібник:

Якщо це виконання не вдалося, оскільки файл не у виконаному форматі, а файл не є каталогом, передбачається, що це сценарій оболонки , файл, що містить команди оболонки. Для його виконання створюється підзарядка . Ця підпакет реініціалізує себе, так що ефект виглядає так, ніби для обробки сценарію було викликано нову оболонку , за винятком того, що дитина зберігає розташування команд, запам’ятовуваних батьком (див. Хеш нижче під SHELL BUILTIN COMMANDS).

Якщо програма - це файл, що починається з #!, в решті першого рядка вказується інтерпретатор програми. Оболонка виконує вказаний інтерпретатор в операційних системах, які самі не обробляють цей виконуваний формат. [...]

Це означає, що запуск ./foo.shу командному рядку, коли foo.shне має #!-line, це те саме, що виконання команд у файлі в підклітині, тобто як

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

З правильним #!рядком, що вказує на, наприклад /bin/bash, це так, як це робиться

$ /bin/bash foo.sh

Я думаю, що я слідую, але те, що ви говорите, вірно і у другому випадку: bash також запускає скрипт у другому випадку, як це можна спостерігати, коли psпоказ сценарію працює як " /usr/bin/bash ./foo.sh". Отже, у першому випадку, як ви кажете, bash запустить скрипт, але чи не слід цей сценарій "передавати" виконуваному файлу bash, як у другому випадку? (і якщо так, то я думаю, що це було б обтягуюче, щоб труба
заграла

@StoneThrow Дивіться оновлену відповідь.
Kusalananda

"... за винятком того, що ви отримуєте новий процес" - ну, ви отримуєте новий процес в будь-якому випадку, за винятком того, що він $$все ще вказує на старий у випадку підпакеті ( echo $BASHPID/ bash -c 'echo $PPID').
Майкл Гомер

@MichaelHomer Ах, дякую за це! Буде оновлено.
Кусалаланда

12

Коли сценарій оболонки починається з #!цього першого рядка, це коментар, що стосується оболонки. Однак перші два символи мають значення для іншої частини системи: ядра. Два персонажа #!називають шебангом . Щоб зрозуміти роль шебангу, потрібно зрозуміти, як виконується програма.

Виконання програми з файлу вимагає дії з ядром. Це робиться як частина execveсистемного виклику. Ядру потрібно перевірити права доступу до файлів, звільнити ресурси (пам'ять тощо), пов’язані з виконуваним файлом, який зараз працює в процесі виклику, виділити ресурси для нового виконуваного файлу та передати управління новій програмі (та інші речі, які Не згадаю). execveСистемний виклик замінює код процесу в даний час працює; існує окремий системний виклик forkдля створення нового процесу.

Для цього ядро ​​має підтримувати формат виконуваного файлу. Цей файл повинен містити машинний код, організований таким чином, що ядро ​​розуміє. Сценарій оболонки не містить машинного коду, тому його неможливо виконати таким чином.

Механізм shebang дозволяє ядру відкласти завдання інтерпретації коду для іншої програми. Коли ядро ​​бачить, що виконується файл починається з #!, воно читає наступні кілька символів і інтерпретує перший рядок файлу (мінус провідний #!та необов'язковий пробіл) як шлях до іншого файлу (плюс аргументи, про які я тут не буду обговорювати ). Коли ядро ​​повідомляється виконати файл /my/script, і воно бачить, що файл починається з рядка #!/some/interpreter, ядро ​​виконує /some/interpreterаргумент /my/script. Тоді /some/interpreterвирішувати, що /my/scriptце сценарій, який він повинен виконувати.

Що робити, якщо файл не містить нативного коду у форматі, який ядро ​​розуміє, і не починається з шебанга? Ну, тоді файл не виконується, а execveсистемний виклик виходить з ладу кодом помилки ENOEXEC(Виконавча помилка формату).

Це може бути кінцем історії, але більшість оболонок реалізує резервну функцію. Якщо ядро ​​повертається ENOEXEC, оболонка розглядає вміст файлу і перевіряє, чи схожий він на сценарій оболонки. Якщо оболонка вважає, що файл схожий на скрипт оболонки, він виконує його сам. Деталі того, як це робити, залежать від оболонки. Ви можете побачити деякі з того, що відбувається, додавши ps $$у свій скрипт та інше, переглядаючи процес, strace -p1234 -f -eprocessде 1234 є PID оболонки.

В основному, цей механізм резервного копіювання реалізується за допомогою виклику, forkале не execve. Дочірній процес bash очищає свій внутрішній стан сам і відкриває новий файл сценарію для його запуску. Тому процес, який запускає скрипт, все ще використовує оригінальне зображення коду bash та оригінальні аргументи командного рядка, передані при первинному виклику bash. ATT ksh поводиться так само.

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

Dash, на відміну від цього, реагує на ENOEXECдзвінок /bin/shіз передачею в якості аргументу шлях до сценарію. Іншими словами, коли ви виконуєте сценарій shebangless з тире, він поводиться так, як ніби у сценарію була лінія shebang #!/bin/sh. Mksh і zsh поводяться однаково.

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

Чудова, осмислена відповідь. Одне запитання RE: резервна реалізація, яку ви пояснили: я вважаю, оскільки дитина bashмає роздвоєння, вона має доступ до того ж argv[]масиву, що і його батько, і саме тому він знає "оригінальні аргументи командного рядка, передані, коли ви спочатку викликали bash", і якщо Отже, саме тому дитина не передає оригінальний скрипт як явний аргумент (отже, чому його не можна виправити грепом) - це точно?
StoneThrow

1
Ви можете фактично вимкнути поведінку shebang ядра ( BINFMT_SCRIPTмодуль керує цим і може бути видалений / модульований, хоча він зазвичай статично пов'язаний з ядром), але я не бачу, чому б ви цього хотіли, за винятком можливо вбудованої системи . Як вирішення цієї можливості, bashмає HAVE_HASH_BANG_EXECкомпенсацію прапор ( ) для компенсації!
ErikF

2
@StoneThrow Справа не стільки в тому, що дочірній баш "знає оригінальні аргументи командного рядка", скільки не змінює їх. Програма може змінювати дані psзвітів як аргументи командного рядка, але лише до моменту: вона повинна змінювати існуючий буфер пам'яті, вона не може збільшити цей буфер. Тож якби bash спробував змінити його, argvщоб додати ім'я сценарію, це не завжди вийшло. Дитині не «передається аргумент», тому що в ній ніколи не відбувається execveсистемний виклик. Це просто той самий образ, який триває в роботі.
Жил "ТАК - перестань бути злим"

-1

У першому випадку сценарій запускається роздвоєною дитиною з вашої поточної оболонки.

Спочатку слід запустити, echo $$а потім подивитися на оболонку, яка має ідентифікатор процесу вашої оболонки як ідентифікатор батьківського процесу.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.