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


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}.


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"

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снаружи функции имеет ненулевой код выхода, если файл не был получен.


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) и документацию о том, какой это диалект оболочки. Если вас это не волнует, ваше предложение действительно избавит вас от второго сценария!
— Инго Каркат
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.