Почему «-» в «#! / bin / sh - ”Шебанг?

28
#! / bin / sh -

Является (или, по крайней мере, было) часто рекомендуемым Шебангом для интерпретации сценария /bin/sh.

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

Для чего это -?

Стефан Шазелас
источник

Ответы:

38

По той же причине, по которой вам нужно написать:

rm -- *.txt

И нет

rm *.txt

Если вы не можете гарантировать, что ни один из .txtфайлов в текущем каталоге не имеет имени, начинающегося с -.

В:

рм <арг>

<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()и общее использование --для обозначения конца опций надолго. То, как оболочка Bourne (с конца 70-х) анализировала свои аргументы, рассматривался только первый аргумент для вариантов, если он начинался с -. Все символы после -будут рассматриваться как имена опций; если после персонажа не было персонажа -, вариантов не было. Это застряло, и все последующие Bourne-подобные оболочки признают -как способ обозначить конец вариантов.

В оболочке Bourne (но не в современных Bourne-подобных оболочках) #! /bin/sh -eufтакже можно обойти проблему, так как для вариантов был рассмотрен только первый аргумент.

Теперь, можно сказать , что мы быть педантичным здесь, и это также , почему я написал необходимость курсивом выше:

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

Теперь, кроме проблемы теоретической правильности , есть еще одна причина, по которой он #! /bin/sh -стал рекомендоваться как хорошая практика . И это восходит ко времени, когда несколько систем все еще поддерживали сценарии setuid.

Если у вас есть скрипт, который содержит:

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

И этот сценарий был setuid 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-На них будут LookUp аргументы сценария , не /в $PATHхотя).

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

Обратите внимание, что вы сталкиваетесь с похожими проблемами с большинством переводчиков, а не только с борновскими оболочками Оболочки, не похожие на Bourne, обычно не поддерживают -маркер конца опции, но обычно поддерживают --вместо этого (по крайней мере, для современных версий).

Также

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

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

Также обратите внимание, что на большинстве систем нельзя использовать:

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

Как и в большинстве систем, механизм Шебанга допускает только один аргумент после пути интерпретатора.

Что касается использования #! /bin/sh -против #!/bin/sh -, это просто вопрос вкуса. Я предпочитаю первый, поскольку он делает путь интерпретатора более заметным и облегчает выбор мышью. Существует легенда, которая гласит, что место было необходимо в некоторых древних версиях Unix, но AFAIK, что никогда не проверялось.

Очень хорошая ссылка о Шебанге Unix может быть найдена в https://www.in-ulm.de/~mascheck/various/shebang

Стефан Шазелас
источник
1
Имеет ли это какое-либо отношение к #! / Bin / bash?
Джо
1
@ Джо, да, конечно, bashпринимает параметры, как и любая другая оболочка. Будучи Bourne-подобной и POSIX-оболочкой, bashпринимает как #! /bin/bash -и #! /bin/bash --.
Стефан