Czy 0 USD zawsze będzie zawierać ścieżkę do skryptu?


11

Chcę zastąpić bieżący skrypt, aby móc wydrukować pomoc i informacje o wersji z sekcji komentarzy u góry.

Myślałem o czymś takim:

grep '^#h ' -- "$0" | sed -e 's/#h //'

Ale potem zastanawiałem się, co by się stało, gdyby skrypt został umieszczony w katalogu, który był w PATH i wywołany bez wyraźnego określenia katalogu.

Szukałem wyjaśnienia zmiennych specjalnych i znalazłem następujące opisy $0:

  • nazwa bieżącej powłoki lub programu

  • nazwa pliku bieżącego skryptu

  • nazwa samego skryptu

  • polecenie, jak zostało uruchomione

Żadne z nich nie wyjaśnia, czy wartość $0zawiera katalog, jeśli skrypt zostałby wywołany bez niego. Ten ostatni sugeruje mi, że nie.

Testowanie w moim systemie (Bash 4.1)

Utworzyłem plik wykonywalny w / usr / local / bin o nazwie scriptname z jedną linią echo $0i wywołałem go z różnych lokalizacji.

Oto moje wyniki:

> cd /usr/local/bin/test
> ../scriptname
../scriptname

> cd /usr/local/bin
> ./scriptname
./scriptname

> cd /usr/local
> bin/scriptname
bin/scriptname

> cd /tmp
> /usr/local/bin/scriptname
/usr/local/bin/scriptname

> scriptname
/usr/local/bin/scriptname

W tych testach wartość parametru $0jest zawsze dokładnie taka, jak skrypt został wywołany, z wyjątkiem sytuacji, gdy jest on wywoływany bez komponentu ścieżki. W takim przypadku wartością $0jest ścieżka bezwzględna . Wygląda na to, że przejście do innego polecenia byłoby bezpieczne.

Ale potem natknąłem się na komentarz na temat przepełnienia stosu, który mnie zdezorientował. Odpowiedź sugeruje użycie, $(dirname $0)aby uzyskać katalog bieżącego skryptu. W komentarzu (ocenianym 7 razy) napisano: „to nie zadziała, jeśli skrypt jest na twojej drodze”.

pytania

  • Czy ten komentarz jest poprawny?
  • Czy zachowanie jest inne w innych systemach?
  • Czy są sytuacje, w których $0katalog nie zawierałby?

Ludzie odpowiadali na sytuacje, w których $0jest coś innego niż skrypt, który odpowiada na tytuł pytania. Jednak interesują mnie również sytuacje, w których $0sam skrypt jest, ale nie zawiera katalogu. W szczególności staram się zrozumieć komentarz podany w odpowiedzi na SO.
— toxalot

Odpowiedzi:


17

W najczęstszych przypadkach $0będzie zawierać ścieżkę bezwzględną lub względną do skryptu, więc

script_path=$(readlink -e -- "$0")

(zakładając, że istnieje readlinkpolecenie i obsługuje -e) ogólnie jest wystarczającym sposobem na uzyskanie kanonicznej bezwzględnej ścieżki do skryptu.

$0 jest przypisywany z argumentu określającego skrypt przekazany interpreterowi.

Na przykład w:

the-shell -shell-options the/script its args

$0dostaje the/script.

Kiedy biegniesz:

the/script its args

Twoja powłoka wykona:

exec("the/script", ["the/script", "its", "args"])

Jeśli skrypt zawiera na przykład #! /bin/sh -she-bang, system przekształci go w:

exec("/bin/sh", ["/bin/sh" or "the/script", "-", "the/script", "its", "args"])

(jeśli nie zawiera she-bang, lub bardziej ogólnie, jeśli system zwraca błąd ENOEXEC, to jego powłoka zrobi to samo)

Istnieje wyjątek dla skryptów setuid / setgid w niektórych systemach, gdzie system otworzy skrypt na niektórych fd xi uruchomi zamiast niego:

exec("/bin/sh", ["/bin/sh" or "the/script", "-", "/dev/fd/x", "its", "args"])

aby uniknąć warunków wyścigu (w takim przypadku $0będzie to zawierać /dev/fd/x).

Teraz możesz argumentować, że /dev/fd/x jest to ścieżka do tego skryptu. Pamiętaj jednak, że jeśli czytasz z $0, złamiesz skrypt, gdy zużyjesz dane wejściowe.

Istnieje różnica, jeśli wywołana nazwa polecenia skryptu nie zawiera ukośnika. W:

the-script its args

Twoja powłoka będzie patrzeć the-scriptw $PATH. $PATHmoże zawierać ścieżki bezwzględne lub względne (w tym pusty ciąg) do niektórych katalogów. Na przykład, jeśli $PATHzawiera /bin:/usr/bin:i the-scriptzostanie znaleziony w bieżącym katalogu, powłoka wykona:

