Jak uzyskać katalog źródłowy skryptu Bash z poziomu samego skryptu


4948

Jak uzyskać ścieżkę do katalogu, w którym znajduje się skrypt Bash , wewnątrz tego skryptu?

Chcę użyć skryptu Bash jako programu uruchamiającego dla innej aplikacji. Chcę zmienić katalog roboczy na katalog, w którym znajduje się skrypt Bash, aby móc operować na plikach w tym katalogu, w następujący sposób:

$ ./application

69
Żadne z obecnych rozwiązań nie działa, jeśli na końcu nazwy katalogu znajdują się nowe wiersze - Zostaną usunięte przez zastąpienie polecenia. Aby obejść ten problem, możesz dodać znak nowej linii do podstawienia polecenia DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd && echo x)"- i usunąć go bez podstawienia polecenia - DIR="${DIR%x}".
l0b0

80
@ jpmc26 Istnieją dwie bardzo częste sytuacje: wypadki i sabotaż. Skrypt nie powinien zawieść w nieprzewidywalny sposób tylko dlatego, że ktoś gdzieś zrobił mkdir $'\n'.
l0b0

24
każdy, kto pozwala ludziom sabotować swój system w ten sposób, nie powinien powstrzymywać się przed wykrywaniem takich problemów ... a tym bardziej zatrudniać ludzi zdolnych do popełnienia tego rodzaju błędu. W ciągu 25 lat używania bash nigdy nie widziałem, żeby coś takiego zdarzyło się nigdzie… dlatego mamy takie rzeczy jak Perl i praktyki takie jak sprawdzanie skażenia (prawdopodobnie zostanę oskarżony za to, że to mówię :)
osirisgothra

3
@ l0b0 Weź pod uwagę, że potrzebujesz takiej samej ochrony dirnamei że katalog może zaczynać się od -(np --help.). DIR=$(reldir=$(dirname -- "$0"; echo x); reldir=${reldir%?x}; cd -- "$reldir" && pwd && echo x); DIR=${DIR%?x}. Być może to przesada?
Score_Under

55
Szczególnie polecam przeczytanie tego Bash FAQ na ten temat.
Rany Albeg Wein

Odpowiedzi:


6566
#!/bin/bash

DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"

jest użytecznym jednowierszowym, który daje pełną nazwę katalogu skryptu, bez względu na to, skąd jest wywoływany.

Będzie działał tak długo, jak ostatni komponent ścieżki użytej do znalezienia skryptu nie będzie dowiązaniem symbolicznym (łącza do katalogów są OK). Jeśli chcesz również rozwiązać jakiekolwiek łącza do samego skryptu, potrzebujesz rozwiązania wieloliniowego:

#!/bin/bash

