Como definir um script de shell a ser originado não executado

40

Estou definindo um script de shell que um usuário deve sourceexecutar em vez de.

Existe uma maneira convencional ou inteligente de sugerir ao usuário que esse é o caso, por exemplo, através de uma extensão de arquivo?

Existe código shell que eu possa escrever no próprio arquivo, o que fará com que ele ecoe uma mensagem e saia se for executado em vez de originado, para que eu possa ajudar o usuário a evitar esse erro óbvio?

algal
fonte
11
Portanto, se o usuário estiver escrevendo um script de shell de uma linha x, que contém apenas o comando . your-script-to-be-sourced, tudo bem, mas se ele quiser executá- bash your-script-to-be-sourcedlo, deve ser proibido? Qual é o objetivo dessa restrição?
user1934428
8
@ user1934428 Claro. Isso é normal para um script que calcula várias envvariáveis ​​e as deixa como saída de script de fato . Um novato ficará preso por dias com o quebra-cabeça se você permitir que ele execute.
Kubanczyk

Respostas:

45

Supondo que você esteja executando o bash, coloque o código a seguir no início do script que você deseja obter, mas não executar:

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

Em bash, ${BASH_SOURCE[0]}conterá o nome do arquivo atual que o shell está lendo, independentemente de estar sendo originado ou executado.

Por outro lado, $0é o nome do arquivo atual que está sendo executado.

-eftesta se esses dois arquivos são o mesmo arquivo. Se estiverem, alertamos o usuário e saímos.

Nem -efnem BASH_SOURCEsão POSIX. Embora -efseja suportado por ksh, yash, zsh e Dash, BASH_SOURCErequer bash. Emzsh , no entanto, ${BASH_SOURCE[0]}poderia ser substituído por ${(%):-%N}.

John1024
fonte
2
Simplesmente echo "Usage: source \"$myfile\""
kubanczyk
6
@kubanczyk sourcenão é portátil. Considerando que esta resposta é específico do bash, não é tão ruim, mas é um bom hábito de usar o portátil.
gronostaj
33

Um arquivo não executável pode ser originado, mas não executado, portanto, como primeira linha de defesa, não definir o sinalizador executável deve ser uma boa dica ...

Edit: trick Eu apenas tropecei: faça o shebang ser qualquer executável que não seja um interpretador de shell, /bin/falsefaça com que o script retorne um erro (rc! = 0)

#!/bin/false "This script should be sourced in a shell, not executed directly"
xenoid
fonte
7
No entanto, um arquivo não-executável ainda pode ser executado através, por exemplo bash somefile.sh...
twalberg
11
Se você sabe que pode executá-lo com bash(vd perl, python, awk ...), então você olhou t a fonte e visto o comentário que diz para não fazê-lo :)
xenoid
11
Scripts Perl são geralmente chamado somefile.ple Python como somefile.py, de modo nenhum, eu provavelmente não leu os comentários (que são aqueles, mesmo, de qualquer maneira?) E bash somefile.shé mais curto para escrever do que chmod +x somefile.sh; ./somefile.sh...
twalberg
Além disso, alguns shells tipo Bourne, inclusive bash, primeiro tentarão execveum arquivo, mas se isso falhar, eles inspecionam o arquivo manualmente e manualmente interpretam o arquivo e o #!invocam por esse intérprete: esse é um legado dos dias em que #!era um espaço puramente para o usuário convenção, em vez de ser tratado pelo próprio kernel. Eu acho que bash , pelo menos, não fará isso com arquivos não executáveis, mas não sei se é portátil esperar um comportamento tão sensato de todos os shells que o usuário possa estar chamando o script.
Mtraceur
"Se você sabe que pode executá-lo com o bash" Hum, não, às vezes o usuário simplesmente não tem idéia de que existem outros shells bash que bash script.shpodem ser perigosos.
Sergiy Kolodyazhnyy
10

Existem vários métodos sugeridos neste post do Stack Overflow , dos quais gostei do baseado em função sugerido por Wirawan Purwanto e mr.spuratic :

A maneira mais robusta, como sugerido por Wirawan Purwanto, é verificar FUNCNAME[1] dentro de uma função :

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

Então:

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

Isso equivale a verificar a saída de caller, os valores maine sourcedistinguir o contexto do chamador. Usar FUNCNAME[]salva a captura e a análise de callersaída. Você precisa conhecer ou calcular a profundidade da chamada local para estar correto. Casos como um script originário de outra função ou script farão com que a matriz (pilha) seja mais profunda. ( FUNCNAMEé uma variável especial do array bash, ela deve ter índices contíguos correspondentes à pilha de chamadas, desde que nunca seja unset).

Então você pode adicionar ao início do script:

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
muru
fonte
7

Supondo que seja apenas inútil, e não prejudicial, executar o script, você pode adicionar

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

até o final do script. returnfora de uma função possui um código de saída diferente de zero, a menos que o arquivo esteja sendo originado.

chepner
fonte
3
Observe que isso retorna um status de saída 0. Tente este idioma semelhante que eu uso:return 2>/dev/null; echo "$0: This script must be sourced" 1>&2; exit 1
wjandrea 16/02
5

Quando você cria um script de shell, a linha shebang é ignorada. Ao inserir um shebang inválido, você pode alertar o usuário que o script foi executado erroneamente:

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

A mensagem de erro será esta:

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

O nome do argumento (arbitrário) já fornece uma dica forte, mas a mensagem de erro ainda não está 100% clara. Podemos corrigir isso com um script de utilitário source-this-scriptque é colocado em algum lugar no seu PATH:

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

Agora, a mensagem de erro será esta:

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

Comparação com outras abordagens

Comparado às outras respostas, essa abordagem requer apenas alterações mínimas em cada script (e ter uma linha shebang ajuda na detecção do tipo de arquivo nos editores e especifica o dialeto do shell script, para que haja benefícios). A desvantagem é uma mensagem de erro pouco clara ou a adição (única) de outro script de shell.

No entanto, não impede a chamada explícita bash path/to/script.sh(obrigado @muru!).

Ingo Karkat
fonte
11
Outra desvantagem é que isso não vai proteger contra bash some/script.sh, o que também ignoraria o shebang.
muru
4
Você pode deixar a mensagem mais clara fazendo o shebang #!/bin/echo 'You must source this script!'ou algo assim.
Chris
11
@ Chris: Certo, mas então eu perderia a detecção do tipo de arquivo (por exemplo, no Vim) e a documentação de qual shell dialeto é esse. Se você não se importa com isso, sua sugestão de fato se livraria do segundo script!
Ingo Karkat