exec("the-script", ["the-script", "its", "args"])

który stanie się:

exec("/bin/sh", ["/bin/sh" or "the-script", "-", "the-script", "its", "args"]

Lub jeśli zostanie znaleziony w /usr/bin:

exec("/usr/bin/the-script", ["the-script", "its", "args"])
exec("/bin/sh", ["/bin/sh" or "the-script" or "/usr/bin/the-script",
     "-", "/usr/bin/the-script", "its", "args")

We wszystkich powyższych przypadkach, z wyjątkiem przypadków narożników setuid, $0będzie zawierać ścieżkę (bezwzględną lub względną) do skryptu.

Teraz skrypt można również wywołać jako:

the-interpreter the-script its args

Gdy the-scriptjak wyżej nie zawiera znaków ukośnika, zachowanie różni się nieznacznie w zależności od powłoki.

Stare kshimplementacje AT&T faktycznie szukały skryptu bezwarunkowo w $PATH(co było w rzeczywistości błędem i luką w zabezpieczeniach dla skryptów setuid), więc tak $0naprawdę nie zawierało ścieżki do skryptu, chyba że $PATHwyszukiwanie rzeczywiście zdarzyło się znaleźć the-scriptw bieżącym katalogu.

Nowsze AT&T kshspróbuje zinterpretować the-scriptw bieżącym katalogu, jeśli jest to możliwe do odczytania. Jeśli nie, to szukałby czytelnego i wykonywalnego the-script w $PATH.

Sprawdza bowiem bash, czy the-scriptznajduje się w bieżącym katalogu (i nie jest zepsutym dowiązaniem symbolicznym), a jeśli nie, poszukaj możliwego do odczytu (niekoniecznie wykonywalnego) the-scriptw $PATH.

zshw shemulacji zrobiłoby to tak, bashże jeśli the-scriptjest zepsute dowiązanie symboliczne w bieżącym katalogu, nie szukałoby the-scriptin $PATHi zamiast tego zgłosiłoby błąd.

Wszystkie inne muszle podobne do Bourne'a nie patrzą w the-scriptgórę $PATH.

W każdym razie, jeśli okaże się, że $0nie zawiera /i nie jest czytelny, prawdopodobnie został sprawdzony $PATH. W związku z tym, ponieważ pliki w plikach $PATHprawdopodobnie są wykonywalne, prawdopodobnie jest to bezpieczne przybliżenie do command -v -- "$0"znalezienia ich ścieżki (chociaż nie zadziałałoby, jeśli $0tak się składa, że ​​jest to nazwa wbudowanej powłoki lub słowa kluczowego (w większości powłok)).

Więc jeśli naprawdę chcesz ukryć sprawę, możesz ją napisać:

progname=$0
[ -r "$progname" ] || progname=$(
    IFS=:; set -f
    for i in ${PATH-$(getconf PATH)}""; do
      case $i in
        "") p=$progname;;
        */) p=$i$progname;;
        *) p=$i/$progname
      esac
      [ -r "$p" ] && exec printf '%s\n' "$p"
    done
    exit 1
  ) && progname=$(readlink -e -- "$progname") ||
  progname=unknown

( ""dołączono do $PATHzachowania końcowego pustego elementu za pomocą powłok, które $IFSdziałają jak separator zamiast separatora ).

Teraz są bardziej ezoteryczne sposoby na wywołanie skryptu. Można zrobić:

the-shell < the-script

Lub:

cat the-script | the-shell

W takim przypadku $0będzie to pierwszy argument ( argv[0]) otrzymany przez interpretera (powyżej the-shell, ale może to być cokolwiek, ale ogólnie albo basename, albo jedna ścieżka do tego interpretera).

Wykrywanie, że jesteś w takiej sytuacji na podstawie wartości, $0nie jest wiarygodne. Możesz spojrzeć na wynik, ps -o args= -p "$$"aby uzyskać wskazówkę. W przypadku potoku nie ma prawdziwego sposobu na powrót do ścieżki do skryptu.

Można również zrobić:

the-shell -c '. the-script' blah blih

Wtedy, z wyjątkiem zsh(i niektórych starych implementacji powłoki Bourne'a), $0byłoby blah. Ponownie trudno jest znaleźć ścieżkę skryptu w tych powłokach.

Lub:

the-shell -c "$(cat the-script)" blah blih

itp.

Aby upewnić się, że masz do tego prawo $progname, możesz wyszukać w nim określony ciąg znaków, np .:

progname=$0
[ -r "$progname" ] || progname=$(
    IFS=:; set -f
    for i in ${PATH-$(getconf PATH)}:; do
      case $i in
        "") p=$progname;;
        */) p=$i$progname;;
        *) p=$i/$progname
      esac
      [ -r "$p" ] && exec printf '%s\n' "$p"
    done
    exit 1
  ) && progname=$(readlink -e -- "$progname") ||
  progname=unknown

