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ść $0
zawiera 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 $0
i 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 $0
jest 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ą $0
jest ś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
$0
katalog nie zawierałby?
źródło
$0
jest coś innego niż skrypt, który odpowiada na tytuł pytania. Jednak interesują mnie również sytuacje, w których$0
sam skrypt jest, ale nie zawiera katalogu. W szczególności staram się zrozumieć komentarz podany w odpowiedzi na SO.Odpowiedzi:
W najczęstszych przypadkach
$0
będzie zawierać ścieżkę bezwzględną lub względną do skryptu, więc(zakładając, że istnieje
readlink
polecenie 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:
$0
dostajethe/script
.Kiedy biegniesz:
Twoja powłoka wykona:
Jeśli skrypt zawiera na przykład
#! /bin/sh -
she-bang, system przekształci go w:(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
x
i uruchomi zamiast niego:aby uniknąć warunków wyścigu (w takim przypadku
$0
bę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:
Twoja powłoka będzie patrzeć
the-script
w$PATH
.$PATH
może zawierać ścieżki bezwzględne lub względne (w tym pusty ciąg) do niektórych katalogów. Na przykład, jeśli$PATH
zawiera/bin:/usr/bin:
ithe-script
zostanie znaleziony w bieżącym katalogu, powłoka wykona:który stanie się:
Lub jeśli zostanie znaleziony w
/usr/bin
:We wszystkich powyższych przypadkach, z wyjątkiem przypadków narożników setuid,
$0
będzie zawierać ścieżkę (bezwzględną lub względną) do skryptu.Teraz skrypt można również wywołać jako:
Gdy
the-script
jak wyżej nie zawiera znaków ukośnika, zachowanie różni się nieznacznie w zależności od powłoki.Stare
ksh
implementacje 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$0
naprawdę nie zawierało ścieżki do skryptu, chyba że$PATH
wyszukiwanie rzeczywiście zdarzyło się znaleźćthe-script
w bieżącym katalogu.Nowsze AT&T
ksh
spróbuje zinterpretowaćthe-script
w bieżącym katalogu, jeśli jest to możliwe do odczytania. Jeśli nie, to szukałby czytelnego i wykonywalnegothe-script
w$PATH
.Sprawdza bowiem
bash
, czythe-script
znajduje się w bieżącym katalogu (i nie jest zepsutym dowiązaniem symbolicznym), a jeśli nie, poszukaj możliwego do odczytu (niekoniecznie wykonywalnego)the-script
w$PATH
.zsh
wsh
emulacji zrobiłoby to tak,bash
że jeślithe-script
jest zepsute dowiązanie symboliczne w bieżącym katalogu, nie szukałobythe-script
in$PATH
i zamiast tego zgłosiłoby błąd.Wszystkie inne muszle podobne do Bourne'a nie patrzą w
the-script
górę$PATH
.W każdym razie, jeśli okaże się, że
$0
nie zawiera/
i nie jest czytelny, prawdopodobnie został sprawdzony$PATH
. W związku z tym, ponieważ pliki w plikach$PATH
prawdopodobnie są wykonywalne, prawdopodobnie jest to bezpieczne przybliżenie docommand -v -- "$0"
znalezienia ich ścieżki (chociaż nie zadziałałoby, jeśli$0
tak 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ć:
(
""
dołączono do$PATH
zachowania końcowego pustego elementu za pomocą powłok, które$IFS
działają jak separator zamiast separatora ).Teraz są bardziej ezoteryczne sposoby na wywołanie skryptu. Można zrobić:
Lub:
W takim przypadku
$0
będzie to pierwszy argument (argv[0]
) otrzymany przez interpretera (powyżejthe-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,
$0
nie 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ć:
Wtedy, z wyjątkiem
zsh
(i niektórych starych implementacji powłoki Bourne'a),$0
byłobyblah
. Ponownie trudno jest znaleźć ścieżkę skryptu w tych powłokach.Lub:
itp.
Aby upewnić się, że masz do tego prawo
$progname
, możesz wyszukać w nim określony ciąg znaków, np .:Ale znowu nie sądzę, żeby było warto.
źródło
"-"
w powyższych przykładach. Z mojego doświadczeniaexec("the-script", ["the-script", "its", "args"])
wynikaexec("/the/interpreter", ["/the/interpreter", "the-script", "its", "args"])
, że oczywiście istnieje możliwość skorzystania z opcji tłumacza.#! /bin/sh -
to „zawsze używaj,cmd -- something
jeśli nie możesz zagwarantować, żesomething
nie zacznie się od-
” tutaj zastosowano dobre objaśnienie/bin/sh
(gdzie-
znacznik końca opcji jest bardziej przenośny niż--
),something
bę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-i
lub-s
za instancja./bin/sh
powinien 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./dev/fd/x
zaczyna się od/
, nie-
. Podstawowym celem jest jednak usunięcie warunków wyścigu między tymi dwomaexecve()
(pomiędzy,execve("the-script")
które podnoszą przywileje, a późniejexecve("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 ().Oto dwie sytuacje, w których katalog nie zostałby uwzględniony:
W obu przypadkach katalogiem bieżącym musiałby być katalog, w którym zlokalizowana była nazwa skryptu .
W pierwszym przypadku wartość parametru
$0
nadal może zostać przekazana,grep
ponieważ 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.źródło
Dowolny argument zero może być określony przy użyciu
-c
opcji dla większości (wszystkich?) Powłok. Na przykład:Od
man bash
(wybrany wyłącznie dlatego, że ma lepszy opis niż mójman sh
- użycie jest takie samo niezależnie):źródło
argv0
jest to pierwszy argument wiersza polecenia nieoperand.UWAGA: Inni już wyjaśnili mechanikę,
$0
wię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:
Utwórz katalog + plik:
Teraz zacznij się popisywać
readlink
:Dodatkowe oszustwo
Teraz, gdy zwracamy się
$0
za pośrednictwem spójnego wynikureadlink
, możemy użyć po prostu,dirname $(readlink -f $0)
aby uzyskać bezwzględną ścieżkę do skryptu lubbasename $(readlink -f $0)
uzyskać rzeczywistą nazwę skryptu.źródło
Moja
man
strona mówi: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ł, żesh ./somescript
był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
sh
shell
$ENV
W ten sposób
sh ./somescript.sh
różni się od tego,. ./somescript.sh
który działa w bieżącym środowisku i$0
jest już ustawiony.Możesz to sprawdzić, porównując
$0
z/proc/$$/status
.Dzięki za korektę, @toxalot. Nauczyłem się czegoś.
źródło
. ./myscript.sh
lubsource ./myscript.sh
), to$0
jest to powłoka. Ale jeśli jest przekazywany jako argument do powłoki (sh ./myscript.sh
), to$0
jest ścieżka do skryptu. Oczywiściesh
w moim systemie jest Bash. Więc nie wiem, czy to robi różnicę, czy nie.exec
i zamiast tego powinna ją pozyskiwać, ale z nią powinnaexec
../somescript
z bangline#!/bin/sh
, byłoby to równoważne z uruchomieniem/bin/sh ./somescript
. W przeciwnym razie nie mają znaczenia dla powłoki.Chociaż
$0
zawiera 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
Najdłuższą częścią
$0
tych dopasowań*/
będzie cały prefiks ścieżki, zwracający tylko nazwę skryptu.źródło
Do czegoś podobnego używam:
rPath
zawsze ma tę samą wartość.źródło