Skrypt powłoki uniksowej dowiedzieć się, w którym katalogu znajduje się plik skryptu?
510
Zasadniczo muszę uruchomić skrypt ze ścieżkami związanymi z lokalizacją pliku skryptu powłoki. Jak mogę zmienić bieżący katalog na ten sam katalog, w którym znajduje się plik skryptu?
Czy to naprawdę duplikat? To pytanie dotyczy „skryptu powłoki unix”, drugie dotyczy Bash.
michaeljt
2
@BoltClock: To pytanie zostało nieprawidłowo zamknięte. Połączone pytanie dotyczy Bash. To pytanie dotyczy programowania powłoki w systemie Unix. Zauważ, że akceptowane odpowiedzi są zupełnie inne!
Dietrich Epp,
@Dietrich Epp: Masz rację. Wygląda na to, że wybór akceptowanej odpowiedzi przez pytającego i dodanie tagu [bash] (prawdopodobnie w odpowiedzi na to) doprowadziło mnie do oznaczenia pytania jako duplikatu w odpowiedzi na flagę.
To nie działa, jeśli skrypt został wywołany przez dowiązanie symboliczne w innym katalogu. Aby wykonać tę pracę, musisz również użyć readlink(patrz odpowiedź al poniżej)
AndrewR
44
W bash to bezpieczniej jest używać $BASH_SOURCEw miejsce $0, ponieważ $0nie zawsze zawierać ścieżkę do skryptu powołano się, na przykład podczas „pozyskiwania” skryptu.
mklement0
2
$BASH_SOURCEjest specyficzne dla Bash, pytanie dotyczy ogólnie skryptu powłoki.
Ha-Duong Nguyen
7
@auraham: CUR_PATH=$(pwd)lub pwdzwróć bieżący katalog (który nie musi być katalogiem macierzystym skryptów)!
Andreas Dietrich,
5
Wypróbowałem zalecaną metodę @ mklement0 $BASH_SOURCE, która zwraca to, czego potrzebowałem. Mój skrypt jest wywoływany z innego skryptu i $0zwraca, .podczas gdy $BASH_SOURCEzwraca właściwy podkatalog (w moim przypadku scripts).
David Rissato Cruz,
401
Oryginalny post zawiera rozwiązanie (zignoruj odpowiedzi, nie dodają niczego przydatnego). Interesującą pracę wykonuje wspomniane polecenie unix readlinkz opcją -f. Działa, gdy skrypt jest wywoływany zarówno przez ścieżkę bezwzględną, jak i względną.
Dla bash, sh, ksh:
#!/bin/bash # Absolute path to this script, e.g. /home/user/bin/foo.sh
SCRIPT=$(readlink -f "$0")# Absolute path this script is in, thus /home/user/bin
SCRIPTPATH=$(dirname "$SCRIPT")
echo $SCRIPTPATH
Dla tcsh, csh:
#!/bin/tcsh# Absolute path to this script, e.g. /home/user/bin/foo.cshset SCRIPT=`readlink -f "$0"`# Absolute path this script is in, thus /home/user/binset SCRIPTPATH=`dirname "$SCRIPT"`
echo $SCRIPTPATH
Aby wyjaśnić komentarz @ Ergwun: -fnie jest w ogóle obsługiwany w OS X (od Lion); tam możesz albo zrezygnować -fz rozwiązania co najwyżej jednego poziomu pośredni, np. pushd "$(dirname "$(readlink "$BASH_SOURCE" || echo "$BASH_SOURCE")")"albo możesz rzucić swój własny rekursywny skrypt śledzący dowiązanie symboliczne, jak pokazano w łączonym poście.
mklement0
2
Nadal nie rozumiem, dlaczego PO potrzebowałaby absolutnej ścieżki. Raportowanie „.” powinien działać dobrze, jeśli chcesz uzyskać dostęp do plików względem ścieżki skryptów i wywołałeś skrypt jak ./myscript.sh
Stefan Haberl
10
@StefanHaberl Myślę, że byłby problem, gdybyś uruchomił skrypt, podczas gdy twój obecny katalog roboczy był inny niż lokalizacja skryptu (np. sh /some/other/directory/script.sh)W tym przypadku .byłby twój pwd, a nie/some/other/directory
Jon z
51
Powiedział to wcześniejszy komentarz do odpowiedzi, ale łatwo można przeoczyć wszystkie pozostałe odpowiedzi.
Podczas korzystania z bash:
echo this file:"$BASH_SOURCE"
echo this dir:"$(dirname "$BASH_SOURCE")"
Tylko ten działa ze skryptem w ścieżce środowiska, najczęściej wybierane nie działają. Dziękuję Ci!
lipiec
Zamiast tego należy używać dirname "$BASH_SOURCE"do obsługi spacji w $ BASH_SOURCE.
Mygod
1
Bardziej wyraźnym sposobem wydrukowania katalogu byłoby, zgodnie z narzędziem ShellCheck,: „$ (dirname" $ {BASH_SOURCE {0}} ")", ponieważ BASH_SOURCE jest tablicą i bez indeksu dolnego domyślnie brany jest pierwszy element .
Adrian M.
1
@AdrianM. , chcesz nawiasów, a nie nawiasów klamrowych, dla indeksu:"$(dirname "${BASH_SOURCE[0]}")"
Hashbrown
Nie ma dla mnie dobrego. Drukuje tylko kropkę (prawdopodobnie w bieżącym katalogu)
Ten skrypt powinien wydrukować katalog, w którym się znajdujesz, a następnie katalog, w którym znajduje się skrypt. Na przykład, wywołując go /z poziomu skryptu /home/mez/, wyświetla dane wyjściowe
//home/mez
Pamiętaj, że przypisując zmienne z danych wyjściowych polecenia, zawiń je $(i )- inaczej nie uzyskasz żądanego wyniku.
To nie zadziała, gdy wywołam skrypt z bieżącego katalogu.
Eric Wang,
1
@EricWang jesteś zawsze w bieżącym katalogu.
ctrl-alt-delor
Dla mnie $ current_dir jest rzeczywiście ścieżką, z której wywołuję skrypt. Jednak $ script_dir nie jest katalogiem skryptu, to tylko kropka.
Michael
31
Jeśli używasz bash ....
#!/bin/bash
pushd $(dirname "${0}")>/dev/null
basedir=$(pwd -L)# Use "pwd -P" for the path without links. man bash for more info.
popd >/dev/null
echo "${basedir}"
Można wymienić pushd/ popdz cd $(dirname "${0}")i cd -aby to działało na innych pocisków, jeśli mają pwd -L.
docwhat
dlaczego miałbyś używać tutaj pushd i popd?
qodeninja
1
Więc nie muszę przechowywać oryginalnego katalogu w zmiennej. Jest to wzór, którego często używam w funkcjach i tym podobnych. Gniazduje się naprawdę dobrze, co jest dobre.
docwhat
Nadal jest przechowywany w pamięci - w zmiennej - niezależnie od tego, czy zmienna jest przywoływana w skrypcie, czy nie. Ponadto uważam, że koszt wykonania pushd i popd znacznie przewyższa oszczędności wynikające z braku tworzenia lokalnej zmiennej Bash w skrypcie, zarówno w cyklach procesora, jak i czytelności.
ingyhere
20
Jak sugeruje Marko:
BASEDIR=$(dirname $0)
echo $BASEDIR
Działa to, chyba że wykonasz skrypt z tego samego katalogu, w którym znajduje się skrypt, w którym to przypadku otrzymasz wartość „.”
Możesz teraz używać zmiennej current_dir w całym skrypcie, aby odwoływać się do katalogu skryptów. Jednak może to nadal powodować problem z dowiązaniem symbolicznym.
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}")" && pwd )"
Jednowierszowy, który da ci pełną nazwę katalogu skryptu, bez względu na to, skąd jest wywoływany.
Aby zrozumieć, jak to działa, możesz wykonać następujący skrypt:
#!/bin/bash
SOURCE="${BASH_SOURCE[0]}"while[-h "$SOURCE"];do# resolve $SOURCE until the file is no longer a symlink
TARGET="$(readlink "$SOURCE")"if[[ $TARGET ==/*]];then
echo "SOURCE '$SOURCE' is an absolute symlink to '$TARGET'"
SOURCE="$TARGET"else
DIR="$( dirname "$SOURCE" )"
echo "SOURCE '$SOURCE' is a relative symlink to '$TARGET' (relative to '$DIR')"
SOURCE="$DIR/$TARGET"# if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was locatedfidone
echo "SOURCE is '$SOURCE'"
RDIR="$( dirname "$SOURCE" )"
DIR="$( cd -P "$( dirname "$SOURCE")" && pwd )"if["$DIR"!="$RDIR"];then
echo "DIR '$RDIR' resolves to '$DIR'"fi
echo "DIR is '$DIR'"
@Der_Meister - proszę podać bardziej szczegółowe informacje. Lub napisz (i prawdopodobnie zaszyfruj) e-mail do jasan na jasan.tk
Ján Sáreník
2
ln -s / home / der / 1 / test / home / der / 2 / test && / home / der / 2 / test => / home / der / 2 (zamiast tego powinien pokazywać ścieżkę do oryginalnego skryptu)
najbardziej niezawodny sposób, który nie jest specyficzny dla basha, jaki znam.
eddygeek,
9
Jeśli chcesz uzyskać rzeczywisty katalog skryptów (niezależnie od tego, czy wywołujesz skrypt za pomocą dowiązania symbolicznego, czy bezpośrednio), spróbuj:
Działa to zarówno na systemie Linux, jak i macOS. Nie widziałem, żeby ktokolwiek tutaj wspominał realpath. Nie jestem pewien, czy takie podejście ma wady.
w systemie macOS musisz zainstalować, coreutilsaby użyć realpath. Np brew install coreutils. :
DLACZEGO UŻYWANIE dirname „$ 0” NA WŁASNEJ DZIAŁANIE?
nazwa katalogu $ 0 będzie działać tylko wtedy, gdy użytkownik uruchomi skrypt w bardzo specyficzny sposób. Udało mi się znaleźć kilka sytuacji, w których ta odpowiedź kończy się niepowodzeniem i powoduje awarię skryptu.
Przede wszystkim zrozummy, jak działa ta odpowiedź. Robi to, robiąc katalog skryptów
dirname "$0"
$ 0 reprezentuje pierwszą część polecenia wywołującego skrypt (jest to w zasadzie polecenie wprowadzone bez argumentów:
/some/path/./script argument1 argument2
$ 0 = "/ some / path /./ script"
dirname w zasadzie znajduje ostatni / w ciągu i obcina go tam. Więc jeśli to zrobisz:
dirname /usr/bin/sha256sum
dostaniesz: / usr / bin
Ten przykład działa dobrze, ponieważ / usr / bin / sha256sum jest poprawnie sformatowaną ścieżką, ale
dirname "/some/path/./script"
nie działałby dobrze i dałby ci:
BASENAME="/some/path/."#which would crash your script if you try to use it as a path
Załóżmy, że masz ten sam katalog co skrypt i uruchamiasz go za pomocą tego polecenia
./script
0 $ w tej sytuacji to ./script, a nazwa katalogu 0 $ da:
.#or BASEDIR=".", again this will crash your script
Za pomocą:
sh script
Bez wprowadzania pełna ścieżka da również BASEDIR = "."
Korzystanie z katalogów względnych:
../some/path/./script
Podaje nazwę katalogu 0 $ z:
../some/path/.
Jeśli jesteś w katalogu / some i wywołujesz skrypt w ten sposób (zwróć uwagę na brak / na początku, ponownie ścieżka względna):
path/./script.sh
Otrzymasz tę wartość dla nazwy katalogu 0 $:
path/.
i ./path/./script (inna forma ścieżki względnej) daje:
./path/.
Jedynymi dwiema sytuacjami, w których będzie działał oparty na 0 $ , jest to, że użytkownik użyje sh lub touch, aby uruchomić skrypt, ponieważ oba przyniosą 0 $:
$0=/some/path/script
który da ci ścieżkę, której możesz użyć z dirname.
ROZWIĄZANIE
Będziesz mieć konto i wykryć każdą z wyżej wymienionych sytuacji i zastosuj poprawkę, jeśli się pojawi:
#!/bin/bash#this script will only work in bash, make sure it's installed on your system.#set to false to not see all the echos
debug=true
if["$debug"= true ];then echo "\$0=$0";fi#The line below detect script's parent directory. $0 is the part of the launch command that doesn't contain the arguments
BASEDIR=$(dirname "$0")#3 situations will cause dirname $0 to fail: #situation1: user launches script while in script dir ( $0=./script)#situation2: different dir but ./ is used to launch script (ex. $0=/path_to/./script)#situation3: different dir but relative path used to launch scriptif["$debug"= true ];then echo 'BASEDIR=$(dirname "$0") gives: '"$BASEDIR";fiif["$BASEDIR"="."];then BASEDIR="$(pwd)";fi# fix for situation1
_B2=${BASEDIR:$((${#BASEDIR}-2))}; B_=${BASEDIR::1}; B_2=${BASEDIR::2}; B_3=${BASEDIR::3}# <- bash onlyif["$_B2"="/."];then BASEDIR=${BASEDIR::$((${#BASEDIR}-1))};fi#fix for situation2 # <- bash onlyif["$B_"!="/"];then#fix for situation3 #<- bash onlyif["$B_2"="./"];then#covers ./relative_path/(./)scriptif["$(pwd)"!="/"];then BASEDIR="$(pwd)/${BASEDIR:2}";else BASEDIR="/${BASEDIR:2}";fielse#covers relative_path/(./)script and ../relative_path/(./)script, using ../relative_path fails if current path is a symbolic linkif["$(pwd)"!="/"];then BASEDIR="$(pwd)/$BASEDIR";else BASEDIR="/$BASEDIR";fififiif["$debug"= true ];then echo "fixed BASEDIR=$BASEDIR";fi
Ten linijka informuje, gdzie znajduje się skrypt powłoki, nie ma znaczenia, czy go uruchomiłeś, czy go pozyskałeś . Ponadto rozwiązuje wszelkie zaangażowane łącza symboliczne, jeśli tak jest:
Tak wiele odpowiedzi, wszystkie prawdopodobne, każda z celami przeciwników i przeciwników oraz nieco różniącymi się celami (które prawdopodobnie należy podać dla każdej z nich). Oto inne rozwiązanie, które spełnia główny cel, którym jest zarówno przejrzystość, jak i działanie we wszystkich systemach, na wszystkich wersjach bash (bez założeń dotyczących wersji readlinklub pwdopcji bash ) i rozsądnie spełnia to, czego można się spodziewać (np. Rozwiązywanie dowiązań symbolicznych jest interesujący problem, ale zwykle nie jest tym, czego naprawdę chcesz), obsługuj przypadki krawędzi, takie jak spacje w ścieżkach itp., ignoruje wszelkie błędy i używa rozsądnego ustawienia domyślnego, jeśli występują jakiekolwiek problemy.
Każdy składnik jest przechowywany w osobnej zmiennej, z której można korzystać osobno:
# script path, filename, directory
PROG_PATH=${BASH_SOURCE[0]}# this script's name
PROG_NAME=${PROG_PATH##*/} # basename of script (strip path)
PROG_DIR="$(cd "$(dirname "${PROG_PATH:-$PWD}")" 2>/dev/null 1>&2 && pwd)"
Może wyglądać brzydko w zależności od tego, w jaki sposób został wywołany i cwd, ale powinien zabrać cię tam, gdzie musisz iść (lub możesz poprawić ciąg, jeśli zależy ci na tym, jak to wygląda).
Odpowiedzi:
W Bash powinieneś otrzymać to, czego potrzebujesz:
źródło
readlink
(patrz odpowiedź al poniżej)$BASH_SOURCE
w miejsce$0
, ponieważ$0
nie zawsze zawierać ścieżkę do skryptu powołano się, na przykład podczas „pozyskiwania” skryptu.$BASH_SOURCE
jest specyficzne dla Bash, pytanie dotyczy ogólnie skryptu powłoki.CUR_PATH=$(pwd)
lubpwd
zwróć bieżący katalog (który nie musi być katalogiem macierzystym skryptów)!$BASH_SOURCE
, która zwraca to, czego potrzebowałem. Mój skrypt jest wywoływany z innego skryptu i$0
zwraca,.
podczas gdy$BASH_SOURCE
zwraca właściwy podkatalog (w moim przypadkuscripts
).Oryginalny post zawiera rozwiązanie (zignoruj odpowiedzi, nie dodają niczego przydatnego). Interesującą pracę wykonuje wspomniane polecenie unix
readlink
z opcją-f
. Działa, gdy skrypt jest wywoływany zarówno przez ścieżkę bezwzględną, jak i względną.Dla bash, sh, ksh:
Dla tcsh, csh:
Zobacz także: https://stackoverflow.com/a/246128/59087
źródło
readlink
. Dlatego zaleciłem użycie pushd / popd (wbudowane w bash).-f
Opcjareadlink
robi coś innego na OS X (Lion) i ewentualnie BSD. stackoverflow.com/questions/1055671/…-f
nie jest w ogóle obsługiwany w OS X (od Lion); tam możesz albo zrezygnować-f
z rozwiązania co najwyżej jednego poziomu pośredni, np.pushd "$(dirname "$(readlink "$BASH_SOURCE" || echo "$BASH_SOURCE")")"
albo możesz rzucić swój własny rekursywny skrypt śledzący dowiązanie symboliczne, jak pokazano w łączonym poście.sh /some/other/directory/script.sh)
W tym przypadku.
byłby twój pwd, a nie/some/other/directory
Powiedział to wcześniejszy komentarz do odpowiedzi, ale łatwo można przeoczyć wszystkie pozostałe odpowiedzi.
Podczas korzystania z bash:
Podręcznik referencyjny Bash, 5.2 Zmienne Bash
źródło
dirname "$BASH_SOURCE"
do obsługi spacji w $ BASH_SOURCE."$(dirname "${BASH_SOURCE[0]}")"
Zakładając, że używasz bash
Ten skrypt powinien wydrukować katalog, w którym się znajdujesz, a następnie katalog, w którym znajduje się skrypt. Na przykład, wywołując go
/
z poziomu skryptu/home/mez/
, wyświetla dane wyjściowePamiętaj, że przypisując zmienne z danych wyjściowych polecenia, zawiń je
$(
i)
- inaczej nie uzyskasz żądanego wyniku.źródło
Jeśli używasz bash ....
źródło
pushd
/popd
zcd $(dirname "${0}")
icd -
aby to działało na innych pocisków, jeśli mająpwd -L
.Jak sugeruje Marko:
Działa to, chyba że wykonasz skrypt z tego samego katalogu, w którym znajduje się skrypt, w którym to przypadku otrzymasz wartość „.”
Aby obejść ten problem, użyj:
Możesz teraz używać zmiennej current_dir w całym skrypcie, aby odwoływać się do katalogu skryptów. Jednak może to nadal powodować problem z dowiązaniem symbolicznym.
źródło
Najlepsza odpowiedź na to pytanie została udzielona tutaj:
Pobieranie katalogu źródłowego skryptu Bash od wewnątrz
I to jest:
Jednowierszowy, który da ci pełną nazwę katalogu skryptu, bez względu na to, skąd jest wywoływany.
Aby zrozumieć, jak to działa, możesz wykonać następujący skrypt:
źródło
źródło
Zróbmy to oneliner POSIX:
Testowany na wielu powłokach zgodnych z Bourne'em, w tym na BSD.
O ile wiem, jestem autorem i umieszczam go w domenie publicznej. Aby uzyskać więcej informacji, zobacz: https://www.jasan.tk/posts/2017-05-11-posix_shell_dirname_replacement/
źródło
cd: too many arguments
jeśli spacje na ścieżce i zwraca$PWD
. (oczywista poprawka, ale pokazuje tylko, ile faktycznie jest krawędzi)źródło
Jeśli chcesz uzyskać rzeczywisty katalog skryptów (niezależnie od tego, czy wywołujesz skrypt za pomocą dowiązania symbolicznego, czy bezpośrednio), spróbuj:
Działa to zarówno na systemie Linux, jak i macOS. Nie widziałem, żeby ktokolwiek tutaj wspominał
realpath
. Nie jestem pewien, czy takie podejście ma wady.w systemie macOS musisz zainstalować,
coreutils
aby użyćrealpath
. Npbrew install coreutils
. :źródło
WPROWADZENIE
Ta odpowiedź koryguje bardzo zepsutą, ale szokująco najczęściej głosowaną odpowiedź tego wątku (napisaną przez TheMarko):
DLACZEGO UŻYWANIE dirname „$ 0” NA WŁASNEJ DZIAŁANIE?
nazwa katalogu $ 0 będzie działać tylko wtedy, gdy użytkownik uruchomi skrypt w bardzo specyficzny sposób. Udało mi się znaleźć kilka sytuacji, w których ta odpowiedź kończy się niepowodzeniem i powoduje awarię skryptu.
Przede wszystkim zrozummy, jak działa ta odpowiedź. Robi to, robiąc katalog skryptów
$ 0 reprezentuje pierwszą część polecenia wywołującego skrypt (jest to w zasadzie polecenie wprowadzone bez argumentów:
$ 0 = "/ some / path /./ script"
dirname w zasadzie znajduje ostatni / w ciągu i obcina go tam. Więc jeśli to zrobisz:
dostaniesz: / usr / bin
Ten przykład działa dobrze, ponieważ / usr / bin / sha256sum jest poprawnie sformatowaną ścieżką, ale
nie działałby dobrze i dałby ci:
Załóżmy, że masz ten sam katalog co skrypt i uruchamiasz go za pomocą tego polecenia
0 $ w tej sytuacji to ./script, a nazwa katalogu 0 $ da:
Za pomocą:
Bez wprowadzania pełna ścieżka da również BASEDIR = "."
Korzystanie z katalogów względnych:
Podaje nazwę katalogu 0 $ z:
Jeśli jesteś w katalogu / some i wywołujesz skrypt w ten sposób (zwróć uwagę na brak / na początku, ponownie ścieżka względna):
Otrzymasz tę wartość dla nazwy katalogu 0 $:
i ./path/./script (inna forma ścieżki względnej) daje:
Jedynymi dwiema sytuacjami, w których będzie działał oparty na 0 $ , jest to, że użytkownik użyje sh lub touch, aby uruchomić skrypt, ponieważ oba przyniosą 0 $:
który da ci ścieżkę, której możesz użyć z dirname.
ROZWIĄZANIE
Będziesz mieć konto i wykryć każdą z wyżej wymienionych sytuacji i zastosuj poprawkę, jeśli się pojawi:
źródło
Ten linijka informuje, gdzie znajduje się skrypt powłoki, nie ma znaczenia, czy go uruchomiłeś, czy go pozyskałeś . Ponadto rozwiązuje wszelkie zaangażowane łącza symboliczne, jeśli tak jest:
Nawiasem mówiąc, przypuszczam, że używasz / bin / bash .
źródło
Tak wiele odpowiedzi, wszystkie prawdopodobne, każda z celami przeciwników i przeciwników oraz nieco różniącymi się celami (które prawdopodobnie należy podać dla każdej z nich). Oto inne rozwiązanie, które spełnia główny cel, którym jest zarówno przejrzystość, jak i działanie we wszystkich systemach, na wszystkich wersjach bash (bez założeń dotyczących wersji
readlink
lubpwd
opcji bash ) i rozsądnie spełnia to, czego można się spodziewać (np. Rozwiązywanie dowiązań symbolicznych jest interesujący problem, ale zwykle nie jest tym, czego naprawdę chcesz), obsługuj przypadki krawędzi, takie jak spacje w ścieżkach itp., ignoruje wszelkie błędy i używa rozsądnego ustawienia domyślnego, jeśli występują jakiekolwiek problemy.Każdy składnik jest przechowywany w osobnej zmiennej, z której można korzystać osobno:
źródło
Zainspirowany odpowiedzią blueyed
źródło
To powinno załatwić sprawę:
Może wyglądać brzydko w zależności od tego, w jaki sposób został wywołany i cwd, ale powinien zabrać cię tam, gdzie musisz iść (lub możesz poprawić ciąg, jeśli zależy ci na tym, jak to wygląda).
źródło
`pwd`/`dirname $0`
ale może nadal zawieść w dowiązaniach symbolicznych