[ -f "$progname" ] && grep -q 7YQLVVD3UIUDTA32LSE8U9UOHH < "$progname" ||
  progname=unknown

Ale znowu nie sądzę, żeby było warto.


Stéphane, nie rozumiem twojego użycia "-"w powyższych przykładach. Z mojego doświadczenia exec("the-script", ["the-script", "its", "args"])wynika exec("/the/interpreter", ["/the/interpreter", "the-script", "its", "args"]), że oczywiście istnieje możliwość skorzystania z opcji tłumacza.
— jrw32982 obsługuje Monikę

@ jrw32982, #! /bin/sh -to „zawsze używaj, cmd -- somethingjeśli nie możesz zagwarantować, że somethingnie zacznie się od -” tutaj zastosowano dobre objaśnienie /bin/sh(gdzie -znacznik końca opcji jest bardziej przenośny niż --), somethingbędąc ścieżką / nazwą scenariusz. Jeśli nie użyć dla skryptów setuid (w systemach, które je obsługują, ale nie z katalogu / dev / fd / x metoda wspomniano w odpowiedzi), a następnie można uzyskać powłokę roota poprzez utworzenie dowiązania do skryptu o nazwie -ilub -sza instancja.
— Stéphane Chazelas

Dzięki, Stéphane. Brakowało mi pojedynczego łącznika w twojej przykładowej linii shebang. Musiałem szukać tam, gdzie udokumentowano, że pojedynczy myślnik jest równoważny podwójnemu myślnikowi pubs.opengroup.org/onlinepubs/9699919799/utilities/sh.html .
— jrw32982 obsługuje Monikę

Zbyt łatwo jest zapomnieć o łączniku pojedynczym / podwójnym w linii shebang dla skryptu setuid; jakoś system powinien się tym zająć. Zablokuj skrypty setuid całkowicie lub w jakiś sposób /bin/shpowinien wyłączyć własne przetwarzanie opcji, jeśli może wykryć, że działa setuid. Nie widzę, jak samo użycie / dev / fd / x to naprawia. Myślę, że nadal potrzebujesz pojedynczego / podwójnego łącznika.
— jrw32982 obsługuje Monikę

