Jak zdefiniować skrypt powłoki, który ma być pozyskiwany, aby nie był uruchamiany

40

Definiuję skrypt powłoki, który użytkownik powinien sourceraczej wykonać niż wykonać.

Czy istnieje konwencjonalny lub inteligentny sposób, aby poinformować użytkownika, że ​​tak jest, na przykład poprzez rozszerzenie pliku?

Czy istnieje kod powłoki, który mogę napisać w samym pliku, co spowoduje, że wyświetli komunikat i zakończy działanie, jeśli zostanie wykonany zamiast źródła, aby pomóc użytkownikowi uniknąć tego oczywistego błędu?

glon
źródło
1
Tak więc, jeśli użytkownik pisze jednowierszowy skrypt powłoki x, który zawiera tylko polecenie . your-script-to-be-sourced, jest w porządku, ale jeśli chce go wykonać bash your-script-to-be-sourced, powinien być zabroniony? Jaki jest sens tego ograniczenia?
user1934428
8
@ user1934428 Oczywiście. Jest to normalne dla skryptu, który oblicza liczbę envzmiennych i pozostawia je jako de facto wyjście skryptu. Nowicjusz utknie na kilka dni z układanką, jeśli pozwolisz jej wykonać.
kubańczyk

Odpowiedzi:

45

Zakładając, że uruchamiasz bash, umieść następujący kod na początku skryptu, który chcesz pobrać, ale nie wykonać:

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

Pod bash ${BASH_SOURCE[0]}będzie zawierać nazwę bieżącego pliku, który odczytuje powłoka, niezależnie od tego, czy jest pobierana czy wykonywana.

Natomiast $0nazwa bieżącego pliku, który jest wykonywany.

-efsprawdza, czy te dwa pliki są tym samym plikiem. Jeśli tak, ostrzegamy użytkownika i wychodzimy.

POSIX też -efnie BASH_SOURCEjest. Chociaż -efjest obsługiwany przez ksh, yash, zsh i Dash, BASH_SOURCEwymaga bash. Wzsh jednakże ${BASH_SOURCE[0]}można zastąpić ${(%):-%N}.

John1024
źródło
2
Po prostu echo "Usage: source \"$myfile\""
kubańczyk
6
@kubanczyk sourcenie jest przenośny. Biorąc pod uwagę, że ta odpowiedź jest specyficzna dla bash, nie jest tak źle, ale dobrym zwyczajem jest korzystanie z przenośnego.
gronostaj
33

Plik niewykonywalny może być pozyskiwany, ale nie wykonywany, więc jako pierwsza linia obrony nie ustawianie flagi wykonywalnej powinno być dobrą wskazówką ...

Edycja: trik, na który się natknąłem: spraw, aby shebang był dowolnym plikiem wykonywalnym, który nie jest interpreterem powłoki, /bin/falsepowoduje , że skrypt zwraca błąd (rc! = 0)