SOURCE="${BASH_SOURCE[0]}"
while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink
  DIR="$( cd -P "$( dirname "$SOURCE" )" >/dev/null 2>&1 && pwd )"
  SOURCE="$(readlink "$SOURCE")"
  [[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE" # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located
done
DIR="$( cd -P "$( dirname "$SOURCE" )" >/dev/null 2>&1 && pwd )"

Ten ostatni będzie współpracować z dowolną kombinacją pseudonimy, source, bash -c, dowiązania symboliczne, itd.

Uwaga: jeśli cdprzejdziesz do innego katalogu przed uruchomieniem tego fragmentu kodu, wynik może być niepoprawny!

Uważaj również na $CDPATHgotchas i efekty uboczne wyjścia stderr, jeśli użytkownik inteligentnie przesłonił cd, aby zamiast tego przekierować wyjście do stderr (w tym sekwencje specjalne, na przykład podczas wywoływania update_terminal_cwd >&2na komputerze Mac). Dodanie >/dev/null 2>&1na końcu twojego cdpolecenia zadba o obie możliwości.

Aby zrozumieć, jak to działa, spróbuj uruchomić ten bardziej szczegółowy formularz:

#!/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" )" >/dev/null 2>&1 && pwd )"
if [ "$DIR" != "$RDIR" ]; then
  echo "DIR '$RDIR' resolves to '$DIR'"
fi
echo "DIR is '$DIR'"

I wydrukuje coś takiego:

SOURCE './scriptdir.sh' is a relative symlink to 'sym2/scriptdir.sh' (relative to '.')
SOURCE is './sym2/scriptdir.sh'
DIR './sym2' resolves to '/home/ubuntu/dotfiles/fo fo/real/real1/real2'
DIR is '/home/ubuntu/dotfiles/fo fo/real/real1/real2'

27
Można bezpiecznik tego podejścia z odpowiedzią przez user25866 aby dojść do rozwiązania, który współpracuje z source <script>i bash <script>: DIR="$(cd -P "$(dirname "${BASH_SOURCE[0]}")" && pwd)".
Dan Molding

21
Czasami cddrukuje coś do STDOUT! Na przykład, jeśli $CDPATHma .. Aby objąć tę sprawę, użyjDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" > /dev/null && pwd )"
user716468

182
Ta zaakceptowana odpowiedź jest nieprawidłowa, nie działa z dowiązaniami symbolicznymi i jest zbyt złożona. dirname $(readlink -f $0)to właściwe polecenie. Zobacz gist.github.com/tvlooy/cbfbdb111a4ebad8b93e dla przypadku
testowego

165
@tvlooy IMO twoja odpowiedź również nie jest w porządku, ponieważ nie działa, ponieważ na ścieżce jest spacja. W przeciwieństwie do znaku nowej linii, nie jest to mało prawdopodobne ani nawet rzadkie. dirname "$(readlink -f "$0")"nie dodaje złożoności i jest sprawiedliwie bardziej odporny na minimalne problemy.
Adrian Günter

9
@tvlooy twój komentarz nie jest kompatybilny z macOS (lub prawdopodobnie BSD ogólnie), podczas gdy zaakceptowana odpowiedź jest. readlink -f $0daje readlink: illegal option -- f.
Alexander Ljungberg,

875

Użyj dirname "$0":

#!/bin/bash
echo "The script you are running has basename `basename "$0"`, dirname `dirname "$0"`"
echo "The present working directory is `pwd`"

użycie pwdsamego nie będzie działać, jeśli nie uruchamiasz skryptu z katalogu, w którym się on znajduje.

[matt@server1 ~]$ pwd
/home/matt
[matt@server1 ~]$ ./test2.sh
The script you are running has basename test2.sh, dirname .
The present working directory is /home/matt
[matt@server1 ~]$ cd /tmp
[matt@server1 tmp]$ ~/test2.sh
The script you are running has basename test2.sh, dirname /home/matt
The present working directory is /tmp

25
Dla przenośności poza bash, 0 $ może nie zawsze wystarczać. Może być konieczne zastąpienie „type -p $ 0”, aby to zadziałało, jeśli polecenie zostało znalezione na ścieżce.
Darron

10
@Darron: możesz używać tylko type -pwtedy, gdy skrypt jest wykonywalny. Może to również otworzyć subtelną dziurę, jeśli skrypt jest wykonywany przy użyciu, bash test2.sha gdzieś jest inny skrypt o tej samej nazwie wykonywalny.
D.Shawley,

90
@Darron: ale ponieważ pytanie jest otagowane, basha linia hash-bang wyraźnie wspomina /bin/bash, powiedziałbym, że można bezpiecznie polegać na baszizmach.
Joachim Sauer

34
+1, ale problem z używaniem dirname $0polega na tym, że jeśli katalog jest katalogiem bieżącym, otrzymasz .. W porządku, chyba że zamierzasz zmienić katalogi w skrypcie i oczekiwać, że użyjesz ścieżki, z której masz do czynienia, dirname $0jakby była absolutna. Aby uzyskać bezwzględną ścieżkę: pushd `dirname $0` > /dev/null, SCRIPTPATH=`pwd`, popd > /dev/null: pastie.org/1489386 (ale na pewno nie ma lepszego sposobu, aby rozwinąć tę ścieżkę?)
TJ Crowder

9
@TJ Crowder Nie jestem pewien, czy dirname $0jest to problem, jeśli przypiszesz go do zmiennej, a następnie użyjesz do uruchomienia skryptu podobnego do $dir/script.sh; Wyobrażam sobie, że jest to przypadek użycia tego typu rzeczy w 90% przypadków. ./script.shdziałałoby dobrze.
matt b

513

dirnameKomenda jest najbardziej podstawowym, po prostu parsowania ścieżkę do pliku off z $0zmienna (nazwa skryptu):

dirname "$0"

Ale, jak zauważył Matt B , zwrócona ścieżka różni się w zależności od sposobu wywołania skryptu. pwdnie wykonuje zadania, ponieważ mówi tylko o tym, jaki jest bieżący katalog, a nie katalog, w którym znajduje się skrypt. Ponadto, jeśli zostanie wykonane dowiązanie symboliczne do skryptu, otrzymasz ścieżkę (prawdopodobnie względną) do gdzie znajduje się link, a nie rzeczywisty skrypt.

Inni wspomnieli o readlinkpoleceniu, ale najprościej możesz użyć:

dirname "$(readlink -f "$0")"

readlinkrozwiąże ścieżkę skryptu do ścieżki bezwzględnej z katalogu głównego systemu plików. Zatem wszelkie ścieżki zawierające pojedyncze lub podwójne kropki, tyldy i / lub dowiązania symboliczne zostaną przekształcone w pełną ścieżkę.

Oto skrypt demonstrujący każdy z nich whatdir.sh:

#!/bin/bash
echo "pwd: `pwd`"
echo "\$0: $0"
echo "basename: `basename $0`"
echo "dirname: `dirname $0`"
echo "dirname/readlink: $(dirname $(readlink -f $0))"

Uruchomienie tego skryptu w moim katalogu domowym przy użyciu ścieżki względnej:

>>>$ ./whatdir.sh 
pwd: /Users/phatblat
$0: ./whatdir.sh
basename: whatdir.sh
dirname: .
dirname/readlink: /Users/phatblat

Ponownie, ale używając pełnej ścieżki do skryptu:

>>>$ /Users/phatblat/whatdir.sh 
pwd: /Users/phatblat
$0: /Users/phatblat/whatdir.sh
basename: whatdir.sh
dirname: /Users/phatblat
dirname/readlink: /Users/phatblat

Teraz zmieniamy katalogi:

>>>$ cd /tmp
>>>$ ~/whatdir.sh 
pwd: /tmp
$0: /Users/phatblat/whatdir.sh
basename: whatdir.sh
dirname: /Users/phatblat
dirname/readlink: /Users/phatblat

I na koniec używając linku symbolicznego do wykonania skryptu:

>>>$ ln -s ~/whatdir.sh whatdirlink.sh
>>>$ ./whatdirlink.sh 
pwd: /tmp
$0: ./whatdirlink.sh
basename: whatdirlink.sh
dirname: .
dirname/readlink: /Users/phatblat

13
readlinknie będzie dostępny na niektórych platformach w domyślnej instalacji. Staraj się go nie używać, jeśli możesz
TL

43
export SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
pamiętaj,

13
W OSX Yosemite 10.10.1 -fnie jest rozpoznawany jako opcja readlink. Używanie stat -fzamiast tego wykonuje zadanie. Dzięki
cucu8,

11
W OSX jest coś greadlink, co w zasadzie readlinkwszyscy znamy. Oto niezależna od platformy wersja:dir=`greadlink -f ${BASH_SOURCE[0]} || readlink -f ${BASH_SOURCE[0]}`
Robert

6
Dobra rozmowa, @robert. FYI, greadlinkmożna łatwo zainstalować za pomocą homebrew:brew install coreutils
phatblat

184
pushd . > /dev/null
SCRIPT_PATH="${BASH_SOURCE[0]}"
if ([ -h "${SCRIPT_PATH}" ]); then
  while([ -h "${SCRIPT_PATH}" ]); do cd `dirname "$SCRIPT_PATH"`; 
  SCRIPT_PATH=`readlink "${SCRIPT_PATH}"`; done
fi
cd `dirname ${SCRIPT_PATH}` > /dev/null
SCRIPT_PATH=`pwd`;
popd  > /dev/null

Działa dla wszystkich wersji, w tym

  • wywołany przez miękki link o wielu głębokościach,
  • kiedy plik to
  • gdy skrypt wywoływany przez polecenie „ source” aka .(kropka) operator.
  • kiedy arg $0jest modyfikowany z dzwoniącego.
  • "./script"
  • "/full/path/to/script"
  • "/some/path/../../another/path/script"
  • "./some/folder/script"

Alternatywnie, jeśli sam skrypt bash jest względnym dowiązaniem symbolicznym , chcesz go podążać i zwrócić pełną ścieżkę skryptu połączonego z:

pushd . > /dev/null
SCRIPT_PATH="${BASH_SOURCE[0]}";
if ([ -h "${SCRIPT_PATH}" ]) then
  while([ -h "${SCRIPT_PATH}" ]) do cd `dirname "$SCRIPT_PATH"`; SCRIPT_PATH=`readlink "${SCRIPT_PATH}"`; done
fi
cd `dirname ${SCRIPT_PATH}` > /dev/null
SCRIPT_PATH=`pwd`;
popd  > /dev/null

SCRIPT_PATHjest podane pełną ścieżką, bez względu na to, jak się nazywa.
Tylko upewnij się, że znajdziesz to na początku skryptu.

Ten komentarz i kod Copyleft, licencja do wyboru na licencji GPL2.0 lub nowszej lub CC-SA 3.0 (CreativeCommons Share Alike) lub nowszej. (c) 2008. Wszelkie prawa zastrzeżone. Żadnej gwarancji. Zostałeś ostrzeżony.
http://www.gnu.org/licenses/gpl-2.0.txt
http://creativecommons.org/licenses/by-sa/3.0/
18eedfe1c99df68dc94d4a94712a71aaa8e1e9e36cacf421b9463dd2bbaa02906d0d6656


4
Miły! Można go skrócić, zastępując „pushd [...] popd / dev / null” przez SCRIPT_PATH = readlink -f $(dirname "${VIRTUAL_ENV}");
e-satis

5
I zamiast używać pushd ...; czy nie byłoby lepiej używać $ (cd dirname "${SCRIPT_PATH}"&& pwd)? Ale i tak świetny skrypt!
ovanes

6
Niebezpieczne jest, aby skrypt cdopuścił swój bieżący katalog w nadziei, cdże wróci później: Skrypt może nie mieć uprawnień do zmiany katalogu z powrotem na katalog, który był aktualny podczas jego wywołania. (To samo dotyczy pushd / popd)
Adrian Pronk

7
readlink -fjest specyficzny dla GNU. BSD readlinknie ma tej opcji.
Kara Brightwell,

3
O co chodzi z tymi wszystkimi niepotrzebnymi podpowłokami? ([ ... ])jest mniej wydajny niż [ ... ]i nie ma żadnej korzyści z izolacji oferowanej w zamian za to uderzenie wydajności tutaj.
Charles Duffy,

110

Krótka odpowiedź:

`dirname $0`

lub ( najlepiej ):

$(dirname "$0")

17
Nie będzie działać, jeśli skrypt zostanie pobrany. „source my / script.sh”
Arunprasad Rajkumar

Używam tego cały czas w moich skryptach bash, które automatyzują różne rzeczy i często wywołuję inne skrypty w tym samym katalogu. Nigdy bym tego nie sourceużyła i cd $(dirname $0)jest łatwa do zapamiętania.
kqw

16
@vidstige: ${BASH_SOURCE[0]}zamiast $0współpracować zsource my/script.sh
Timothy Jones

@TimothyJones, który zawiedzie przez 100% czasu, jeśli będzie pochodził z innej powłoki niż bash. ${BASH_SOURCE[0]}wcale nie jest zadowalający. ${BASH_SOURCE:-0}jest znacznie lepszy.
Mathieu CAROFF

106

Możesz użyć $BASH_SOURCE:

#!/bin/bash

scriptdir=`dirname "$BASH_SOURCE"`

Pamiętaj, że musisz go używać, #!/bin/basha nie, #!/bin/shponieważ jest to rozszerzenie Bash.


14
Kiedy to robię ./foo/script, to $(dirname $BASH_SOURCE)jest ./foo.
Do

1
@ Do tego momentu możemy użyć realpathpolecenia, aby uzyskać pełną ścieżkę ./foo/script. Tak więc dirname $(realpath ./foo/script) podam ścieżkę skryptu.
purushothaman poovai

73

To powinno to zrobić:

DIR="$(dirname "$(readlink -f "$0")")"

Działa to z dowiązaniami symbolicznymi i spacjami na ścieżce.

Zobacz strony podręcznika dla dirnamei readlink.

Ze ścieżki komentarzy wydaje się, że nie działa w systemie Mac OS. Nie mam pojęcia, dlaczego tak jest. Jakieś sugestie?


6
z twoim rozwiązaniem, wywołując skrypt jak ./script.shprogramy .zamiast pełnej ścieżki katalogu
Bruno Negrão Zica

5
Na MacOS nie ma opcji -f dla readlink. Użyj statzamiast tego. Ale wciąż pokazuje, .czy jesteś w „tym” reż.
Denis The Menace

2
Musisz zainstalować coreutilsz Homebrew i użyć, greadlinkaby uzyskać -fopcję na MacOS, ponieważ jest to * BSD pod okładkami, a nie Linux.
dragon788

Powinieneś dodać podwójne cudzysłowy otaczające całą prawą stronę:DIR="$(dirname "$(readlink -f "$0")")"
Hagello

60

pwdmożna użyć do znalezienia bieżącego katalogu roboczego i dirnamedo znalezienia katalogu określonego pliku (polecenie, które zostało uruchomione, jest $0, więc dirname $0powinno dać ci katalog bieżącego skryptu).

Jednak dirnamedaje właśnie część katalogów nazwy pliku, który nie jest bardziej prawdopodobne niż będzie względem bieżącego katalogu roboczego. Jeśli twój skrypt musi z jakiegoś powodu zmienić katalog, wynik z dirnamestaje się bez znaczenia.

Proponuję następujące:

#!/bin/bash

reldir=`dirname $0`
cd $reldir
directory=`pwd`

echo "Directory is $directory"

W ten sposób otrzymujesz katalog absolutny, a nie względny.

Ponieważ skrypt zostanie uruchomiony w osobnej instancji bash, nie ma potrzeby późniejszego przywracania katalogu roboczego, ale jeśli z jakiegoś powodu chcesz z powrotem zmienić skrypt, możesz łatwo przypisać wartość pwdzmiennej przed zmień katalog, do wykorzystania w przyszłości.

Chociaż tylko

cd `dirname $0`

rozwiązuje konkretny scenariusz w pytaniu, uważam, że ścieżka bezwzględna jest bardziej użyteczna ogólnie.


9
Możesz to wszystko zrobić w jednym wierszu: DIRECTORY = $ (cd dirname $0&& pwd)
dogbane

To nie działa, jeśli skrypt pozyskuje inny skrypt i chcesz znać jego nazwę.
reinierpost

52

Mam dość ciągłego odwiedzania tej strony w celu skopiowania wklejania jednej linijki w zaakceptowanej odpowiedzi. Problem w tym, że niełatwo to zrozumieć i zapamiętać.

Oto łatwy do zapamiętania skrypt:

DIR="$(dirname "${BASH_SOURCE[0]}")"  # get the directory name
DIR="$(realpath "${DIR}")"    # resolve its full path if need be

2
Lub, bardziej niejasno, w jednym wierszu: DIR=$(realpath "$(dirname "${BASH_SOURCE[0]}")")
agc

Dlaczego nie jest to zaakceptowana odpowiedź? Czy jest jakaś różnica w korzystaniu realpathz rozwiązywania „ręcznie” za pomocą pętli readlink? Nawet readlinkstrona Note realpath(1) is the preferred command to use for canonicalization functionality.
podręcznika

1
A tak przy okazji, czy nie powinniśmy aplikować realpathwcześniej dirname, a nie później? Jeśli sam plik skryptu jest dowiązaniem symbolicznym ... Dałby coś takiego DIR="$(dirname "$(realpath "${BASH_SOURCE[0]}")")". Właściwie bardzo blisko odpowiedzi zaproponowanej przez Simona.
User9123

@ User9123 Wydaje mi się, że akceptujący próbuje być kompatybilny ze wszystkimi popularnymi shellami / dystrybucjami. Co więcej, w zależności od tego, co próbujesz zrobić, w większości przypadków ludzie chcą uzyskać katalog, w którym znajduje się dowiązanie symboliczne, zamiast katalogu rzeczywistego źródła.
Wang

37

Nie sądzę, że jest to tak łatwe, jak inni się na to wydawali. pwdnie działa, ponieważ bieżący katalog niekoniecznie jest katalogiem ze skryptem. $0nie zawsze ma te informacje. Rozważ następujące trzy sposoby wywołania skryptu:

./script

/usr/bin/script

script

W pierwszy i trzeci sposób $0nie ma pełnej informacji o ścieżce. W drugim i trzecim pwdnie działa. Jedynym sposobem na uzyskanie katalogu w trzeci sposób byłoby uruchomienie ścieżki i znalezienie pliku z poprawnym dopasowaniem. Zasadniczo kod musiałby powtórzyć to, co robi system operacyjny.

Jednym ze sposobów zrobienia tego, o co prosisz, byłoby po prostu zakodować dane w /usr/sharekatalogu i odwołać się do nich pełną ścieżką. Dane i tak nie powinny znajdować się w /usr/binkatalogu, więc prawdopodobnie tak należy zrobić.


9
Jeśli zamierzasz obalić jego komentarz, POTWIERDŹ, że skrypt MOŻE uzyskać dostęp do miejsca, w którym jest przechowywany, z przykładem kodu.
Richard Duerr

34
SCRIPT_DIR=$( cd ${0%/*} && pwd -P )

Jest to o wiele mniej niż wybrana odpowiedź. I wydaje się działać równie dobrze. Zasługuje na 1000 głosów, aby ludzie nie przeoczyli tego.
Patrick,

2
Ponieważ wiele wcześniejszych odpowiedzi wyjaśnia szczegółowo, nie ma ani $0nie pwdma gwarancji, że ma właściwe informacje, w zależności od sposobu wywołania skryptu.
IMSoP,

34
$(dirname "$(readlink -f "$BASH_SOURCE")")

Wolę, $BASH_SOURCEponad $0, bo to wyraźny nawet dla czytelników nie dobrze zorientowani w bash. $(dirname -- "$(readlink -f -- "$BASH_SOURCE")")
blobmaster

32

Spowoduje to pobranie bieżącego katalogu roboczego w systemie Mac OS X 10.6.6:

DIR=$(cd "$(dirname "$0")"; pwd)

27

Jest to specyficzne dla Linuksa, ale możesz użyć:

SELF=$(readlink /proc/$$/fd/255)

1
Jest to również specyficzne dla bash, ale może zachowanie bash zmieniło się? /proc/fd/$$/255wydaje się wskazywać na tty, a nie na katalog. Na przykład w mojej bieżącej powłoce logowania odnoszą się wszystkie deskryptory plików 0, 1, 2 i 255 /dev/pts/4. W każdym razie instrukcja bash nie wspomina o fd 255, więc prawdopodobnie nierozsądne jest poleganie na tym zachowaniu. \
Keith Thompson

2
Interaktywna powłoka! = Skrypt. W każdym razie realpath ${BASH_SOURCE[0]};wydaje się najlepszą drogą.
Steve Baker,

23

Oto jednolinijka zgodna z POSIX:

SCRIPT_PATH=`dirname "$0"`; SCRIPT_PATH=`eval "cd \"$SCRIPT_PATH\" && pwd"`

# test
echo $SCRIPT_PATH

4
Odniosłem sukces, gdy uruchomiłem skrypt samodzielnie lub używając sudo, ale nie zadzwoniłem do source ./script.sh
Michael R

I kończy się niepowodzeniem, gdy cdjest skonfigurowany do drukowania nowej nazwy ścieżki.
Aaron Digulla,

18

Próbowałem tych wszystkich i nic nie działało. Jeden był bardzo blisko, ale miał drobny błąd, który bardzo go zepsuł; zapomnieli zawinąć ścieżkę w cudzysłów.

Również wiele osób zakłada, że ​​uruchamiasz skrypt z powłoki, więc zapominają, że po otwarciu nowego skryptu domyślnie jest to dom.

Wypróbuj ten katalog dla rozmiaru:

/var/No one/Thought/About Spaces Being/In a Directory/Name/And Here's your file.text

To dobrze, bez względu na to, jak i gdzie go uruchomisz:

#!/bin/bash
echo "pwd: `pwd`"
echo "\$0: $0"
echo "basename: `basename "$0"`"
echo "dirname: `dirname "$0"`"

Aby więc było to naprawdę przydatne, przejdź do katalogu uruchomionego skryptu:

cd "`dirname "$0"`"

4
Nie działa, jeśli skrypt pochodzi z innego skryptu.
reinierpost

To nie działa, jeśli ostatnia część $ 0 jest dowiązaniem symbolicznym prowadzącym do pozycji innego katalogu ( ln -s ../bin64/foo /usr/bin/foo).
Hagello

17

Oto prosty, poprawny sposób:

actual_path=$(readlink -f "${BASH_SOURCE[0]}")
script_dir=$(dirname "$actual_path")

Wyjaśnienie:

  • ${BASH_SOURCE[0]}- pełna ścieżka do skryptu. Wartość tego będzie poprawna nawet podczas pozyskiwania skryptu, np. source <(echo 'echo $0')Wypisuje bash , a zastąpienie go ${BASH_SOURCE[0]}wypisuje pełną ścieżkę skryptu. (Oczywiście zakłada to, że nie masz nic przeciwko Bashowi.)

  • readlink -f- Rekurencyjnie rozwiązuje wszelkie dowiązania symboliczne na określonej ścieżce. Jest to rozszerzenie GNU i nie jest dostępne (na przykład) w systemach BSD. Jeśli używasz komputera Mac, możesz użyć Homebrew, aby zainstalować GNU coreutilsi zastąpić to greadlink -f.

  • I oczywiście dirnamepobiera katalog macierzysty ścieżki.


1
greadlink -fniestety nie działa efektywnie podczas sourcepisania skryptu na Macu :(
Gabe Kopley

17

Najkrótszym i najbardziej eleganckim sposobem na to jest:

#!/bin/bash
DIRECTORY=$(cd `dirname $0` && pwd)
echo $DIRECTORY

To działałoby na wszystkich platformach i jest super czyste.

Więcej informacji można znaleźć w „W którym katalogu znajduje się ten skrypt bash? ”.


świetne czyste rozwiązanie, ale to nie zadziała, jeśli plik jest dowiązaniem symbolicznym.
ruuter

16

Użyłbym czegoś takiego:

# retrieve the full pathname of the called script
scriptPath=$(which $0)

# check whether the path is a link or not
if [ -L $scriptPath ]; then

    # it is a link then retrieve the target path and get the directory name
    sourceDir=$(dirname $(readlink -f $scriptPath))

else

    # otherwise just get the directory name of the script path
    sourceDir=$(dirname $scriptPath)

fi

To jest prawdziwy! Działa również z prostym sh! Problem z prostymi dirname "$0"rozwiązaniami: jeśli skrypt znajduje się $PATHi jest wywoływany bez ścieżki, dadzą zły wynik.
Notinlist,

@Notinlist Nie tak. Jeśli skrypt zostanie znaleziony za pomocą PATH, $0będzie zawierać bezwzględną nazwę pliku. Jeśli skrypt zostanie wywołany z względną lub bezwzględną nazwą pliku zawierającą a /, $0będzie to zawierać.
Neil Mayhew

Nie będzie działać w przypadku skryptu źródłowego.
Amit Naidu

16

Jest to niewielka zmiana rozwiązania e-satis i 3bcdnlklvc04a wskazane w odpowiedzi :

SCRIPT_DIR=''
pushd "$(dirname "$(readlink -f "$BASH_SOURCE")")" > /dev/null && {
    SCRIPT_DIR="$PWD"
    popd > /dev/null
}    

Powinno to nadal działać we wszystkich wymienionych przypadkach.

Zapobiegnie to popdpo awarii pushd, dzięki konsolebox.


Działa to idealnie, aby uzyskać „prawdziwą” nazwę katalogu, a nie tylko nazwę dowiązania symbolicznego. Dziękuję Ci!
Jay Taylor,

1
LepiejSCRIPT_DIR=''; pushd "$(dirname "$(readlink -f "$BASH_SOURCE")")" > /dev/null && { SCRIPT_DIR=$PWD; popd > /dev/null; }
konsolebox

@konsolebox, przed czym próbujesz się bronić? Zasadniczo jestem fanem logicznych warunków warunkowych, ale jaki konkretny błąd widziałeś w pushd? Wolałbym raczej znaleźć sposób, aby obsłużyć go bezpośrednio, zamiast zwracania pustego SCRIPT_DIR.
Fuwjax,

@Fuwjax Naturalna praktyka, aby unikać robienia popdw przypadkach (nawet rzadkich), w których pushdzawodzi. A w razie pushdniepowodzenia, jaka powinna być wartość SCRIPT_DIR? Działanie może się różnić w zależności od tego, co może wydawać się logiczne lub co może preferować jeden użytkownik, ale na pewno robienie popdjest złe.
konsolebox

Wszystkich tych pushd popdniebezpieczeństw można uniknąć po prostu upuszczając je i zamiast tego używając cd+ pwdzawartego w podstawieniu polecenia. SCRIPT_DIR=$(...)
Amit Naidu

16

W systemach z rdzeniami GNU readlink(np. Linux):

$(readlink -f "$(dirname "$0")")

Nie ma potrzeby używania, BASH_SOURCEgdy $0zawiera nazwę skryptu.


2
chyba że skrypt został pobrany. lub „source”, w którym to przypadku nadal będzie to dowolny skrypt, który go pozyskał, lub, jeśli z wiersza poleceń, „-bash” (login tty) lub „bash” (wywoływany przez „bash -l”) lub „/ bin / bash ”(wywoływany jako interaktywna powłoka
niezalogowana

Dodałem drugą parę cytatów wokół dirnamepołączenia. Potrzebny, jeśli ścieżka do katalogu zawiera spacje.
user1338062

14
#!/bin/sh
PRG="$0"

# need this for relative symlinks
while [ -h "$PRG" ] ; do
   PRG=`readlink "$PRG"`
done

scriptdir=`dirname "$PRG"`

Nie testowałem tego na różnych systemach. Ale dla mnie to rozwiązanie działa od razu przynajmniej na Ubuntu!
Natus Drew

$0nie będzie działać w przypadku skryptu
źródłowego

13

$_warto wspomnieć jako alternatywę dla $0. Jeśli uruchamiasz skrypt z Bash, zaakceptowaną odpowiedź można skrócić do:

DIR="$( dirname "$_" )"

Pamiętaj, że musi to być pierwsza instrukcja w twoim skrypcie.


4
Łamie się, jeśli ty sourcelub .skrypt. W takich sytuacjach $_zawierałby ostatni parametr ostatniego polecenia wykonanego przed .. $BASH_SOURCEdziała za każdym razem.
clacke

11

Porównałem wiele udzielonych odpowiedzi i opracowałem bardziej kompaktowe rozwiązania. Wydaje się, że radzą sobie ze wszystkimi szalonymi przypadkami, które wynikają z twojej ulubionej kombinacji:

  • Ścieżki bezwzględne lub ścieżki względne
  • Miękkie linki do plików i katalogów
  • Inwokacja jako script, bash script, bash -c script, source script, lub. script
  • Spacje, tabulatory, znaki nowej linii, Unicode itp. W katalogach i / lub nazwie pliku
  • Nazwy plików zaczynające się od łącznika

Jeśli korzystasz z Linuksa, wydaje się, że użycie procuchwytu jest najlepszym rozwiązaniem do zlokalizowania w pełni rozwiązanego źródła aktualnie uruchomionego skryptu (w sesji interaktywnej link wskazuje na odpowiedni /dev/pts/X):

resolved="$(readlink /proc/$$/fd/255 && echo X)" && resolved="${resolved%$'\nX'}"

Ma to trochę brzydoty, ale poprawka jest zwarta i łatwa do zrozumienia. Nie używamy tylko prymitywów bash, ale nie mam nic readlinkprzeciwko , ponieważ znacznie upraszcza to zadanie. echo XDodaje Xna końcu łańcucha zmiennego tak, że wszystkie spacje spływu w nazwie pliku nie zjedzony, a podstawienie parametr ${VAR%X}na końcu linii pozbywa się X. Ponieważ readlinkdodaje nowy wiersz (który normalnie zostałby zjedzony podczas zastępowania poleceń, gdyby nie nasza poprzednia sztuczka), musimy się go również pozbyć. Najłatwiej to osiągnąć za pomocą $''schematu cytowania, który pozwala nam używać sekwencji specjalnych, takich jak\n reprezentować nowe wiersze (w ten sposób możesz łatwo tworzyć podstępnie nazwane katalogi i pliki).

Powyższe powinno zaspokoić twoje potrzeby zlokalizowania aktualnie działającego skryptu w systemie Linux, ale jeśli nie masz do procdyspozycji systemu plików lub próbujesz znaleźć w pełni rozwiązaną ścieżkę innego pliku, być może poniższy kod jest pomocny. To tylko niewielka modyfikacja powyższego jednowarstwowego. Jeśli grasz wokół dziwnych nazwach plików / katalogów, sprawdzając wyjście z obu lsi readlinkma charakter informacyjny, a lswyjście będzie „uproszczonej” ścieżki, zastępując ?na takie rzeczy jak nowej linii.

absolute_path=$(readlink -e -- "${BASH_SOURCE[0]}" && echo x) && absolute_path=${absolute_path%?x}
dir=$(dirname -- "$absolute_path" && echo x) && dir=${dir%?x}
file=$(basename -- "$absolute_path" && echo x) && file=${file%?x}

ls -l -- "$dir/$file"
printf '$absolute_path: "%s"\n' "$absolute_path"

Dostaję /dev/pts/30z bash na Ubuntu 14.10 Desktop.
Dan Dascalescu,

@DanDascalescu Korzystasz z jednej linijki? Lub pełny fragment kodu na dole? A czy karmiłeś to jakąś trudną ścieżką?
billyjmc,

Jedna linia plus inny linia echo $resolved, uratowałem go jako d, chmod +x d, ./d.
Dan Dascalescu,

@DanDascalescu Pierwszą linią w twoim skrypcie musi być#!/bin/bash
billyjmc

10

Spróbuj użyć:

real=$(realpath $(dirname $0))

1
Chcę tylko wiedzieć, dlaczego ten sposób nie jest dobry? Nie wydawało mi się to złe i poprawne. Czy ktoś mógłby wyjaśnić, dlaczego jest to odrzucone?
Shou Ya

7
realpath nie jest standardowym narzędziem.
Steve Bennett

2
W Linuksie realpath jest standardowym narzędziem (część pakietu GNU coreutils), ale nie jest wbudowanym bash (tj. Funkcją samego basha). Jeśli używasz Linuksa, metoda ta prawdopodobnie będzie działać, chociaż bym zastąpił $0za ${BASH_SOURCE[0]}tak, że ta metoda będzie działać w dowolnym miejscu, w tym w funkcji.
Doug Richardson,

3
Kolejność operacji w tej odpowiedzi jest nieprawidłowa. Musisz najpierw rozwiązać dowiązanie symboliczne, a następnie zrobić to , dirnameponieważ ostatnia część $0może być dowiązaniem symbolicznym wskazującym plik, który nie znajduje się w tym samym katalogu, co sam dowiązanie symboliczne. Rozwiązanie opisane w tej odpowiedzi po prostu pobiera ścieżkę do katalogu, w którym przechowywane jest dowiązanie symboliczne, a nie do katalogu docelowego. Ponadto w tym rozwiązaniu brakuje cytatów. To nie zadziała, jeśli ścieżka zawiera znaki specjalne.
hagello

3
dir="$(realpath "$(dirname "${BASH_SOURCE[0]}")")"
Kostiantyn Ponomarenko

9

Wypróbuj następujące rozwiązanie kompatybilne krzyżowo:

CWD="$(cd -P -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"

ponieważ polecenia takie jak realpathlub readlinkmogą być niedostępne (w zależności od systemu operacyjnego).

Uwaga: W Bash zaleca się stosowanie ${BASH_SOURCE[0]}zamiast $0, w przeciwnym razie ścieżka może się popsuć podczas pobierania pliku ( source/ .).

Alternatywnie możesz wypróbować następującą funkcję w bash:

realpath () {
  [[ $1 = /* ]] && echo "$1" || echo "$PWD/${1#./}"
}

Ta funkcja przyjmuje 1 argument. Jeśli argument ma już ścieżkę bezwzględną, wypisz ją taką, jaka jest, w przeciwnym razie wypisz $PWDargument zmienna + nazwa pliku (bez ./prefiksu).

Związane z:


Proszę wyjaśnić więcej na temat funkcji realpath.
Chris

1
realpathFunkcja @Chris przyjmuje 1 argument. Jeśli argument ma już ścieżkę bezwzględną, wypisz ją taką, jaka jest, w przeciwnym razie wypisz $PWD+ nazwa pliku (bez ./prefiksu).
kenorb

Twoje rozwiązanie kompatybilne nie działa, gdy skrypt jest dowiązaniem symbolicznym.
Jakub Jirutka

9

Myślę, że mam to. Spóźniam się na przyjęcie, ale myślę, że niektórzy docenią to, że będą tutaj, jeśli natkną się na ten wątek. Komentarze powinny wyjaśniać:

#!/bin/sh # dash bash ksh # !zsh (issues). G. Nixon, 12/2013. Public domain.

## 'linkread' or 'fullpath' or (you choose) is a little tool to recursively
## dereference symbolic links (ala 'readlink') until the originating file
## is found. This is effectively the same function provided in stdlib.h as
## 'realpath' and on the command line in GNU 'readlink -f'.

## Neither of these tools, however, are particularly accessible on the many
## systems that do not have the GNU implementation of readlink, nor ship
## with a system compiler (not to mention the requisite knowledge of C).

## This script is written with portability and (to the extent possible, speed)
## in mind, hence the use of printf for echo and case statements where they
## can be substituded for test, though I've had to scale back a bit on that.

## It is (to the best of my knowledge) written in standard POSIX shell, and
## has been tested with bash-as-bin-sh, dash, and ksh93. zsh seems to have
## issues with it, though I'm not sure why; so probably best to avoid for now.

## Particularly useful (in fact, the reason I wrote this) is the fact that
## it can be used within a shell script to find the path of the script itself.
## (I am sure the shell knows this already; but most likely for the sake of
## security it is not made readily available. The implementation of "$0"
## specificies that the $0 must be the location of **last** symbolic link in
## a chain, or wherever it resides in the path.) This can be used for some
## ...interesting things, like self-duplicating and self-modifiying scripts.

## Currently supported are three errors: whether the file specified exists
## (ala ENOENT), whether its target exists/is accessible; and the special
## case of when a sybolic link references itself "foo -> foo": a common error
## for beginners, since 'ln' does not produce an error if the order of link
## and target are reversed on the command line. (See POSIX signal ELOOP.)

## It would probably be rather simple to write to use this as a basis for
## a pure shell implementation of the 'symlinks' util included with Linux.

## As an aside, the amount of code below **completely** belies the amount
## effort it took to get this right -- but I guess that's coding for you.

##===-------------------------------------------------------------------===##

for argv; do :; done # Last parameter on command line, for options parsing.

## Error messages. Use functions so that we can sub in when the error occurs.

recurses(){ printf "Self-referential:\n\t$argv ->\n\t$argv\n" ;}
dangling(){ printf "Broken symlink:\n\t$argv ->\n\t"$(readlink "$argv")"\n" ;}
errnoent(){ printf "No such file: "$@"\n" ;} # Borrow a horrible signal name.

# Probably best not to install as 'pathfull', if you can avoid it.

pathfull(){ cd "$(dirname "$@")"; link="$(readlink "$(basename "$@")")"

## 'test and 'ls' report different status for bad symlinks, so we use this.

 if [ ! -e "$@" ]; then if $(ls -d "$@" 2>/dev/null) 2>/dev/null;  then
    errnoent 1>&2; exit 1; elif [ ! -e "$@" -a "$link" = "$@" ];   then
    recurses 1>&2; exit 1; elif [ ! -e "$@" ] && [ ! -z "$link" ]; then
    dangling 1>&2; exit 1; fi
 fi

## Not a link, but there might be one in the path, so 'cd' and 'pwd'.

 if [ -z "$link" ]; then if [ "$(dirname "$@" | cut -c1)" = '/' ]; then
   printf "$@\n"; exit 0; else printf "$(pwd)/$(basename "$@")\n"; fi; exit 0
 fi

## Walk the symlinks back to the origin. Calls itself recursivly as needed.

 while [ "$link" ]; do
   cd "$(dirname "$link")"; newlink="$(readlink "$(basename "$link")")"
   case "$newlink" in
    "$link") dangling 1>&2 && exit 1                                       ;;
         '') printf "$(pwd)/$(basename "$link")\n"; exit 0                 ;;
          *) link="$newlink" && pathfull "$link"                           ;;
   esac
 done
 printf "$(pwd)/$(basename "$newlink")\n"
}

## Demo. Install somewhere deep in the filesystem, then symlink somewhere 
## else, symlink again (maybe with a different name) elsewhere, and link
## back into the directory you started in (or something.) The absolute path
## of the script will always be reported in the usage, along with "$0".

if [ -z "$argv" ]; then scriptname="$(pathfull "$0")"

# Yay ANSI l33t codes! Fancy.
 printf "\n\033[3mfrom/as: \033[4m$0\033[0m\n\n\033[1mUSAGE:\033[0m   "
 printf "\033[4m$scriptname\033[24m [ link | file | dir ]\n\n         "
 printf "Recursive readlink for the authoritative file, symlink after "
 printf "symlink.\n\n\n         \033[4m$scriptname\033[24m\n\n        "
 printf " From within an invocation of a script, locate the script's "
 printf "own file\n         (no matter where it has been linked or "
 printf "from where it is being called).\n\n"

else pathfull "$@"
fi

8

Oto krótkie sposoby na uzyskanie informacji o skrypcie:

Foldery i pliki:

    Script: "/tmp/src dir/test.sh"
    Calling folder: "/tmp/src dir/other"

Za pomocą tych poleceń:

    echo Script-Dir : `dirname "$(realpath $0)"`
    echo Script-Dir : $( cd ${0%/*} && pwd -P )
    echo Script-Dir : $(dirname "$(readlink -f "$0")")
    echo
    echo Script-Name : `basename "$(realpath $0)"`
    echo Script-Name : `basename $0`
    echo
    echo Script-Dir-Relative : `dirname "$BASH_SOURCE"`
    echo Script-Dir-Relative : `dirname $0`
    echo
    echo Calling-Dir : `pwd`

I mam ten wynik:

     Script-Dir : /tmp/src dir
     Script-Dir : /tmp/src dir
     Script-Dir : /tmp/src dir

     Script-Name : test.sh
     Script-Name : test.sh

     Script-Dir-Relative : ..
     Script-Dir-Relative : ..

     Calling-Dir : /tmp/src dir/other

Zobacz także: https://pastebin.com/J8KjxrPF



Myślę, że moja odpowiedź jest dobra, ponieważ trudno jest znaleźć proste wydanie robocze. Tutaj możesz wziąć kod, który ci się podoba, np. Cd + pwd, dirname + realpath lub dirname + readlink. Nie jestem pewien, czy wszystkie części istniały wcześniej, a większość odpowiedzi jest złożona i przeciążona. Tutaj możesz znaleźć kod, którego chcesz użyć. Przynajmniej proszę nie usuwaj go tak, jak potrzebuję w przyszłości: D
User8461

8

Działa to w bash-3.2:

path="$( dirname "$( which "$0" )" )"

Jeśli masz ~/binkatalog w swoim katalogu $PATH, znajdziesz go Aw tym katalogu. Źródła skryptu ~/bin/lib/B. Wiesz, gdzie dołączony skrypt jest względny w stosunku do oryginalnego, w libpodkatalogu, ale nie w którym jest odniesiony do bieżącego katalogu użytkownika.

Rozwiązuje to następujące (wewnątrz A):

source "$( dirname "$( which "$0" )" )/lib/B"

Nie ma znaczenia, gdzie jest użytkownik ani jak wywołuje skrypt, to zawsze zadziała.


3
Sprawa whichjest bardzo dyskusyjna. type, hashi inne wbudowane funkcje robią to samo lepiej w bash. whichjest trochę bardziej przenośny, chociaż tak naprawdę nie jest taki sam, jak whichw innych powłokach, takich jak tcsh, który ma go jako wbudowany.
Przywróć Monikę, proszę

"Zawsze"? Ani trochę. whichbędąc narzędziem zewnętrznym, nie ma powodu, by sądzić, że zachowuje się identycznie jak powłoka nadrzędna.
Charles Duffy,

7

Moim zdaniem najlepszym kompaktowym rozwiązaniem byłoby:

"$( cd "$( echo "${BASH_SOURCE[0]%/*}" )"; pwd )"

Nie można polegać na niczym innym niż Bash. Zastosowanie dirname, readlinka basenamew konsekwencji doprowadzi do problemów z kompatybilnością, więc są one najlepiej unikać, jeśli to w ogóle możliwe.


2
Prawdopodobnie należy dodać ukośnik się, że: "$( cd "$( echo "${BASH_SOURCE[0]%/*}/" )"; pwd )". Jeśli nie masz problemów z katalogiem głównym. Również dlaczego w ogóle musisz używać echa?
konsolebox

dirnamei basenamesą znormalizowane POSIX, więc dlaczego ich unikać? Linki: dirname,basename
myrdd

Jednym z powodów może być zapobieganie dwóm dodatkowym procesorom procesowym i przyklejanie się do wbudowanych powłok.
Amit Naidu
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.