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?

William Yeung
źródło
12
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ę.
BoltClock
2
Myślę, że ta odpowiedź jest lepsza: Pobieranie katalogu źródłowego skryptu Bash od wewnątrz
Alan Zhiliang Feng
1
Możliwy duplikat
pobrania

Odpowiedzi:

567

W Bash powinieneś otrzymać to, czego potrzebujesz:

#!/usr/bin/env bash

BASEDIR=$(dirname "$0")
echo "$BASEDIR"
TheMarko
źródło
17
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.csh
set SCRIPT=`readlink -f "$0"`
# Absolute path this script is in, thus /home/user/bin
set SCRIPTPATH=`dirname "$SCRIPT"`
echo $SCRIPTPATH

Zobacz także: https://stackoverflow.com/a/246128/59087

glin.
źródło
12
Uwaga: nie wszystkie systemy mają readlink. Dlatego zaleciłem użycie pushd / popd (wbudowane w bash).
docwhat
23
-fOpcja readlinkrobi coś innego na OS X (Lion) i ewentualnie BSD. stackoverflow.com/questions/1055671/…
Ergwun
9
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")"

Podręcznik referencyjny Bash, 5.2 Zmienne Bash

Daniel
źródło
3
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)
JL_SO,
40

Zakładając, że używasz bash

#!/bin/bash

current_dir=$(pwd)
script_dir=$(dirname $0)

echo $current_dir
echo $script_dir

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.

Mez
źródło
3
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}"
docwhat
źródło
4
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ść „.”

Aby obejść ten problem, użyj:

current_dir=$(pwd)
script_dir=$(dirname $0)

if [ $script_dir = '.' ]
then
script_dir="$current_dir"
fi

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.

ranamalo
źródło
20

Najlepsza odpowiedź na to pytanie została udzielona tutaj:
Pobieranie katalogu źródłowego skryptu Bash od wewnątrz

I to jest:

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 located
  fi
done
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'"
Alexandro de Oliveira
źródło
14
cd $(dirname $(readlink -f $0))
niebieskawy
źródło
10

Zróbmy to oneliner POSIX:

a="/$0"; a=${a%/*}; a=${a#/}; a=${a:-.}; BASEDIR=$(cd "$a"; pwd)

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/

Ján Sáreník
źródło
1
jak napisano, cd: too many argumentsjeśli spacje na ścieżce i zwraca $PWD. (oczywista poprawka, ale pokazuje tylko, ile faktycznie jest krawędzi)
Michael
1
Głosowałby. Z wyjątkiem komentarza @ Michaela, że ​​zawodzi ze spacjami na ścieżce ... Czy jest na to poprawka?
spechter
1
@spechter tak, jest na to poprawka. Zobacz zaktualizowane jasan.tk/posix/2017/05/11/posix_shell_dirname_replacement
Ján Sáreník
@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)
Der_Meister
10
BASE_DIR="$(cd "$(dirname "$0")"; pwd)";
echo "BASE_DIR => $BASE_DIR"
Searchrome
źródło
3
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:

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

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. :

Rohith
źródło
6

WPROWADZENIE

Ta odpowiedź koryguje bardzo zepsutą, ale szokująco najczęściej głosowaną odpowiedź tego wątku (napisaną przez TheMarko):

#!/usr/bin/env bash

BASEDIR=$(dirname "$0")
echo "$BASEDIR"

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 script
if [ "$debug" = true ]; then echo 'BASEDIR=$(dirname "$0") gives: '"$BASEDIR";fi                                 

if [ "$BASEDIR" = "." ]; then BASEDIR="$(pwd)";fi # fix for situation1

_B2=${BASEDIR:$((${#BASEDIR}-2))}; B_=${BASEDIR::1}; B_2=${BASEDIR::2}; B_3=${BASEDIR::3} # <- bash only
if [ "$_B2" = "/." ]; then BASEDIR=${BASEDIR::$((${#BASEDIR}-1))};fi #fix for situation2 # <- bash only
if [ "$B_" != "/" ]; then  #fix for situation3 #<- bash only
        if [ "$B_2" = "./" ]; then
                #covers ./relative_path/(./)script
                if [ "$(pwd)" != "/" ]; then BASEDIR="$(pwd)/${BASEDIR:2}"; else BASEDIR="/${BASEDIR:2}";fi
        else
                #covers relative_path/(./)script and ../relative_path/(./)script, using ../relative_path fails if current path is a symbolic link
                if [ "$(pwd)" != "/" ]; then BASEDIR="$(pwd)/$BASEDIR"; else BASEDIR="/$BASEDIR";fi
        fi
fi

if [ "$debug" = true ]; then echo "fixed BASEDIR=$BASEDIR";fi
thebunnyrules
źródło
4

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:

dir=$(dirname $(test -L "$BASH_SOURCE" && readlink -f "$BASH_SOURCE" || echo "$BASH_SOURCE"))

Nawiasem mówiąc, przypuszczam, że używasz / bin / bash .

Richard Gomes
źródło
2

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)"
Michał
źródło
-4

To powinno załatwić sprawę:

echo `pwd`/`dirname $0`

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).

kenorb
źródło
1
problem ucieczki stackoverflow tutaj: z pewnością powinien wyglądać tak: `pwd`/`dirname $0`ale może nadal zawieść w dowiązaniach symbolicznych
Andreas Dietrich