Bash betiğinin kaynak dizinini betiğin içinden alma


4950

Bash betiğinin bulunduğu dizinin yolunu bu betiğin içine nasıl alırım ?

Bash betiğini başka bir uygulama için başlatıcı olarak kullanmak istiyorum. Bash betiğinin bulunduğu dizine çalışma dizinini değiştirmek istiyorum, böylece bu dizindeki dosyalar üzerinde çalışabilirim:

$ ./application

69
Dizin adının sonunda herhangi bir satırsonu varsa, geçerli çözümlerin hiçbiri çalışmaz - Komut yerine koymakla çıkarılırlar. Bu soruna geçici bir çözüm bulmak için, komut değiştirme yerine yeni satır olmayan bir karakter ekleyebilir - DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd && echo x)"- ve komut değiştirme olmadan kaldırabilirsiniz DIR="${DIR%x}".
l0b0

80
@ jpmc26 Çok yaygın iki durum vardır: Kazalar ve sabotaj. Bir komut dosyası, birilerinin bir yerlerde, bir şey yapması nedeniyle öngörülemeyen şekillerde başarısız olmamalıdır mkdir $'\n'.
l0b0

24
İnsanların sistemlerini bu şekilde sabote etmesine izin veren herkes, bu tür sorunları tespit etmek için onu bash'a bırakmamalıdır ... bu tür bir hata yapabilen insanları çok daha az işe alır. Ben bash kullanarak 25 yıl içinde, bu tür bir şey her yerde olduğunu hiç görmedim .... bu yüzden perl gibi şeyler ve leke kontrolü gibi uygulamalar var (muhtemelen bunu söylemek için alev olacak :)
osirisgothra

3
@ l0b0 Aynı korumanın gerekeceğini dirnameve dizinin bir -(ör. --help) ile başlayabileceğini düşünün . DIR=$(reldir=$(dirname -- "$0"; echo x); reldir=${reldir%?x}; cd -- "$reldir" && pwd && echo x); DIR=${DIR%?x}. Belki bu aşırıya kaçmadır?
Score_Under

55
Konuyla ilgili bu Bash SSS'sini okumanızı şiddetle tavsiye ediyorum .
Rany Albeg Wein

Yanıtlar:


6569
#!/bin/bash

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

nereden çağrılırsa kullanılsın size komut dosyasının tam dizin adını verecek kullanışlı bir tek satırlıktır.

Komut dosyasını bulmak için kullanılan yolun son bileşeni bir simge bağlantısı olmadığı sürece çalışır (dizin bağlantıları uygundur). Komut dosyasının kendisine olan bağlantıları da çözmek istiyorsanız, çok satırlı bir çözüme ihtiyacınız vardır:

#!/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 )"

Bu sonuncusu takma adları, herhangi bir kombinasyonu ile çalışacaktır source, bash -c, sembolik bağlar, vb

Dikkat: cdBu pasajı çalıştırmadan önce farklı bir dizine geçerseniz , sonuç yanlış olabilir!

Ayrıca, kullanıcı çıktıyı stderr'e yönlendirmek için akıllıca geçersiz $CDPATHkıldıysa gotchas ve stderr çıktı yan etkilerine dikkat edin ( update_terminal_cwd >&2Mac'te arama yaparken olduğu gibi kaçış dizileri dahil ). Komutunuzun >/dev/null 2>&1sonuna eklemek , cdher iki olasılığı da dikkate alacaktır.

Nasıl çalıştığını anlamak için bu daha ayrıntılı formu çalıştırmayı deneyin:

#!/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'"

Ve şöyle bir şey basacaktır:

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
Sen bir çözüme olduğu eserler gelmesi user25866 tarafından cevap bu yaklaşımı kaynaştırır source <script>ve bash <script>: DIR="$(cd -P "$(dirname "${BASH_SOURCE[0]}")" && pwd)".
Dan Moulding

21
Bazen cdSTDOUT'a bir şeyler yazdırır! Örneğin, sizin eğer $CDPATHsahiptir .. Bu vakayı karşılamak içinDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" > /dev/null && pwd )"
user716468

182
Bu kabul edilen cevap iyi değil, sembollerle çalışmaz ve aşırı derecede karmaşıktır. dirname $(readlink -f $0)doğru komuttur. Bir testcase için gist.github.com/tvlooy/cbfbdb111a4ebad8b93e adresine bakın
tvlooy

