Несколько аргументов в Шебанге

33

Мне интересно, существует ли общий способ передачи нескольких опций в исполняемый файл через строку shebang ( #!).

Я использую NixOS, и обычно первая часть шебанга в любом сценарии, который я пишу /usr/bin/env. Проблема, с которой я сталкиваюсь, состоит в том, что все, что приходит после, интерпретируется системой как один файл или каталог.

Предположим, например, что я хочу написать скрипт, который будет выполняться bashв режиме posix. Наивный способ написания Шебанга будет:

#!/usr/bin/env bash --posix

но попытка выполнить полученный скрипт выдает следующую ошибку:

/usr/bin/env: ‘bash --posix’: No such file or directory

Я знаю об этом посте , но мне было интересно, было ли более общее и более чистое решение.


РЕДАКТИРОВАТЬ : Я знаю, что для скриптов Guile , есть способ достичь того, что я хочу, документировано в разделе 4.3.4 руководства:

 #!/usr/bin/env sh
 exec guile -l fact -e '(@ (fac) main)' -s "$0" "$@"
 !#

Хитрость в том, что вторая строка (начинающаяся с exec) интерпретируется как код, shно, находясь в блоке #!... !#, как комментарий, и, следовательно, игнорируется интерпретатором Guile.

Не было бы возможно обобщить этот метод для любого интерпретатора?


Второе РЕДАКТИРОВАНИЕ : после небольшой игры кажется, что для интерпретаторов, которые могут читать их ввод stdin, будет работать следующий метод:

#!/usr/bin/env sh
sed '1,2d' "$0" | bash --verbose --posix /dev/stdin; exit;

Это, вероятно, не оптимально, поскольку shпроцесс длится до тех пор, пока переводчик не завершит свою работу. Любые отзывы или предложения будут оценены.

Rastapopoulos
источник

Ответы:

27

Не существует общего решения, по крайней мере, если вам нужно поддерживать Linux, потому что ядро ​​Linux рассматривает все, что следует за первым «словом» в строке shebang, как один аргумент .

Я не уверен, каковы ограничения NixOS, но обычно я бы просто написал ваш шебанг как

#!/bin/bash --posix

или, где возможно, установите параметры в скрипте :

set -o posix

В качестве альтернативы, вы можете сделать так, чтобы скрипт сам перезапускался с соответствующим вызовом оболочки:

#!/bin/sh -

if [ "$1" != "--really" ]; then exec bash --posix -- "$0" --really "$@"; fi

shift

# Processing continues

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

GNU coreutils' envобеспечивает обходной путь начиная с версии 8.30, см unode «сек ответ для деталей. (Это доступно в Debian 10 и новее, RHEL 8 и новее, Ubuntu 19.04 и новее и т. Д.)

Стивен Китт
источник
18

Хотя и не совсем переносимый, начиная с coreutils 8.30 и в соответствии с его документацией вы сможете использовать:

#!/usr/bin/env -S command arg1 arg2 ...

Итак, учитывая:

$ cat test.sh
#!/usr/bin/env -S showargs here 'is another' long arg -e "this and that " too

ты получишь:

% ./test.sh 
$0 is '/usr/local/bin/showargs'
$1 is 'here'
$2 is 'is another'
$3 is 'long'
$4 is 'arg'
$5 is '-e'
$6 is 'this and that '
$7 is 'too'
$8 is './test.sh'

и в случае, если вам интересно, showargsэто:

#!/usr/bin/env sh
echo "\$0 is '$0'"

i=1
for arg in "$@"; do
    echo "\$$i is '$arg'"
    i=$((i+1))
done
unode
источник
Это очень полезно знать для дальнейшего использования.
Джон МакГи
Эта опция была скопирована из FreeBSD, envгде она -Sбыла добавлена ​​в 2005 году. См. Lists.gnu.org/r/coreutils/2018-04/msg00011.html
Стефан
Работает на Fedora 29
Эрик
@unode некоторые улучшения showargs: pastebin.com/q9m6xr8H и pastebin.com/gS8AQ5WA (однострочный)
Эрик
FYI: от Coreutils 8,31, envвключает в свой showargs: опция -v , например#!/usr/bin/env -vS --option1 --option2 ...
chocolateboy
9

Стандарт POSIX очень лаконичен в описании #!:

Из обоснования раздела документации exec()семейства системных интерфейсов :

Другой способ, которым некоторые исторические реализации обрабатывают сценарии оболочки, заключается в распознавании первых двух байтов файла как строки символов #!и использовании оставшейся части первой строки файла в качестве имени интерпретатора команд для выполнения.

Из раздела «Введение в оболочку» :

Оболочка считывает данные из файла (см sh), с -cопцией или от system()и popen()функции , определенные в объеме системы Интерфейсы POSIX.1-2008. Если первая строка файла команд оболочки начинается с символов #!, результаты не указываются .

По сути, это означает, что любая реализация (используемая вами Unix) может выполнять специфику разбора строки shebang так, как хочет.

Некоторые Unices, такие как macOS (не могут тестировать ATM), будут разбивать аргументы, переданные интерпретатору в строке shebang, на отдельные аргументы, в то время как Linux и большинство других Unix будут давать аргументы интерпретатору как одну опцию.

Таким образом, неразумно полагаться на то, что линия Шебанга может принимать более одного аргумента.

Смотрите также раздел о переносимости статьи Шебанга в Википедии .


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

#!/bin/sh
exec /bin/bash --posix /some/path/realscript "$@"

Я не думаю, что я лично попытался бы заставить его перезапустить себя, поскольку это кажется несколько хрупким.

Кусалананда
источник
7

Шебанг описан на execve(2) странице руководства следующим образом:

#! interpreter [optional-arg]

В этом синтаксисе допускаются два пробела:

  1. Один пробел перед путём интерпретатора , но этот пробел необязателен.
  2. Один пробел, разделяющий путь интерпретатора и его необязательный аргумент.

Обратите внимание, что я не использовал множественное число, когда говорил о необязательном аргументе, также как и приведенный выше синтаксис [optional-arg ...], так как вы можете указать не более одного аргумента .

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

В твоем случае:

set -o posix

В командной строке Bash проверьте выходные данные, help setчтобы получить все доступные параметры.

WhiteWinterWolf
источник
1
Вам разрешено иметь более двух пробелов, они просто считаются частью необязательного аргумента.
Стивен Китт,
@StephenKitt: Действительно, пробел здесь должен восприниматься скорее как категория, чем фактический символ пространства. Я полагаю, что другие пробелы, такие как вкладки, также должны быть широко приняты.
WhiteWinterWolf
3

В Linux шебанг не очень гибкий; согласно множественным ответам (ответ Стивена Китта и Йорга Миттага ), не существует определенного способа передачи нескольких аргументов в строке Шебанга.

Я не уверен, будет ли это кому-нибудь полезно, но я написал короткий скрипт для реализации недостающей функции. См. Https://gist.github.com/loxaxs/7cbe84aed1c38cf18f70d8427bed1efa .

Также возможно написать встроенные обходные пути. Ниже я представляю четыре не зависящих от языка обходных пути, примененных к одному и тому же тестовому сценарию, и результат, который печатает каждый из них. Я полагаю, что скрипт является исполняемым и находится в /tmp/shebang.


Оборачиваем ваш скрипт в bash heredoc внутри процесса подстановки

Насколько я знаю, это самый надежный, независимый от языка способ сделать это. Это позволяет передавать аргументы и сохраняет стандартный ввод. Недостатком является то, что интерпретатор не знает (реального) местоположения файла, который он читает.

#!/bin/bash
exec python3 -O <(cat << 'EOWRAPPER'
print("PYTHON_SCRIPT_BEGINNING")

from sys import argv
try:
    print("input() 0 ::", input())
    print("input() 1 ::", input())
except EOFError:
    print("input() caused EOFError")
print("argv[0]   ::", argv[0])
print("argv[1:]  ::", argv[1:])
print("__debug__ ::", __debug__)
# The -O option changes __debug__ to False

print("PYTHON_SCRIPT_END")
EOWRAPPER
) "$@"

Печать echo -e 'aa\nbb' | /tmp/shebang 'arg1' 'arg2 contains spaces' 'arg3\ uses\ \\escapes\\'звонков:

PYTHON_SCRIPT_BEGINNING
input() 0 :: aa
input() 1 :: bb
argv[0]   :: /dev/fd/62
argv[1:]  :: ['arg1', 'arg2 contains spaces', 'arg3\\ uses\\ \\\\escapes\\\\']
__debug__ :: False
PYTHON_SCRIPT_END

Обратите внимание, что подстановка процесса создает специальный файл. Это может не подходить для всех исполняемых файлов. Например, #!/usr/bin/lessжалуется:/dev/fd/63 is not a regular file (use -f to see it)

Я не знаю, возможно ли иметь heredoc внутри процесса подстановки в тире.


Обернуть ваш скрипт в простой heredoc

Короче и проще, но вы не сможете получить доступ stdinиз своего скрипта, и для этого требуется, чтобы интерпретатор мог читать и выполнять скрипт из stdin.

#!/bin/sh
exec python3 - "$@" << 'EOWRAPPER'
print("PYTHON_SCRIPT_BEGINNING")

from sys import argv

try:
    print("input() 0 ::", input())
    print("input() 1 ::", input())
except EOFError:
    print("input() caused EOFError")
print("argv[0]   ::", argv[0])
print("argv[1:]  ::", argv[1:])
print("__debug__ ::", __debug__)
# The -O option changes __debug__ to False

print("PYTHON_SCRIPT_END")
EOWRAPPER

Печать echo -e 'aa\nbb' | /tmp/shebang 'arg1' 'arg2 contains spaces' 'arg3\ uses\ \\escapes\\'звонков:

PYTHON_SCRIPT_BEGINNING
input() caused EOFError
argv[0]   :: -
argv[1:]  :: ['arg1', 'arg2 contains spaces', 'arg3\\ uses\\ \\\\escapes\\\\']
__debug__ :: True
PYTHON_SCRIPT_END

Используйте system()вызов awk, но без аргументов

Правильно передает имя исполняемого файла, но ваш скрипт не получит аргументы, которые вы ему дадите. Обратите внимание, что awk - единственный из известных мне языков, чей интерпретатор по умолчанию установлен в linux и по умолчанию читает его инструкции из командной строки.

#!/usr/bin/gawk BEGIN {system("python3 -O " ARGV[1])}
print("PYTHON_SCRIPT_BEGINNING")

from sys import argv

print("input() 0 ::", input())
print("input() 1 ::", input())
print("argv[0]   ::", argv[0])
print("argv[1:]  ::", argv[1:])
print("__debug__ ::", __debug__)
# The -O option changes __debug__ to False

print("PYTHON_SCRIPT_END")

Печать echo -e 'aa\nbb' | /tmp/shebang 'arg1' 'arg2 contains spaces' 'arg3\ uses\ \\escapes\\'звонков:

PYTHON_SCRIPT_BEGINNING
input() 0 :: aa
input() 1 :: bb
argv[0]   :: /tmp/shebang
argv[1:]  :: []
__debug__ :: False
PYTHON_SCRIPT_END

Используйте system()вызов awk 4.1+ , если ваши аргументы не содержат пробелов

Хорошо, но только если вы уверены, что ваш скрипт не будет вызываться с аргументами, содержащими пробелы. Как вы можете видеть, ваши аргументы, содержащие пробелы, будут разделены, если пробелы не экранированы.

#!/usr/bin/gawk @include "join"; BEGIN {system("python3 -O " join(ARGV, 1, ARGC, " "))}
print("PYTHON_SCRIPT_BEGINNING")

from sys import argv

print("input() 0 ::", input())
print("input() 1 ::", input())
print("argv[0]   ::", argv[0])
print("argv[1:]  ::", argv[1:])
print("__debug__ ::", __debug__)
# The -O option changes __debug__ to False

print("PYTHON_SCRIPT_END")

Печать echo -e 'aa\nbb' | /tmp/shebang 'arg1' 'arg2 contains spaces' 'arg3\ uses\ \\escapes\\'звонков:

PYTHON_SCRIPT_BEGINNING
input() 0 :: aa
input() 1 :: bb
argv[0]   :: /tmp/shebang
argv[1:]  :: ['arg1', 'arg2', 'contains', 'spaces', 'arg3 uses \\escapes\\']
__debug__ :: False
PYTHON_SCRIPT_END

Для версий awk ниже 4.1 вам придется использовать конкатенацию строк внутри цикла for, см. Пример функции https://www.gnu.org/software/gawk/manual/html_node/Join-Function.html .

loxaxs
источник
1
Цитировать здесь терминатор документа, чтобы запретить $variableили `command`заменить:exec python3 -O <(cat <<'EOWRAPPER'
Джон МакГи
2

Трюк, который нужно использовать LD_LIBRARY_PATHс python в #!строке (shebang), которая не зависит ни от чего, кроме оболочки, и работает на удовольствие:

#!/bin/sh
'''' 2>/dev/null; exec /usr/bin/env LD_LIBRARY_PATH=. python -x "$0" "$@" #'''

__doc__ = 'A great module docstring'

Как объяснено в другом месте на этой странице, некоторые оболочки, такие как, shмогут использовать сценарий для своего стандартного ввода.

Сценарий мы даем shпытается выполнить команду , ''''которая упрощена до ''(пустая строка) с помощью shи, конечно , она не сможет выполнить его как нет ''команды, поэтому он обычно выводит line 2: command not foundна стандартный дескриптор ошибки , но мы переадресовать это сообщение , используя 2>/dev/nullв самая близкая черная дыра, потому что было бы беспорядочно и запутанно shпоказывать пользователю ее отображение.

Затем мы переходим к интересующей нас команде: execкоторая заменяет текущий процесс оболочки следующим, в нашем случае: /usr/bin/env pythonадекватными параметрами:

  • "$0" дать Python знать, какой скрипт он должен открывать и интерпретировать, а также устанавливать sys.argv[0]
  • "$@"установить Python sys.argv[1:]для аргументов, передаваемых в командной строке скрипта.

И мы также просим envустановить LD_LIBRARY_PATHпеременную окружения, которая является единственной целью взлома.

Команда оболочки заканчивается комментарием, начинающимся с #того, что оболочка игнорирует завершающие тройные кавычки '''.

shзатем заменяется новым экземпляром интерпретатора python, который открывает и читает исходный скрипт python, указанный в качестве первого аргумента (the "$0").

Python открывает файл и пропускает первую строку исходного кода благодаря -xаргументу. Примечание: это также работает без, -xпотому что для Python шебанг это просто комментарий .

Затем Python интерпретирует 2-ю строку как строку документации для текущего файла модуля, поэтому, если вам нужна действительная строка документации модуля, просто установите __doc__в своей программе Python первое, как в примере выше.

Эрик
источник
Учитывая, что пустая строка ... хм ... пуста, вы должны быть в состоянии отбросить вашу команду not found monkey business: ''''exec ...должны выполнить работу. Обратите внимание, что перед exec нет пробела, иначе он будет искать пустую команду. Вы хотите разделить пустое место на первый аргумент, так оно и $0есть exec.
Калеб
1

Я нашел довольно глупый обходной путь, когда искал исполняемый файл, который исключает скрипт в качестве единственного аргумента:

#!/usr/bin/awk BEGIN{system("bash --posix "ARGV[1])}
до
источник