@ jrw32982, /dev/fd/xzaczyna się od /, nie -. Podstawowym celem jest jednak usunięcie warunków wyścigu między tymi dwoma execve()(pomiędzy, execve("the-script")które podnoszą przywileje, a później execve("interpreter", "thescript")gdzieinterpreter skrypt otwiera się później (co równie dobrze może zostać zastąpione przez dowiązanie symboliczne do czegoś innego w międzyczasie). skrypty suid poprawnie wykonują execve("interpreter", "/dev/fd/n")zamiast tego, gdzie n zostało otwarte jako część pierwszego execve ().
— Stéphane Chazelas

6

Oto dwie sytuacje, w których katalog nie zostałby uwzględniony:

> bash scriptname
scriptname

> bash <scriptname
bash

W obu przypadkach katalogiem bieżącym musiałby być katalog, w którym zlokalizowana była nazwa skryptu .

W pierwszym przypadku wartość parametru $0nadal może zostać przekazana, grepponieważ zakłada, że ​​argument PLIK jest względem bieżącego katalogu.

W drugim przypadku, jeśli informacje pomocy i wersji są drukowane tylko w odpowiedzi na konkretną opcję wiersza poleceń, nie powinno to stanowić problemu. Nie jestem pewien, dlaczego ktokolwiek miałby wywoływać skrypt w ten sposób, aby wydrukować pomoc lub informacje o wersji.

Ostrzeżenia

  • Jeśli skrypt zmieni bieżący katalog, nie będziesz chciał używać ścieżek względnych.

  • Jeśli skrypt jest pozyskiwany, wartość $0 zwykle będzie skrypt wywołujący, a nie skrypt źródłowy.


3

Dowolny argument zero może być określony przy użyciu -copcji dla większości (wszystkich?) Powłok. Na przykład:

sh -c 'echo $0' argv0

Od man bash(wybrany wyłącznie dlatego, że ma lepszy opis niż mój man sh- użycie jest takie samo niezależnie):

-do

Jeśli opcja -c jest obecna, polecenia są odczytywane z pierwszego nie-opcji argumentu łańcuch_w poleceniu. Jeśli po łańcuchu komend występują argumenty, są one przypisywane do parametrów pozycyjnych, zaczynając od $ 0.


Myślę, że to działa, ponieważ -c „polecenie” to operandy i argv0jest to pierwszy argument wiersza polecenia nieoperand.
— mikeserv

@mike poprawne, zaktualizowałem fragment kodu człowieka.
— Graeme

3

UWAGA: Inni już wyjaśnili mechanikę, $0więc pominę wszystko.

Generalnie wykonuję krok po kroku cały problem i po prostu używam polecenia readlink -f $0 . To zawsze da ci pełną ścieżkę tego, co podasz jako argument.

Przykłady

Powiedz, że jestem tu na początek:

$ pwd
/home/saml/tst/119929/adir

Utwórz katalog + plik:

$ mkdir adir
$ touch afile
$ cd adir/

Teraz zacznij się popisywać readlink:

$ readlink -f ../adir
/home/saml/tst/119929/adir

$ readlink -f ../
/home/saml/tst/119929

$ readlink -f ../afile 
/home/saml/tst/119929/afile

$ readlink -f .
/home/saml/tst/119929/adir

Dodatkowe oszustwo

Teraz, gdy zwracamy się $0za pośrednictwem spójnego wyniku readlink, możemy użyć po prostu, dirname $(readlink -f $0)aby uzyskać bezwzględną ścieżkę do skryptu lub basename $(readlink -f $0)uzyskać rzeczywistą nazwę skryptu.


0

Moja manstrona mówi:

$0: Rozwija się do namez shelllub shell script.

Wygląda na to, że przekłada się to argv[0]na bieżącą powłokę - lub na pierwszy argument nieoperandolowy wiersza poleceń, który aktualnie interpretowana powłoka jest podawana po wywołaniu. I wcześniej stwierdził, że sh ./somescriptbyłoby jej ścieżce zmiennej do ale to było błędne, ponieważ jest to nowy proces z własnej i wywołany z nowym .$0 $ENV shshell$ENV

W ten sposób sh ./somescript.shróżni się od tego, . ./somescript.shktóry działa w bieżącym środowisku i $0jest już ustawiony.

Możesz to sprawdzić, porównując $0z /proc/$$/status.

echo 'script="/proc/$$/status"
    echo $0
    cat "$script"' \
    > ./script.sh
sh ./script.sh ; . ./script.sh

Dzięki za korektę, @toxalot. Nauczyłem się czegoś.


W moim systemie, jeśli jest to źródło ( . ./myscript.shlub source ./myscript.sh), to $0jest to powłoka. Ale jeśli jest przekazywany jako argument do powłoki ( sh ./myscript.sh), to $0jest ścieżka do skryptu. Oczywiście shw moim systemie jest Bash. Więc nie wiem, czy to robi różnicę, czy nie.
— toxalot

Czy to hashbang? Myślę, że różnica ma znaczenie - i mogę to dodać - bez niej nie powinna execi zamiast tego powinna ją pozyskiwać, ale z nią powinna exec.
— mikeserv

Z haszyszem lub bez niego uzyskuję te same wyniki. Z wykonalnością lub bez niej uzyskuję te same wyniki.
— toxalot

Ja też! Myślę, że to dlatego, że jest to wbudowana powłoka. Sprawdzam ...
— Mikeserv

Linie Bang są interpretowane przez jądro. Jeśli wykonasz ./somescriptz bangline #!/bin/sh, byłoby to równoważne z uruchomieniem /bin/sh ./somescript. W przeciwnym razie nie mają znaczenia dla powłoki.
— Graeme

0

Chcę zastąpić bieżący skrypt, aby móc wydrukować pomoc i informacje o wersji z sekcji komentarzy u góry.

Chociaż $0zawiera nazwę skryptu, może zawierać prefiksową ścieżkę na podstawie sposobu, w jaki skrypt jest wywoływany, zawsze ${0##*/}drukowałem nazwę skryptu na wyjściu pomocy, która usuwa wszelką wiodącą ścieżkę $0.

Zaczerpnięte z przewodnika Advanced Bash Scripting - Zmiana parametrów w rozdziale 10.2

${var#Pattern}

Usuń z $varnajkrótszej części $Patternpasującej do przedniej części $var.

${var##Pattern}

Usuń z $varnajdłuższej części $Patternpasującej do przedniej części $var.

Najdłuższą częścią $0tych dopasowań */będzie cały prefiks ścieżki, zwracający tylko nazwę skryptu.


Tak, robię to również dla nazwy skryptu używanego w komunikacie pomocy. Ale mówię o pomijaniu skryptu, więc muszę mieć przynajmniej ścieżkę względną. Chcę umieścić komunikat pomocy u góry w komentarzach i nie muszę powtarzać tego komunikatu później podczas drukowania.
— toxalot

0

Do czegoś podobnego używam:

rPath="$(dirname $(realpath $0))"
echo $rPath 

rPath=$(dirname $(readlink -e -- "$0"))
echo $rPath 

rPath zawsze ma tę samą wartość.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.