167
@tvlooy IMO cevabınız tam olarak olduğu gibi değil, çünkü yolda bir boşluk olduğunda başarısız oluyor. Yeni satır karakterinin aksine, bu olası veya hatta nadir değildir. dirname "$(readlink -f "$0")"karmaşıklık katmaz ve en az sorun için adil bir önlem daha sağlamdır.
Adrian Günter

10
@tvlooy yorumunuz macOS (veya büyük olasılıkla genel olarak BSD) uyumlu değil, kabul edilen cevap ise. readlink -f $0verir readlink: illegal option -- f.
Alexander Ljungberg

875

Kullanım dirname "$0":

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

pwdbetiği içerdiği dizinden çalıştırmıyorsanız, yalnız kullanmak çalışmaz.

[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
Bash ötesinde taşınabilirlik için, 0 $ her zaman yeterli olmayabilir. Komut yolda bulunmuşsa, bu çalışmayı gerçekleştirmek için "type -p $ 0" yerine geçmeniz gerekebilir.
Darron

10
@Darron: yalnızca type -pkomut dosyası çalıştırılabilir olduğunda kullanabilirsiniz . Komut dosyası kullanılarak yürütülürse bash test2.shve başka bir yerde aynı ada sahip başka bir komut dosyası varsa, bu da küçük bir delik açabilir .
D.Shawley

90
@Darron: ama soru etiketlendiğinden bashve hash-bang satırından açıkça bahsettiğim /bin/bashiçin bashisms'e bağımlı olmanın oldukça güvenli olduğunu söyleyebilirim.
Joachim Sauer

34
+1, ancak kullanımdaki sorun dirname $0, dizin geçerli dizinse, alacağınızdır .. Komut dosyasındaki dizinleri değiştirmeyip, aldığınız yolu mutlakmış dirname $0gibi kullanmayı beklemediğiniz sürece sorun olmaz . : Mutlak yolunu almak için pushd `dirname $0` > /dev/null, SCRIPTPATH=`pwd`, popd > /dev/null: pastie.org/1489386 (Ama elbette bu yolu genişletmek için daha iyi bir yolu var?)
TJ Crowder

9
@TJ Crowder dirname $0Bir değişkene atar ve sonra böyle bir komut dosyası başlatmak için kullanırsanız bir sorun olduğundan emin değilim $dir/script.sh; Bu tür bir şey için% 90 zamanın kullanım durumu olduğunu düşünürdüm. ./script.shiyi çalışır.
matt b

515

dirnameKomut en temel, basit kapalı dosya yolunu yukarı ayrıştırma olduğu $0(script adı) değişken:

dirname "$0"

Ancak, matt b'nin işaret ettiği gibi , döndürülen yol komut dosyasının nasıl adlandırıldığına bağlı olarak farklıdır. pwdbu sadece geçerli dizinin ne olduğunu, komut dosyasının hangi dizinde bulunduğunu söylemediğinden işi yapmaz. Ayrıca, bir komut dosyasına sembolik bir bağlantı yürütülürse, bağlantının bulunduğu yer, gerçek komut dosyası değil.

Bazıları readlinkkomuttan bahsetti , ancak en basit haliyle şunları kullanabilirsiniz:

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

readlinkkomut dosyasının yolunu, dosya sisteminin kökünden mutlak bir yola çözümleyecektir. Böylece, tek veya çift nokta, tildes ve / veya sembolik bağlantılar içeren yollar tam bir yola çözülecektir.

İşte bunların her birini gösteren bir senaryo 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))"

Göreli bir yol kullanarak bu komut dosyasını ev dizinimde çalıştırma:

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

Yine de, betiğin tam yolunu kullanarak:

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

Şimdi dizinleri değiştirme:

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

Ve son olarak betiği çalıştırmak için sembolik bir bağlantı kullanarak:

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

13
readlinkvarsayılan kurulumda bazı platformlarda kullanılamaz. Mümkünse kullanmaktan kaçının
TL

43
boşluk sorunlarını önlemek için her şeyi alıntı için dikkatli olun:export SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
Catskul

13
OSX'te Yosemite 10.10.1 -fiçin bir seçenek olarak tanınmadı readlink. Kullanılması stat -fyerine işi yapar. Thanks
cucu8

