Почему «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 - 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, и вы запускаете сценарий без #!-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»), остаются дочерними.

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

Это означает, что выполнение ./foo.shв командной строке, когда foo.shнет строки, #!аналогично выполнению команд в файле в подоболочке, то есть

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

С правильной #!линией, указывающей, например /bin/bash, это как

$ /bin/bash foo.sh

Я думаю, что следую, но то, что вы говорите, верно и во втором случае: bash также запускает скрипт во втором случае, что можно наблюдать, когда psпоказывает, что скрипт работает как " /usr/bin/bash ./foo.sh". Таким образом, в первом случае, как вы говорите, bash запустит скрипт, но разве этот сценарий не нужно будет «передавать» в разветвленный исполняемый файл bash, как во втором случае? (и если это так, я думаю, что это можно было бы найти с помощью трубы, чтобы grep ...?)
StoneThrow

@StoneThrow Смотрите обновленный ответ.
Кусалананда

«... кроме того, что вы получаете новый процесс» - ну, вы получаете новый процесс в любом случае, за исключением того, что он $$все еще указывает на старый в случае подоболочки ( 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 оболочки.

В bash этот резервный механизм реализован путем вызова, 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путь к сценарию, переданному в качестве аргумента. Другими словами, когда вы выполняете сценарий без shebang из dash, он ведет себя так, как будто сценарий имеет строку shebang #!/bin/sh. Мкш и зш ведут себя одинаково.

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

Отличный, понятный ответ. Один вопрос RE: резервная реализация, которую вы объяснили: я полагаю, что поскольку дочерний элемент bashразветвлен, он имеет доступ к тому же argv[]массиву, что и его родительский элемент, и именно так он узнает «исходные аргументы командной строки, переданные при первоначальном вызове bash», и если вот почему ребенок не передал исходный сценарий в качестве явного аргумента (следовательно, почему он не может быть найден grep) - это точно?
StoneThrow

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

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

-1

В первом случае скрипт запускается раздвоенным потомком из вашей текущей оболочки.

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.