Кілька аргументів у shebang


33

Мені цікаво, чи існує загальний спосіб передачі декількох варіантів у виконуваний файл через лінію shebang ( #!).

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

Припустимо, наприклад, що я хочу написати сценарій, який буде виконуватися bashв режимі posix. Наївним способом написання шебангу було б:

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

але спроба виконати скрипт, що виникає, видає таку помилку:

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

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


EDIT : Я знаю, що для сценаріїв 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процес живе, поки перекладач не закінчить свою роботу. Будемо вдячні будь-які відгуки чи пропозиції.



Відповіді:


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

Це дуже добре знати для подальшого використання.
Джон Макгі

Цей варіант був скопійований з FreeBSD, envкуди -Sбуло додано 2005 року. Див. List.gnu.org/r/coreutils/2018-04/msg00011.html
Stéphane Chazelas

Працює частування на Fedora 29
Ерік

@unode деякі вдосконалення showargs: pastebin.com/q9m6xr8H та pastebin.com/gS8AQ5WA (one-liner)
Ерік,

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 та більшість інших Unices нададуть аргументи як єдиний варіант для інтерпретатора.

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

Дивіться також розділ Переносність статті Шебанга у Вікіпедії .


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

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

Я не думаю, що я б особисто намагався змусити її повторно виконати себе, оскільки це відчуває себе дещо крихким.


7

Shebang описаний на execve(2) сторінці чоловіка так:

#! interpreter [optional-arg]

У цьому синтаксисі прийнято два пробіли:

  1. Один пробіл до шляху інтерпретатора , але цей пробіл необов’язковий.
  2. Простір, що розділяє шлях інтерпретатора та його необов'язковий аргумент.

Зауважте, що я не використовував множину, коли говорив про необов'язковий аргумент, а також не використовує синтаксис, описаний вище [optional-arg ...], оскільки ви можете навести щонайменше один єдиний аргумент .

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

У вашому випадку:

set -o posix

У підказці Bash перевірте результат, help setщоб отримати всі доступні параметри.


1
У вас може бути більше двох пробілів, вони просто вважаються частиною необов'язкового аргументу.
Стівен Кітт

@StephenKitt: Білий простір тут слід сприймати більше як категорію, ніж фактичний простір. Я припускаю, що інші білі проміжки, такі як вкладки, також повинні бути широко прийнятими.
WhiteWinterWolf

3

В Linux шебанг не дуже гнучкий; в відповідно до декількох варіантами відповідей ( відповідь Стівена Кітт в і Йорг W Mittag - х ), там немає призначеного шляху , щоб пройти кілька аргументів на хатину лінії.

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

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


Загортання сценарію в баш гередок всередині процесу заміни

Наскільки мені відомо, це найнадійніший мовно-агностичний спосіб зробити це. Це дозволяє передавати аргументи і зберігає stdin. Недолік полягає в тому, що перекладач не знає (реального) розташування файлу, який він читає.

#!/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 всередині процесу заміни в тирі.


Складання сценарію в простому гередоку

Коротше і простіше, але ви не зможете отримати доступ 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 вам доведеться використовувати конкатенацію рядків усередині a для циклу, див. Приклад функції https://www.gnu.org/software/gawk/manual/html_node/Join-Function.html .


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's sys.argv[1:]на аргументи, передані в командному рядку скрипту.

І ми також просимо envвстановити LD_LIBRARY_PATHзмінну середовища, яка є єдиною точкою злому.

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

shпотім замінюється новим екземпляром інтерпретатора python, який відкриває та читає сценарій джерела python, заданий як перший аргумент (the "$0").

Python відкриває файл і пропускає через 1-й рядок джерела завдяки -xаргументу. Примітка: це також працює без того, -xщо для Python shebang - це лише коментар .

Потім Python інтерпретує 2-й рядок як docstring для поточного файлу модуля, тому, якщо вам потрібна дійсна docstring модуля, просто встановіть __doc__першу річ у вашій програмі python, як у наведеному вище прикладі.



Зважаючи на те, що порожній рядок… гм… порожній, ви повинні мати можливість кинути вашу команду не знайденого бізнесу мавп: ''''exec ...слід виконати роботу. Не забувайте пробілу перед exec, інакше це змусить шукати порожню команду. Ви хочете , щоб зростити порожній на перші арг так так $0це exec.
Калеб

1

Я знайшов досить дурне вирішення, коли шукав виконуваний файл, який виключає сценарій як єдиний аргумент:

#!/usr/bin/awk BEGIN{system("bash --posix "ARGV[1])}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.