11
OSX'te, greadlinktemelde readlinkhepimiz tanıdık olan şey vardır. İşte platform bağımsız bir sürümü:dir=`greadlink -f ${BASH_SOURCE[0]} || readlink -f ${BASH_SOURCE[0]}`
Robert

6
İyi işti, @robert. FYI, greadlinkhomebrew ile kolayca kurulabilir: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

Aşağıdakiler dahil tüm sürümler için çalışır:

  • çok derinlikli yumuşak bağlantı ile çağrıldığında,
  • dosya ne zaman
  • komut " source" aka .(nokta) operatörü tarafından çağrıldığında .
  • arg $0, arayandan değiştirildiğinde.
  • "./script"
  • "/full/path/to/script"
  • "/some/path/../../another/path/script"
  • "./some/folder/script"

Alternatif olarak, bash betiğinin kendisi göreceli bir sembolik bağlantıysa onu takip etmek ve bağlantılı komut dosyasının tam yolunu döndürmek istersiniz :

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_PATHnasıl adlandırılırsa adlandırılsın tam yol olarak verilir.
Bunu komut dosyasının başlangıcında bulduğunuzdan emin olun.

Bu yorum ve kod Copyleft, GPL2.0 veya üstü veya CC-SA 3.0 (CreativeCommons Paylaşımı) veya üstü altında seçilebilir lisans. (c) 2008. Tüm hakları saklıdır. Hiçbir garanti yok. Uyarıldın.
http://www.gnu.org/licenses/gpl-2.0.txt
http://creativecommons.org/licenses/by-sa/3.0/
18eedfe1c99df68dc94d4a94712a71aaa8e1e9e36cacf421b9463dd2bbaa02906d0d6656


4
Güzel! "Pushd [...] popd / dev / null" yerine SCRIPT_PATH = readlink -f $(dirname "${VIRTUAL_ENV}");
e-satis

5
Ve pushd yerine ...; $ (cd dirname "${SCRIPT_PATH}"&& pwd) kullanmak daha iyi olmaz mıydı ? Ama yine de harika bir senaryo!
Ağustos'ta Ovanes

6
Bir komut dosyasının daha sonra tekrar geri dönme cdumuduyla mevcut dizininden çıkması tehlikelidir cd: Kod, çağrıldığında geçerli olan dizine geri değiştirme iznine sahip olmayabilir. (Aynı şey pushd / popd için de geçerli)
Adrian Pronk

7
readlink -fGNU'ya özgüdür. BSD'nin readlinkbu seçeneği yoktur.
Kara Brightwell

3
Tüm gereksiz alt kabuklarda ne var? ([ ... ])daha az verimlidir [ ... ]ve burada gerçekleşen performans için sunulan izolasyondan hiçbir avantajı yoktur.
Charles Duffy

110

Kısa cevap:

`dirname $0`

veya ( tercihen ):

$(dirname "$0")

17
Senaryoyu kaynak yaparsanız çalışmaz. "source my / script.sh"
Arunprasad Rajkumar

Bunu her zaman bash scriptlerimde otomatikleştiriyorum ve çoğu zaman aynı direkte diğer betikleri çağırıyorum. Bunları asla kullanmam sourceve cd $(dirname $0)hatırlaması kolay.
kqw

16
@vidstige: ${BASH_SOURCE[0]}yerine $0çalışacaksource my/script.sh
Timothy Jones

@TimothyJones, bash dışında başka bir kabuktan kaynaklanırsa zamanın% 100'ü başarısız olur. ${BASH_SOURCE[0]}hiç tatmin edici değil. ${BASH_SOURCE:-0}daha iyi.
Mathieu CAROFF

106

Şunları kullanabilirsiniz $BASH_SOURCE:

#!/bin/bash

scriptdir=`dirname "$BASH_SOURCE"`

Bir Bash uzantısı olduğundan #!/bin/bashdeğil kullanmanız gerektiğini unutmayın #!/bin/sh.


14
Ben ne zaman ./foo/script, o zaman $(dirname $BASH_SOURCE)olduğunu ./foo.
Till

1
@Till, Bu durumda realpath./foo/script dosyasının tam yolunu almak için command komutunu kullanabiliriz . Böylece dirname $(realpath ./foo/script) senaryo yolunu verecektir.
purushothaman poovai

73

Bunu yapmalı:

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

