Подтвердить что ты не робот

Bash script исполнение с и без shebang в Linux и BSD

Как и кто определяет, что выполняется, когда Bash -like script выполняется как двоичный файл без shebang?

Я предполагаю, что работа с обычным script с shebang обрабатывается с помощью binfmt_script Модуль Linux, который проверяет shebang, анализирует командную строку и запускает назначенный интерпретатор script.

Но что происходит, когда кто-то запускает script без shebang? Я протестировал прямой подход execv и выяснил, что там нет волшебства ядра, то есть такого файла:

$ cat target-script
echo Hello
echo "bash: $BASH_VERSION"
echo "zsh: $ZSH_VERSION"

Запуск скомпилированной C-программы, которая выполняет только вызов execv:

$ cat test-runner.c
void main() {
        if (execv("./target-script", 0) == -1)
                perror();
}
$ ./test-runner
./target-script: Exec format error

Однако, если я делаю то же самое из другой оболочки script, она запускает целевой script, используя тот же интерпретатор интерпретатора оболочки, что и исходный:

$ cat test-runner.bash
#!/bin/bash
./target-script

$ ./test-runner.bash 
Hello
bash: 4.1.0(1)-release
zsh: 

Если я делаю тот же трюк с другими оболочками (например, Debian по умолчанию sh - /bin/dash), он также работает:

$ cat test-runner.dash   
#!/bin/dash
./target-script

$ ./test-runner.dash 
Hello
bash: 
zsh: 

Загадочно, он не работает должным образом с zsh и не следует общей схеме. Похоже, zsh выполнил /bin/sh в таких файлах в конце концов:

[email protected] ~/z/test-runner $ cat test-runner.zsh 
#!/bin/zsh
echo ZSH_VERSION=$ZSH_VERSION
./target-script

[email protected] ~/z/test-runner $ ./test-runner.zsh 
ZSH_VERSION=4.3.10
Hello
bash: 
zsh: 

Обратите внимание, что ZSH_VERSION в родительском script работал, а ZSH_VERSION у ребенка не было!

Как оболочка (Bash, тире) определяет, что выполняется, когда нет shebang? Я попытался выкопать это место в источниках Bash/dash, но, увы, похоже, что я потерялся там. Может ли кто-нибудь пролить свет на магию, которая определяет, должен ли файл цели без shebang выполняться как script или как двоичный файл в Bash/dash? Или может быть какое-то взаимодействие с kernel/libc, и тогда я бы приветствовал объяснения того, как это работает в Linux и ядрах FreeBSD/libcs?

4b9b3361

Ответ 1

Так как это происходит в тире, а тире проще, я сначала посмотрел туда.

Похоже, что exec.c - это место для поиска, а соответствующие функции - tryexec, который вызывается из shellexec, который вызывается всякий раз, когда необходимо, чтобы командная оболочка выполняла команду. И (упрощенная версия) функция tryexec выглядит следующим образом:

STATIC void
tryexec(char *cmd, char **argv, char **envp)
{
        char *const path_bshell = _PATH_BSHELL;

repeat:

        execve(cmd, argv, envp);

        if (cmd != path_bshell && errno == ENOEXEC) {
                *argv-- = cmd;
                *argv = cmd = path_bshell;
                goto repeat;
        }
}

Таким образом, он просто всегда заменяет команду на выполнение с помощью пути к себе (_PATH_BSHELL по умолчанию "/bin/sh"), если происходит ENOEXEC. Здесь нет никакой магии.

Я обнаружил, что FreeBSD демонстрирует идентичное поведение в bash и в своем собственном sh.

Путь bash обрабатывает это похоже, но намного сложнее. Если вы хотите заглянуть в нее, я рекомендую читать bash execute_command.c и смотреть конкретно на execute_shell_script, а затем shell_execve. Комментарии являются довольно описательными.

Ответ 2

(Похоже, что Sorpigal покрыл его, но я уже набрал это, и это может представлять интерес.)

Согласно раздел 3.16 часто задаваемых вопросов Unix, оболочка сначала смотрит на магическое число (первые два байта файла). Некоторые номера указывают бинарный исполняемый файл; #! указывает, что остальную часть строки следует интерпретировать как shebang. В противном случае оболочка пытается запустить ее как оболочку script.

Кроме того, кажется, что csh смотрит на первый байт, и если он #, он попытается запустить его как csh script.