Чому "-" у "#! / бін / ш - ”shebang?


28
#! / бін / ш -

Часто рекомендований шебанг є (або принаймні був), щоб інтерпретувати сценарій /bin/sh.

Чому б не просто #! /bin/shчи #!/bin/sh?

Для чого це -?

Відповіді:


38

Це з аналогічної причини, чому потрібно писати:

rm -- *.txt

І ні

rm *.txt

Якщо ви не можете гарантувати, що жоден з .txtфайлів у поточному каталозі не має імені, з якого починається -.

В:

rm <arg>

<arg>вважається варіантом, якщо він починається з -або видалити файл в іншому випадку. В

rm - <arg>

argзавжди вважається файлом для видалення незалежно від того, починається він -чи ні.

Це те саме для sh.

Коли виконується сценарій, який починається з

#! /bin/sh

Як правило:

execve("path/to/the-script", ["the-script", "arg"], [environ])

Система перетворює його на:

execve("/bin/sh", ["/bin/sh", "path/to/the-script", "arg"], [environ])

Зазвичай path/to/the-scriptце не те, що знаходиться під контролем автора сценарію. Автор не може передбачити, де зберігатимуться копії сценарію, ні під яким іменем. Зокрема, вони не можуть гарантувати, що з того, path/to/the-scriptз чим він викликається, не почнеться -(або з чим +це теж проблема sh). Ось чому ми потрібні- тут , щоб відзначити кінець опцій.

Наприклад, у моїй системі zcat(як і у більшості інших сценаріїв насправді) є один приклад сценарію, який не дотримувався цієї рекомендації:

$ head -n1 /bin/zcat
#!/bin/sh
$ mkdir +
$ ln -s /bin/zcat +/
$ +/zcat
/bin/sh: +/: invalid option
[...]

Тепер ви можете запитати, чому #! /bin/sh -ні #! /bin/sh --?

Хоча #! /bin/sh --би працював із оболонками POSIX, той #! /bin/sh -більш портативний; зокрема до стародавніх версій о sh. shтрактування -як і попередніх варіантів закінчення варіанта, getopt()і загальне використання --для довготривалого позначення кінця варіантів. Те, як оболонка Борна (з кінця 70-х) розбирала свої аргументи, лише перший аргумент вважався варіантами, якщо він починався з -. Усі символи після -вважатимуться іменами опцій; якщо не було символу після -, не було варіантів. Це застрягло, і всі пізніші оболонки Борна розпізнають -як спосіб позначити кінець варіантів.

У оболонці Борна (але не в сучасних оболонках, схожих на Борна), #! /bin/sh -eufтакож було б вирішено проблему, оскільки лише перший аргумент розглядався як варіант.

Тепер можна сказати, що ми тут робимо педантичний характер, і саме тому я написав необхідність курсивом вище:

  1. ніхто при здоровому глузді не буде викликати скрипт з чим - то , починаючи з -або +або помістити їх в каталог, ім'я якого починається з -або +.
  2. навіть якщо вони це зробили, спочатку можна стверджувати, що вони можуть звинувачувати лише себе, але також, коли ви викликаєте сценарій, частіше за все, це з оболонки або з execvp()/ execlp()-тип функцій. І в такому випадку, як правило, ви викликаєте їх або the-scriptдля того, щоб його шукали, і в $PATHцьому випадку аргумент шляху до execve()системного виклику зазвичай починається з /(ні -норм +) або так, як ./the-scriptякщо ви хочете the-scriptв поточному каталозі запускатися (і тоді шлях починається з ./ні, -ні з +них).

Тепер, крім питання теоретичної коректності , є ще одна причина, чому її #! /bin/sh -рекомендують використовувати як добру практику . І це повертається до часу, коли кілька систем все ще підтримували встановлені сценарії.

Якщо у вас є сценарій, який містить:

#! /bin/sh
/bin/echo "I'm running as root"

І цей скрипт був встановлений корінь (як і з -r-sr-xr-x root binдозволами), в тих системах, коли виконується звичайним користувачем,

execve("/bin/sh", ["/bin/sh", "path/to/the-script"], [environ])

буде зроблено як root!

Якщо користувач створив симпосилання /tmp/-i -> path/to/the-scriptі виконав його як -i, він запустив б інтерактивну оболонку ( /bin/sh -i) як root.

Це -дозволить обійтись (це не обійдеться з питаннями про стан перегонів або з тим, що деякі shреалізації, як-от такі, які ksh88базуються на основі, шукатимуть аргументи сценарію без того, /щоб $PATHвсе-таки було).

В даний час навряд чи будь-яка система підтримує встановлений сценарій, і деякі з тих, що все ще роблять (як правило, не за замовчуванням), в кінцевому підсумку роблять execve("/bin/sh", ["/bin/sh", "/dev/fd/<n>", arg])(де <n>дескриптор файлів відкритий для читання в сценарії), який працює як над цією проблемою, так і стан перегонів.

Зауважте, що у вас виникають подібні проблеми з більшістю перекладачів, а не тільки з оболонками Борна. Оболонки, не подібні до Борна, як правило, не підтримують -як маркер кінця опції, але, як правило, підтримують --(принаймні, для сучасних версій).

Також

#! /usr/bin/awk -f
#! /usr/bin/sed -f

Не майте проблеми, оскільки наступний аргумент розглядається як аргумент до -fпараметра в будь-якому випадку, але він все ще не працює, якщо path/to/scriptє -(у такому випадку, з більшості sed/ awkреалізацій, sed/ awkне читайте код з -файл, але замість stdin).

Також зверніть увагу, що в більшості систем не можна використовувати:

#! /usr/bin/env sh -
#! /usr/bin/perl -w --

Як і в більшості систем, механізм shebang дозволяє лише один аргумент після шляху інтерпретатора.

Що стосується використання #! /bin/sh -vs #!/bin/sh -, це лише питання смаку. Я віддаю перевагу першому, оскільки це робить шлях перекладача більш помітним і полегшує вибір миші. Існує легенда, яка говорить про те, що місце було потрібно в деяких старовинних версіях Unix, однак AFAIK, що ніколи не було перевірено.

Дуже хороший довідник про шебанг Unix можна знайти на https://www.in-ulm.de/~mascheck/various/shebang


1
Чи має це відношення до #! / Bin / bash?
Джо

1
@Joe, так, звичайно, bashприймає параметри, як і всі інші оболонки. Будучи оболонкою Борна і POSIX, bashприймає і те, #! /bin/bash -і #! /bin/bash --.
Стефан Шазелас
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.