Bu, yoldaki semboller ve boşluklarla çalışır.

Man sayfalarına bakın dirnameve readlink.

Yorum parçasından Mac OS ile çalışmıyor gibi görünüyor. Bunun neden olduğu hakkında hiçbir fikrim yok. Herhangi bir öneri?


6
çözümünüzle birlikte, komut dosyasını tam dizin yolu yerine ./script.shşovlar gibi çağırmak.
Bruno Negrão Zica

5
MacOS'ta readlink için -f seçeneği yoktur. statBunun yerine kullanın . Ama yine de, .'bu' dir'de olup olmadığınızı gösterir .
Denis The Menace

2
coreutilsHomebrew'dan yüklemeniz ve MacOS'ta seçeneği greadlinkalmak için kullanmanız gerekir -fçünkü Linux'un değil kapakların altında * BSD'dir.
dragon788

Tüm sağ tarafı çevreleyen çift tırnak eklemelisiniz:DIR="$(dirname "$(readlink -f "$0")")"
hagello

60

pwdgeçerli çalışma dizinini dirnamebulmak ve belirli bir dosyanın dizinini bulmak için kullanılabilir (çalıştırılan komut $0, yani dirname $0geçerli komut dosyasının dizinini vermelidir).

Ancak, dirnametam olarak geçerli çalışma dizinine göreceli olmayacak olan dosya adının dizin bölümünü verir. Komut dosyanızın herhangi bir nedenle dizini değiştirmesi gerekiyorsa, çıktısı dirnameanlamsız olur.

Aşağıdakileri öneririm:

#!/bin/bash

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

echo "Directory is $directory"

Bu şekilde, göreceli bir dizinden ziyade, mutlak bir dizin elde edersiniz.

Komut dosyası ayrı bir bash örneğinde çalışacağından, daha sonra çalışma dizinini geri yüklemeye gerek yoktur, ancak herhangi bir nedenle komut dosyanızda geri dönmek isterseniz, sizden pwdönce bir değişkenin değerini kolayca atayabilirsiniz. ileride kullanmak için dizini değiştirin.

Sadece

cd `dirname $0`

sorudaki belirli senaryoyu çözer, genel olarak daha yararlı olan mutlak yolu buluyorum.


9
Hepsini böyle bir satırda yapabilirsiniz: DIRECTORY = $ (cd dirname $0&& pwd)
dogbane

Komut dosyası başka bir komut dosyası kaynaklıyorsa ve komut dosyasının adını bilmek istiyorsanız bu çalışmaz.
reinierpost 28:14

52

Tek astarı kabul edilen cevapta kopyalamak için bu sayfaya tekrar tekrar gelmekten bıktım. Sorun şu ki, anlamak ve hatırlamak kolay değil.

İşte hatırlaması kolay bir komut dosyası:

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

2
Veya, daha belirsiz bir şekilde, bir satırda: DIR=$(realpath "$(dirname "${BASH_SOURCE[0]}")")
agc

Bu neden kabul edilen cevap değil? realpath"El ile" bir döngü ile çözmekten herhangi bir fark var mı readlink? readlinkMan sayfası bile diyorNote realpath(1) is the preferred command to use for canonicalization functionality.
User9123

1
Ve bu arada daha realpathönce başvurmamalıyız dirname, sonra değil mi? Eğer komut dosyasının kendisi bir symlink ise ... Gibi bir şey verirdi DIR="$(dirname "$(realpath "${BASH_SOURCE[0]}")")". Aslında Simon'un önerdiği cevaba çok yakın.
User9123

@ User9123 Bence kabul et tüm popüler kabuk / dağıtım ile uyumlu olmaya çalışın. Daha fazla, ne yapmaya çalıştığınıza bağlı olarak, çoğu durumda insanlar gerçek kaynağın dizini yerine symlink'in bulunduğu dizini almak isterler.
Wang

37

Bunun diğerlerinin yaptığı kadar kolay olduğunu sanmıyorum. pwdgeçerli dizin komut dosyası içeren dizin olması gerekmediği için çalışmaz. $0her zaman bilgiye sahip değildir. Bir komut dosyasını çağırmanın aşağıdaki üç yolunu göz önünde bulundurun:

./script

/usr/bin/script

script