#!/bin/false "This script should be sourced in a shell, not executed directly"
ksenoid
źródło
7
Jednak plik, który nie jest wykonywalny, może być nadal wykonany np. Za pośrednictwem bash somefile.sh...
twalberg
1
Jeśli wiesz, że możesz to wykonać za pomocą bash(vd perl, python, awk ...), to poszukałeś źródła i zobaczyłeś komentarz, który mówi, aby tego nie robić :)
xenoid
1
Skrypty Perla są ogólnie nazywane somefile.pli Python somefile.py, więc nie, prawdopodobnie nie przeczytałem komentarzy (a bash somefile.shchmod +x somefile.sh; ./somefile.sh
właściwie
Również niektóre powłoki podobne do Bourne'a, w tym bash, najpierw spróbują execveutworzyć plik, ale jeśli to się nie powiedzie, sprawdzają plik ręcznie i ręcznie interpretują #!i wywołują go przez tego interpretera: jest to dziedzictwo z czasów, gdy #!była to czysto przestrzeń użytkownika konwencja, zamiast być obsługiwanym przez samo jądro. I pomyśleć bash , co najmniej, będzie nie to zrobić dla plików niewykonywalnych, ale nie wiem, czy to przenośny oczekiwać takiego zachowania sane ze wszystkich skorup, że użytkownik może być wywoływania skryptu z.
mtraceur
„Jeśli wiesz, że możesz to zrobić za pomocą bash” Hm, nie, czasami użytkownik po prostu nie ma pojęcia, że ​​istnieją inne powłoki niż te, bash które bash script.shmogą być niebezpieczne.
Sergiy Kolodyazhnyy
10

W tym wpisie przepełnienia stosu zasugerowano kilka metod , z których najbardziej podobała mi się ta oparta na funkcjach, sugerowana przez Wirawana Purwanto i mr.spuratic najlepiej:

Najbardziej niezawodny sposób, jak sugeruje Wirawan Purwanto, to sprawdzenie FUNCNAME[1] w ramach funkcji :

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

Następnie:

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

Jest to równoważne sprawdzeniu danych wyjściowych caller, wartości maini sourcerozróżnieniu kontekstu osoby dzwoniącej. Użycie FUNCNAME[]oszczędza przechwytywanie i analizowanie callerwyników. Musisz jednak znać lub obliczyć głębokość lokalnego połączenia, aby być poprawnym. Przypadki takie jak skrypt pochodzący z innej funkcji lub skryptu powodują, że tablica (stos) jest głębsza. ( FUNCNAMEjest specjalną zmienną tablicową bash, powinna mieć ciągłe indeksy odpowiadające stosowi wywołań, o ile nie jest to nigdy unset).

Możesz więc dodać na początku skryptu:

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
źródło
7

Zakładając, że wykonanie skryptu jest po prostu bezużyteczne, a nie szkodliwe, możesz dodać

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

do końca skryptu. returnpoza funkcją ma niezerowy kod wyjścia, chyba że plik jest pobierany.

chepner
źródło
3
Zauważ, że to zwraca status wyjścia 0. Spróbuj tego podobnego idiomu, którego używam zamiast tego:return 2>/dev/null; echo "$0: This script must be sourced" 1>&2; exit 1
wjandrea
5

Gdy pozyskujesz skrypt powłoki, linia shebang jest ignorowana. Wprowadzając nieprawidłowy shebang, możesz ostrzec użytkownika, że ​​skrypt został błędnie wykonany:

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

Komunikat o błędzie będzie następujący:

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

(Dowolna) nazwa argumentu już zapewnia silną wskazówkę, ale komunikat o błędzie nadal nie jest w 100% wyraźny. Możemy to naprawić za pomocą skryptu narzędzia source-this-scriptumieszczonego gdzieś w twoim PATH:

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

Teraz komunikat o błędzie będzie następujący:

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

Porównanie z innymi podejściami

W porównaniu z innymi odpowiedziami to podejście wymaga jedynie minimalnych zmian w każdym skrypcie (a posiadanie linii shebang pomaga w wykrywaniu typów plików w edytorach i określa dialekt skryptu powłoki, więc są nawet korzyści). Minusem jest nieco niejasny komunikat o błędzie lub (jednorazowe) dodanie innego skryptu powłoki.

Nie zapobiega to bash path/to/script.shjednak jawnemu wywoływaniu za pośrednictwem (dzięki @muru!).

Ingo Karkat
źródło
1
Kolejnym minusem jest to, że nie ochroni to przed tym bash some/script.sh, co również zignoruje snbang.
muru
4
Możesz sprawić, by przekaz był bardziej przejrzysty, poprzez wykonanie shebang #!/bin/echo 'You must source this script!'lub coś w tym rodzaju.
Chris
1
@Chris: Racja, ale wtedy straciłbym wykrywanie typu pliku (np. W Vimie) i dokumentację, który to dialekt powłoki. Jeśli nie przejmujesz się nimi, twoja sugestia rzeczywiście pozbędzie się drugiego skryptu!
Ingo Karkat