Как определить сценарий оболочки, который не запускается

40

Я определяю сценарий оболочки, который пользователь должен sourceвыполнять, а не выполнять.

Есть ли обычный или интеллектуальный способ намека пользователю, что это так, например, с помощью расширения файла?

Есть ли код оболочки, который я могу написать в самом файле, который заставит его повторить сообщение и выйти, если он выполняется вместо источника, чтобы я мог помочь пользователю избежать этой очевидной ошибки?

водорослевые
источник
1
Итак, если пользователь пишет однострочный сценарий оболочки x, который просто содержит команду . your-script-to-be-sourced, то все в порядке, но если он хочет выполнить bash your-script-to-be-sourcedего, это должно быть запрещено? Какой смысл этого ограничения?
user1934428
8
@ user1934428 Конечно. Это нормально для сценария, который вычисляет количество envпеременных и оставляет их как вывод сценария де-факто . Новичок застрянет на несколько дней с загадкой, если вы позволите им исполниться.
kubanczyk

Ответы:

45

Предполагая, что вы запускаете bash, поместите следующий код в начало скрипта, который вы хотите получить, но не выполняете:

if [ "${BASH_SOURCE[0]}" -ef "$0" ]
then
    echo "Hey, you should source this script, not execute it!"
    exit 1
fi

В разделе bash ${BASH_SOURCE[0]}будет содержаться имя текущего файла, который читает оболочка, независимо от того, был ли он получен или выполнен.

Напротив, $0это имя текущего исполняемого файла.

-efпроверяет, являются ли эти два файла одним файлом Если они есть, мы предупреждаем пользователя и завершаем работу.

Ни POSIX, -efни BASH_SOURCEесть. Хотя -efподдерживается ksh, yash, zsh и Dash, BASH_SOURCEтребуется bash. Вzsh , однако, ${BASH_SOURCE[0]}может быть заменено на ${(%):-%N}.

John1024
источник
2
Просто echo "Usage: source \"$myfile\""
kubanczyk
6
@kubanczyk sourceне является портативным. Учитывая, что этот ответ зависит от bash, это не так уж плохо, но это хорошая привычка использовать портативный компьютер.
gronostaj
33

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

Edit: трюк, на который я только что наткнулся: сделать shebang любым исполняемым файлом, который не является интерпретатором оболочки, /bin/falseзаставляет скрипт возвращать ошибку (rc! = 0)

#!/bin/false "This script should be sourced in a shell, not executed directly"
xenoid
источник
7
Тем не менее, неисполняемый файл все еще может быть выполнен через, например bash somefile.sh...
twalberg
1
Если вы знаете, что можете выполнить его с помощью bash(vd perl, python, awk ...), то вы посмотрели исходный код и увидели комментарий, в котором говорится, что не следует делать это :)
xenoid
1
Скрипты Perl обычно называются somefile.plи Python как somefile.py, так что нет, я, вероятно, не читал комментарии (что это вообще bash somefile.shтакое ?) И короче chmod +x somefile.sh; ./somefile.sh
набирает,
Также некоторые Bourne-подобные оболочки, в том числе bash, сначала пытаются execveфайл, но если это не удается, они проверяют файл вручную и вручную интерпретируют #!и вызывают его через этот интерпретатор: это наследие со времен, когда #!было чисто пользовательское пространство соглашение, а не обрабатывается самим ядром. Я думаю bash , по крайней мере, не буду делать это для неисполняемых файлов, но я не знаю, переносимо ли ожидать такого нормального поведения от всех оболочек, из которых пользователь может вызывать скрипт.
mtraceur
«Если вы знаете, что можете выполнить его с помощью bash» Гм, нет, иногда пользователь просто не подозревает, что существуют другие оболочки, кроме тех, bash которые bash script.shмогут быть опасными.
Сергей Колодяжный
10

В этом сообщении о переполнении стека предлагается несколько методов , из которых мне больше всего понравился метод на основе функций, предложенный Wirawan Purwanto и mr.spuratic :

Самый надежный способ, как предлагает Wirawan Purwanto, это проверка FUNCNAME[1] внутри функции :

function mycheck() { declare -p FUNCNAME; }
mycheck

Затем:

$ bash sourcetest.sh
declare -a FUNCNAME='([0]="mycheck" [1]="main")'
$ . sourcetest.sh
declare -a FUNCNAME='([0]="mycheck" [1]="source")'

Это эквивалентно проверке вывода callerзначений mainи sourceразличению контекста вызывающего. Использование FUNCNAME[]экономит вам захват и анализ callerвыходных данных. Вы должны знать или рассчитать локальную глубину вызова, чтобы быть правильной, хотя. Случаи, подобные сценарию, полученному из другой функции или сценария, приведут к тому, что массив (стек) будет глубже. ( FUNCNAMEэто специальная переменная массива bash, она должна иметь смежные индексы, соответствующие стеку вызовов, если это не так unset).

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

function check()
{
    if [[ ${FUNCNAME[-1]} != "source" ]]   # bash 4.2+, use ${FUNCNAME[@]: -1} for older
    then
        printf "Usage: source %s\n" "$0"
        exit 1
    fi
}
check
Мур
источник
7

Предполагая, что выполнение сценария просто бесполезно, а не вредно, вы можете добавить

return 0 || printf 'Must be sourced, not executed\n' >&2

до конца сценария. returnснаружи функции имеет ненулевой код выхода, если файл не был получен.

chepner
источник
3
Обратите внимание, что это возвращает 0 статус выхода. Попробуйте эту похожую идиому, которую я использую вместо этого:return 2>/dev/null; echo "$0: This script must be sourced" 1>&2; exit 1
wjandrea
5

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

#!/bin/bash source-this-script
# ...

Сообщение об ошибке будет таким:

/bin/bash: source-this-script: No such file or directory

(Произвольное) имя аргумента уже дает строгую подсказку, но сообщение об ошибке все еще не ясно на 100%. Мы можем исправить это с помощью служебного скрипта, source-this-scriptкоторый находится где-то в вашем PATH:

#!/bin/sh
echo >&2 "This script must be sourced, not executed${1:+: }${1:-!}"
exit 1

Теперь сообщение об ошибке будет таким:

This script must be sourced, not executed: path/to/script.sh

Сравнение с другими подходами

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

Это не мешает явному вызову через bash path/to/script.sh, хотя (спасибо @muru!).

Инго Каркат
источник
1
Другим недостатком является то, что это не защитит от этого bash some/script.sh, что также проигнорирует Шебанг.
Муру
4
Вы можете сделать сообщение более ясным, сделав шебанг #!/bin/echo 'You must source this script!'или что-то в этом роде.
Крис
1
@Chris: Точно, но тогда я потерял бы определение типа файла (например, в Vim) и документацию о том, какой это диалект оболочки. Если вас это не волнует, ваше предложение действительно избавит вас от второго сценария!
Инго Каркат