Birinci ve üçüncü yollarda $0tam yol bilgisi yoktur. İkinci ve üçüncü olarak pwdçalışmaz. Dizini üçüncü şekilde almanın tek yolu, yolu çalıştırmak ve doğru eşleşmeli dosyayı bulmak olacaktır. Temel olarak kod, işletim sisteminin yaptıklarını yeniden yapmak zorunda kalacaktır.

İstediğinizi yapmanın bir yolu, /usr/sharedizindeki verileri sabit olarak kodlamak ve tam yoluna başvurmaktır . Veriler /usr/binyine de dizinde olmamalı , bu yüzden muhtemelen yapılacak şey budur.


9
Yorumunu reddetmek istiyorsanız, bir komut dosyasının kod örneğiyle depolandığı yere erişebileceğini kanıtlayın.
Richard Duerr

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

Bu seçilen cevaptan çok daha kısadır. Ve aynı şekilde çalışıyor gibi görünüyor. Bu, 1000 oyu hak ediyor, bu yüzden insanlar gözden kaçırmıyor.
Patrick

2
Önceki yanıtların çoğunun ayrıntılı olarak açıkladığı gibi , komut dosyasının nasıl çağrıldığına bağlı olarak ne doğru bilgiye sahip oldukları ne $0de pwdgaranti edilemez.
IMSoP

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

Ben, tercih $BASH_SOURCEüzerinde $0bile bash konusunda çok bilgili değil okuyucular için açık olduğundan,. $(dirname -- "$(readlink -f -- "$BASH_SOURCE")")
blobmaster

32

Bu, Mac OS X 10.6.6'daki geçerli çalışma dizinini alır:

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

27

Bu Linux'a özgüdür, ancak şunları kullanabilirsiniz:

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

1
Ayrıca bash'e özgü, ama belki bash'ın davranışı değişti? /proc/fd/$$/255bir dizine değil, tty'ye işaret ediyor gibi görünüyor. Örneğin, geçerli oturum açma kabuğumda 0, 1, 2 ve 255 dosya tanımlayıcılarının tümü başvuruyor /dev/pts/4. Her durumda, bash kılavuzu fd 255'ten bahsetmez, bu nedenle muhtemelen bu davranışa bağlı olmak mantıksızdır. \
Keith Thompson

2
Etkileşimli kabuk! = Komut dosyası. Her neyse realpath ${BASH_SOURCE[0]};, gitmek için en iyi yol gibi görünüyor.
Steve Baker

23

İşte POSIX uyumlu bir astar:

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

# test
echo $SCRIPT_PATH

4
Bir komut dosyasını tek başına veya sudo kullanarak çalıştırırken bunu başardım, ancak kaynak ./script.sh
Michael R

Ve cdyeni yol adını yazdırmak için yapılandırıldığında başarısız olur .
Aaron Digulla

18

Bunların hepsini denedim ve hiçbiri işe yaramadı. Biri çok yakın ama kötü kırdı küçük bir hata vardı; yolu tırnak içine almayı unuttular.

Ayrıca birçok kişi, komut dosyasını bir kabuktan çalıştırdığınızı varsayar; böylece, varsayılan olarak evinize varsayılan yeni bir komut dosyası açtığınızda unuturlar.

Boyut için bu dizini deneyin:

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

Bu, nasıl veya nerede çalıştırdığınızdan bağımsız olarak doğru olur:

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

Yani gerçekten kullanışlı hale getirmek için çalışan komut dosyasının dizinine nasıl geçeceğiniz aşağıda açıklanmıştır:

cd "`dirname "$0"`"

4
Komut dosyası başka bir komut dosyasından kaynaklanıyorsa çalışmaz.
reinierpost 28:14

$ 0'ın son kısmı başka bir dizinin ( ln -s ../bin64/foo /usr/bin/foo) girişine işaret eden sembolik bir bağlantıysa bu çalışmaz .
hagello

17

İşte basit ve doğru yol:

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

Açıklama:

  • ${BASH_SOURCE[0]}- komut dosyasının tam yolu. Bunun değeri, komut dosyası kaynaklanırken bile doğru olacaktır, örn. Bashsource <(echo 'echo $0') yazdırır , bununla değiştirilirken komut dosyasının tam yolu yazdırılır. (Tabii ki, bu Bash'e bağımlılık konusunda iyi olduğunuzu varsayar.)${BASH_SOURCE[0]}

  • readlink -f- Belirtilen yoldaki sembol bağlantılarını yinelemeli olarak çözer. Bu bir GNU oluşumudur ve (örneğin) BSD sistemlerinde mevcut değildir. Mac çalıştırıyorsanız, GNU'yu yüklemek coreutilsve bunun yerine geçmek için Homebrew'u kullanabilirsiniz greadlink -f.

  • Ve elbette dirnameyolun üst dizinini alır.


1
greadlink -fsourceMac'te senaryoyu yazarken maalesef etkili bir şekilde çalışmıyor :(
Gabe Kopley


16

Ben böyle bir şey kullanabilirsiniz:

# 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

Bu gerçek olan! shÇok basit ile çalışır ! Basit dirname "$0"tabanlı çözümlerle ilgili sorun : Komut dosyası $PATHiçindeyse ve yol olmadan çağrılırsa, yanlış sonuç verir.
Notinlist

@Notinlist Öyle değil. Komut yoluyla bulunursa PATH, $0mutlak dosya adı içerecektir. Komut dosyası, a içeren göreli veya mutlak bir dosya adıyla çağrılırsa /, $0bunu içerir.
Neil Mayhew

Kaynaklı bir komut dosyası için çalışmaz.
Amit Naidu

16

Bu işaret çözüm, e-satis ve 3bcdnlklvc04a hafif bir revizyon olduğunu verdikleri yanıta :

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

Bu, listelendikleri tüm durumlarda hala çalışmalıdır.

Bu, konsolebox sayesinde popdbaşarısız olduktan sonra pushdönleyecektir.


Bu, yalnızca bir sembolik bağlantının adı yerine "gerçek" dizin adını almak için mükemmel bir şekilde çalışır. Teşekkür ederim!
Jay Taylor

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

@konsolebox, neye karşı savunmaya çalışıyorsun? Genelde satır içi mantıksal koşulların hayranıyım, ancak pushd'de gördüğünüz belirli hata neydi? Boş bir SCRIPT_DIR döndürmek yerine doğrudan ele almak için bir yol bulmak yerine maç.
Fuwjax

@Fuwjax Başarısız popdolduğu durumlarda (nadiren bile olsa) yapmaktan kaçınmak için doğal uygulama pushd. Ve pushdbaşarısız olursa, değeri ne olmalı SCRIPT_DIR? İşlem, neyin mantıklı görünebileceğine veya bir kullanıcının neyi tercih edebileceğine bağlı olarak değişebilir, ancak kesinlikle popdyanlıştır.
konsolebox

Tüm bu pushd popdtehlikeler, sadece düşürülerek ve bunun yerine bir komut yerine cd+ yerleştirilerek önlenebilir pwd. SCRIPT_DIR=$(...)
Amit Naidu

16

GNU coreutils'e sahip sistemler için readlink(örn. Linux):

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

Kullanmaya gerek yoktur BASH_SOURCEzaman $0komut dosyası dosya adını içerir.


2
komut dosyası kaynaklanmadıkça. veya 'source' (bu durumda, komut dosyası tarafından sağlanan herhangi bir kod olmaya devam eder) veya komut satırından '-bash' (tty login) veya 'bash' ('bash -l' ile çağrılır) veya '/ bin / bash '(etkileşimli ve
girişsiz bir

dirnameÇağrıya ikinci çift tırnak ekledim . Dizin yolu boşluk içeriyorsa gereklidir.
user1338062

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

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

scriptdir=`dirname "$PRG"`

Farklı sistemlerde test etmedim. Ama bu çözüm benim için en azından Ubuntu üzerinde çalışan çözüm!
Natus Drew

$0kaynaklı bir senaryo için çalışmayacak
Amit Naidu

13

$_bir alternatif olarak bahsetmeye değer $0. Bash'ten bir komut dosyası çalıştırıyorsanız, kabul edilen cevap kısaltılabilir:

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

Bunun kodunuzdaki ilk ifade olması gerektiğini unutmayın.


4
Sen sourceveya .senaryonun sonu. Bu durumlarda ,. $_Komutundan önce çalıştırdığınız son komutun son parametresini içerir .. $BASH_SOURCEher zaman çalışır.
clacke

11

Verilen cevapların çoğunu karşılaştırdım ve daha kompakt çözümler ürettim. Bunlar, en sevdiğiniz kombinasyondan kaynaklanan tüm çılgın kenar vakalarını ele alıyor gibi görünüyor:

  • Mutlak yollar veya göreli yollar
  • Dosya ve dizin yazılım bağlantıları
  • Çağırma olarak script, bash script, bash -c script, source script, ya da. script
  • Dizinlerde ve / veya dosya adında boşluklar, sekmeler, yeni satırlar, unicode vb.
  • Kısa çizgi ile başlayan dosya adları

Linux'tan çalışıyorsanız proc, şu anda çalışan komut dosyasının tam çözümlenmiş kaynağını bulmak için tanıtıcıyı kullanmanın en iyi çözüm olduğu görülmektedir (etkileşimli bir oturumda, bağlantı ilgili olanı işaret eder /dev/pts/X):

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

Bunun biraz çirkinliği var, ancak düzeltme kompakt ve anlaşılması kolay. Sadece bash ilkellerini kullanmıyoruz, ama bununla iyiyim çünkü readlinkgörevi önemli ölçüde basitleştiriyor. echo XBir ekleyen Xdosya herhangi izleyen boşluk yemiş almaz ve parametre ikamesi böylece değişken dizesinin sonuna ${VAR%X}satırın sonunda kurtulur X. Çünkü readlink(normalde değilse önceki oyununa gelen komut yerine yemiş olurdu) kendine ait bir yeni satır ekler, biz de o kurtulmak zorundayız. Bu, en kolay şekilde $'', aşağıdaki gibi kaçış dizilerini kullanmamızı sağlayan alıntı şeması kullanılarak gerçekleştirilir .\n yeni satırları temsil etmek için (bu aynı zamanda adsız dizinleri ve dosyaları kolayca nasıl oluşturabilirsiniz).

Yukarıdakiler, Linux'ta şu anda çalışan betiği bulma gereksinimlerinizi kapsamalıdır, ancak procdosya sisteminiz elinizde değilse veya başka bir dosyanın tamamen çözülmüş yolunu bulmaya çalışıyorsanız, belki aşağıdaki kodu yararlı bulabilirsiniz. Yukarıdaki tek astardan sadece küçük bir değişiklik. Garip dizin / dosya adları ile oynuyorsanız, çıktıyı her ikisiyle de kontrol edin lsve readlinkbilgilendirici, yeni satırlar gibi şeylerin lsyerine "basitleştirilmiş" yollar ?çıkarır.

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"

/dev/pts/30Ubuntu 14.10 Masaüstünde bash ile alıyorum .
Dan Dascalescu

@DanDascalescu Tek astarı mı kullanıyorsunuz? Veya alt kısımdaki tam kod snippet'ini mi? Ve herhangi bir zor yol ismi mi besliyorsunuz?
billyjmc

Bir satır artı başka hat echo $resolvedı olarak kaydedilmiş, d, chmod +x d, ./d.
Dan Dascalescu

@DanDascalescu Senaryonuzdaki ilk satır olması gerekiyor#!/bin/bash
billyjmc

10

Kullanmayı deneyin:

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

1
Tek bilmek istediğim, neden bu yol iyi değil? Benim için kötü ve doğru görünmüyordu. Herkes neden indirildiğini açıklayabilir mi?
Shou Ya

7
realpath standart bir yardımcı program değildir.
Steve Bennett

2
Linux'ta realpath standart bir yardımcı programdır (GNU coreutils paketinin bir parçasıdır), ancak yerleşik bir bash değildir (yani bash tarafından sağlanan bir işlev). Linux çalıştırıyorsanız, bu yöntem muhtemelen işe yarayacaktır, ancak for'un yerini alacaktım $0, ${BASH_SOURCE[0]}böylece bu yöntem bir işlev de dahil olmak üzere her yerde çalışacak.
Doug Richardson

3
Bu cevaptaki işlemlerin sırası yanlış. Buna gerek ilk , kararlılıkla sembolik sonra yapılacak dirnameson parçası çünkü $0bir sembolik olabileceğini kendisini sembolik köprüyü aynı dizinde olmayan bir dosyaya işaret eder. Bu cevapta açıklanan çözüm, hedefin dizinini değil, sadece sembolik linkin bulunduğu dizinin yolunu alır. Ayrıca, bu çözümde alıntı eksik. Yol özel karakterler içeriyorsa çalışmaz.
hagello

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

9

Aşağıdaki çapraz uyumlu çözümü deneyin:

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

gibi realpathveya readlinkkullanılamıyor gibi komutlar (işletim sistemine bağlı olarak).

Not: Bash'te ${BASH_SOURCE[0]}bunun yerine kullanılması önerilir $0, aksi takdirde dosya ( source/ .) kaynaklanırken yol kesilebilir .

Alternatif olarak bash'da aşağıdaki işlevi deneyebilirsiniz:

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

Bu işlev 1 argüman alır. Bağımsız değişken zaten mutlak bir yola sahipse, olduğu gibi yazdırın, aksi takdirde $PWDdeğişken + dosya adı bağımsız değişkenini ( ./önek olmadan ) yazdırın .

İlişkili:


Lütfen realpath işlevi hakkında daha fazla bilgi verin.
Chris

1
@Chris realpathişlevi 1 argüman alır. Bağımsız değişken zaten mutlak bir yola sahipse, olduğu gibi yazdırın, aksi takdirde $PWD+ dosya adı ( ./önek olmadan ) yazdırın .
kenorb

Komut dosyası bağlandığında çapraz uyumlu çözümünüz çalışmaz.
Jakub Jirutka

9

Buna sahip olduğuma inanıyorum. Partiye geç kaldım, ama sanırım bazıları bu iş parçacığına rastlarsa burada olmayı takdir edecekler. Yorumlar açıklamalıdır:

#!/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

Komut dosyası bilgilerini almanın kısa yolları şunlardır:

Klasörler ve dosyalar:

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

Bu komutları kullanarak:

    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`

Ve bu çıktıyı aldım:

     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

Ayrıca bakınız: https://pastebin.com/J8KjxrPF



Sanırım cevabım iyi çünkü basit bir çalışma baskısı bulmak zor. Burada istediğiniz kodu alabilirsiniz, örneğin cd + pwd, dirname + realpath veya dirname + readlink. Tüm parçaların daha önce var olduğundan ve çoğu cevabın karmaşık ve aşırı yüklü olduğundan emin değilim. Burada kullanmak istediğiniz kodu öğrenebilirsiniz. En azından lütfen gelecekte ihtiyacım olduğu gibi kaldırmayın: D
User8461

8

Bu bash-3.2'de çalışır:

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

Eğer bir varsa ~/binGözlerinde farklı dizini $PATH, sahip Abu dizinin içinin. Komut dosyasını kaynakları ~/bin/lib/B. Eklenen komut dosyasının libalt dizindeki orijinal komut dosyasına göre nerede olduğunu, ancak kullanıcının geçerli dizinine göre nerede olduğunu bilmiyorsunuz.

Bu, aşağıdakilerle (içeride A) çözülür :

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

Kullanıcının nerede olduğu veya komut dosyasını nasıl aradığı önemli değil, bu her zaman işe yarayacaktır.


3
Bu konu whichçok tartışmalı. type,, hashve diğer yerleşikler aynı şeyi bash'da daha iyi yaparlar. whichbiraz daha taşınabilir, ancak whichtcsh gibi diğer kabuklarda kullanılan ile aynı değil, bir yerleşik olarak var.
Monica'yı eski

"Her zaman"? Bir şey değil. whichharici bir araç olarak, bunun ana kabukla aynı şekilde davrandığına inanmak için hiçbir nedeniniz yoktur.
Charles Duffy

7

Benim görüşüme göre en kompakt çözüm:

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

Bash dışında hiçbir şeye güvenilmez. Kullanımı dirname, readlinkve basenamemümkünse eğer bu tip yerlerden kaçınılmalıdır böylece sonunda, uyumluluk sorunları yol açacaktır.


2
Muhtemelen buna çizgi eklemeniz gerekir: "$( cd "$( echo "${BASH_SOURCE[0]%/*}/" )"; pwd )". Bunu yapmazsanız, kök dizinde sorunlarınız olur. Ayrıca neden yankı kullanmak zorundasın?
konsolebox

dirnameve basenamePOSIX öyleyse neden önlemek bunları kullanarak, standardize edilmiştir? Bağlantılar: dirname,basename
myrdd

Fazladan iki işlem çatalının önlenmesi ve kabuk yerleşiklerine yapışmasının bir nedeni olabilir.
Amit Naidu
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.