Cara mendapatkan direktori sumber skrip Bash dari dalam skrip itu sendiri

4955

Bagaimana cara mendapatkan lintasan direktori tempat skrip Bash berada, di dalam skrip itu?

Saya ingin menggunakan skrip Bash sebagai peluncur untuk aplikasi lain. Saya ingin mengubah direktori kerja ke direktori tempat skrip Bash berada, sehingga saya dapat beroperasi pada file di direktori itu, seperti:

$ ./application
Jiaaro
sumber
69
Tidak ada solusi saat ini yang berfungsi jika ada baris baru di akhir nama direktori - Mereka akan dilucuti oleh substitusi perintah. Untuk mengatasinya, Anda dapat menambahkan karakter non-baris baru di dalam substitusi perintah - DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd && echo x)"- dan menghapusnya tanpa substitusi perintah - DIR="${DIR%x}".
l0b0
80
@ jpmc26 Ada dua situasi yang sangat umum: Kecelakaan dan sabotase. Sebuah skrip tidak boleh gagal dengan cara yang tidak dapat diprediksi hanya karena seseorang, di suatu tempat, melakukan a mkdir $'\n'.
l0b0
24
siapa pun yang membiarkan orang menyabotase sistem mereka dengan cara itu tidak boleh menyerah untuk mendeteksi masalah seperti itu ... apalagi merekrut orang yang mampu membuat kesalahan semacam itu. Saya belum pernah, dalam 25 tahun menggunakan bash, melihat hal semacam ini terjadi di mana saja .... ini sebabnya kami memiliki hal-hal seperti perl dan praktik seperti pengecekan noda (saya mungkin akan dinyalakan karena mengatakan itu :)
osirisgothra
3
@ l0b0 Pertimbangkan bahwa Anda akan memerlukan perlindungan yang sama dirname, dan bahwa direktori dapat dimulai dengan -(misalnya --help). DIR=$(reldir=$(dirname -- "$0"; echo x); reldir=${reldir%?x}; cd -- "$reldir" && pwd && echo x); DIR=${DIR%?x}. Mungkin ini berlebihan?
Score_Under
56
Saya sarankan untuk membaca FAQ Bash tentang subjek ini.
Rany Albeg Wein

Jawaban:

6573
#!/bin/bash

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

adalah satu-liner yang bermanfaat yang akan memberi Anda nama direktori lengkap dari skrip di mana pun ia dipanggil.

Ini akan berfungsi selama komponen terakhir dari jalur yang digunakan untuk menemukan skrip bukan symlink (tautan direktori OK). Jika Anda juga ingin menyelesaikan tautan apa pun ke skrip itu sendiri, Anda memerlukan solusi multisaluran:

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

Yang terakhir ini akan bekerja dengan kombinasi alias, source, bash -c, symlink, dll

Hati-hati: jika Anda cdke direktori yang berbeda sebelum menjalankan cuplikan ini, hasilnya mungkin salah!

Selain itu, perhatikan $CDPATHgotchas , dan efek samping keluaran stderr jika pengguna mengganti cd dengan cerdas untuk mengalihkan output ke stderr (termasuk urutan keluar, seperti saat memanggil update_terminal_cwd >&2Mac). Menambahkan >/dev/null 2>&1pada akhir cdperintah Anda akan menangani kedua kemungkinan.

Untuk memahami cara kerjanya, coba jalankan formulir yang lebih bertele-tele ini:

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

Dan itu akan mencetak sesuatu seperti:

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'
Dave Dopson
sumber
27
Anda dapat sekering pendekatan ini dengan jawaban dengan user25866 untuk sampai pada solusi yang bekerja dengan source <script>dan bash <script>: DIR="$(cd -P "$(dirname "${BASH_SOURCE[0]}")" && pwd)".
Dan Moulding
21
Terkadang cdmencetak sesuatu ke STDOUT! Misal, jika $CDPATHsudah .. Untuk membahas kasus ini, gunakanDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" > /dev/null && pwd )"
user716468
182
Jawaban yang diterima ini tidak ok, tidak bekerja dengan symlink dan terlalu rumit. dirname $(readlink -f $0)adalah perintah yang tepat. Lihat gist.github.com/tvlooy/cbfbdb111a4ebad8b93e untuk testcase
tvlooy
167
@tvlooy IMO jawaban Anda juga tidak persis apa adanya, karena gagal ketika ada ruang di jalan. Berbeda dengan karakter baris baru, ini bukan tidak mungkin atau bahkan tidak biasa. dirname "$(readlink -f "$0")"tidak menambah kerumitan dan ukuran yang adil lebih kuat untuk jumlah masalah minimal.
Adrian Günter
11
@tvlooy komentar Anda tidak kompatibel dengan macOS (atau mungkin BSD secara umum), sedangkan jawaban yang diterima adalah. readlink -f $0memberi readlink: illegal option -- f.
Alexander Ljungberg
876

Gunakan dirname "$0":

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

menggunakan pwdsendiri tidak akan berfungsi jika Anda tidak menjalankan skrip dari direktori yang ada di dalamnya.

[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
matt b
sumber
25
Untuk portabilitas di luar bash, $ 0 mungkin tidak selalu cukup. Anda mungkin perlu mengganti "ketik -p $ 0" untuk membuatnya berfungsi jika perintah itu ditemukan di jalan.
Darron
10
@Darron: Anda hanya dapat menggunakan type -pjika skrip dapat dieksekusi. Ini juga dapat membuka lubang halus jika skrip dieksekusi menggunakan bash test2.shdan ada skrip lain dengan nama yang sama dieksekusi di tempat lain.
D.Shawley
90
@ Darron: tapi karena pertanyaannya ditandai bashdan hash-bang line secara eksplisit menyebutkan /bin/bashsaya akan mengatakan itu cukup aman untuk bergantung pada bashism.
Joachim Sauer
34
+1, tetapi masalah dengan penggunaannya dirname $0adalah jika direktori tersebut adalah direktori saat ini, Anda akan mendapatkannya .. Tidak apa-apa kecuali Anda akan mengubah direktori dalam skrip dan berharap untuk menggunakan jalur yang Anda dapatkan dirname $0seolah-olah itu mutlak. Untuk mendapatkan path absolut: pushd `dirname $0` > /dev/null, SCRIPTPATH=`pwd`, popd > /dev/null: pastie.org/1489386 (Tapi pasti ada cara yang lebih baik untuk memperluas jalan itu?)
TJ Crowder
9
@ TJ Crowder Saya tidak yakin dirname $0masalah jika Anda menetapkannya ke variabel dan kemudian menggunakannya untuk meluncurkan skrip seperti $dir/script.sh; Saya akan membayangkan ini adalah kasus penggunaan untuk hal semacam ini 90% dari waktu. ./script.shakan bekerja dengan baik.
matt b
516

The dirnameperintah yang paling dasar, hanya parsing jalan sampai ke nama file off dari $0variabel (nama script):

dirname "$0"

Tetapi, seperti ditunjukkan matt b , path yang dikembalikan berbeda tergantung pada bagaimana script dipanggil. pwdtidak melakukan pekerjaan karena itu hanya memberi tahu Anda apa direktori saat ini, bukan direktori tempat skrip berada. Selain itu, jika tautan simbolis ke skrip dijalankan, Anda akan mendapatkan jalur (mungkin relatif) untuk tempat tautan berada, bukan skrip yang sebenarnya.

Beberapa orang lain telah menyebutkan readlinkperintah, tetapi yang paling sederhana, Anda dapat menggunakan:

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

readlinkakan menyelesaikan jalur skrip ke jalur absolut dari root filesystem. Jadi, setiap jalur yang mengandung titik tunggal atau ganda, tilde dan / atau tautan simbolik akan diselesaikan ke jalur penuh.

Berikut adalah script menunjukkan masing-masing, 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))"

Menjalankan skrip ini di direktori home saya, menggunakan jalur relatif:

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

Sekali lagi, tetapi menggunakan path lengkap ke skrip:

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

Sekarang mengubah direktori:

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

Dan akhirnya menggunakan tautan simbolis untuk menjalankan skrip:

>>>$ ln -s ~/whatdir.sh whatdirlink.sh
>>>$ ./whatdirlink.sh 
pwd: /tmp
$0: ./whatdirlink.sh
basename: whatdirlink.sh
dirname: .
dirname/readlink: /Users/phatblat
phatblat
sumber
13
readlinktidak akan tersedia di beberapa platform dalam instalasi default. Cobalah untuk menghindari menggunakannya jika Anda bisa
TL
43
berhati-hatilah untuk mengutip segala sesuatu untuk menghindari masalah ruang putih:export SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
Catskul
13
Dalam OSX Yosemite 10.10.1 -ftidak dikenali sebagai opsi untuk readlink. Menggunakan stat -fbukan melakukan pekerjaan. Terima kasih
cucu8
11
Di OSX, ada greadlink, yang pada dasarnya readlinkkita semua kenal. Ini adalah versi platform independen:dir=`greadlink -f ${BASH_SOURCE[0]} || readlink -f ${BASH_SOURCE[0]}`
robert
6
Panggilan bagus, @robert. FYI, greadlinkdapat dengan mudah diinstal melalui 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

Bekerja untuk semua versi, termasuk

  • ketika dipanggil melalui tautan lunak multple depth,
  • ketika file itu
  • ketika skrip dipanggil oleh perintah " source" alias .operator (titik).
  • ketika arg $0dimodifikasi dari pemanggil.
  • "./script"
  • "/full/path/to/script"
  • "/some/path/../../another/path/script"
  • "./some/folder/script"

Atau, jika skrip bash itu sendiri adalah symlink relatif yang ingin Anda ikuti dan kembalikan path lengkap skrip ditautkan ke:

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_PATHdiberikan dalam jalur penuh, tidak peduli bagaimana itu disebut.
Pastikan Anda menemukan ini di awal skrip.

Ini komentar dan kode Copyleft, lisensi yang dipilih di bawah GPL2.0 atau lebih baru atau CC-SA 3.0 (CreativeCommons Share Alike) atau lebih baru. (c) 2008. Hak cipta dilindungi undang-undang. Tidak ada jaminan dalam bentuk apa pun. Anda telah diperingatkan.
http://www.gnu.org/licenses/gpl-2.0.txt
http://creativecommons.org/licenses/by-sa/3.0/
18eedfe1c99df68dc94d4a94712a71aaa8e1e9e36cacf421b9463dd2bbaa02906d0d6656

pengguna25866
sumber
4
Bagus! Dapat dibuat lebih pendek menggantikan "pushd [...] popd / dev / null" oleh SCRIPT_PATH = readlink -f $(dirname "${VIRTUAL_ENV}");
e-satis
5
Dan bukannya menggunakan pushd ...; bukankah lebih baik menggunakan $ (cd dirname "${SCRIPT_PATH}"&& pwd)? Tapi bagaimanapun juga skrip yang bagus!
Ovanes
6
Berbahaya bagi skrip untuk cdkeluar dari direktori saat ini dengan harapan cduntuk kembali lagi nanti: Skrip mungkin tidak memiliki izin untuk mengubah direktori kembali ke direktori yang aktif saat itu dipanggil. (Sama berlaku untuk pushd / popd)
Adrian Pronk
7
readlink -fkhusus untuk GNU. BSD readlinktidak memiliki opsi itu.
Kara Brightwell
3
Ada apa dengan semua subkulit yang tidak perlu? ([ ... ])kurang efisien daripada [ ... ], dan tidak ada keuntungan yang diambil dari isolasi yang ditawarkan sebagai imbalan untuk kinerja yang hit di sini.
Charles Duffy
110

Jawaban singkat:

`dirname $0`

atau ( lebih disukai ):

$(dirname "$0")
Fabien
sumber
17
Ini tidak akan berfungsi jika Anda sumber skrip. "source my / script.sh"
Arunprasad Rajkumar
Saya menggunakan ini sepanjang waktu di skrip bash saya yang mengotomatiskan hal-hal dan sering memanggil skrip lain dalam direktori yang sama. Saya tidak pernah menggunakan sourceini dan cd $(dirname $0)mudah diingat.
kqw
16
@vidstige: ${BASH_SOURCE[0]}bukannya $0akan bekerja dengansource my/script.sh
Timothy Jones
@TimothyJones yang akan gagal 100% dari waktu jika bersumber dari shell selain bash. ${BASH_SOURCE[0]}sama sekali tidak memuaskan. ${BASH_SOURCE:-0}jauh lebih baik.
Mathieu CAROFF
106

Anda bisa menggunakan $BASH_SOURCE:

#!/bin/bash

scriptdir=`dirname "$BASH_SOURCE"`

Perhatikan bahwa Anda harus menggunakan #!/bin/bashdan bukan #!/bin/shkarena ini adalah ekstensi Bash.

Mr Shark
sumber
14
Ketika saya melakukannya ./foo/script, maka $(dirname $BASH_SOURCE)adalah ./foo.
Hingga
1
@Till, Dalam hal ini kita dapat menggunakan realpathperintah untuk mendapatkan path lengkap dari ./foo/script. Jadi dirname $(realpath ./foo/script) akan memberi jalan naskah.
purushothaman poovai
73

Ini harus dilakukan:

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

Ini berfungsi dengan symlink dan spasi di jalur.

Lihat halaman manual untuk dirnamedan readlink.

Dari trek komentar sepertinya tidak berfungsi dengan Mac OS. Saya tidak tahu mengapa itu terjadi. Ada saran?

Simon Rigét
sumber
6
dengan solusi Anda, menjalankan skrip seperti ./script.shpertunjukan .alih-alih jalur direktori lengkap
Bruno Negrão Zica
5
Tidak ada opsi -f untuk tautan baca pada MacOS. Gunakan statsebagai gantinya. Tapi tetap saja, itu menunjukkan .jika Anda berada di dir 'this'.
Denis The Menace
2
Anda perlu menginstal coreutilsdari Homebrew dan menggunakan greadlinkuntuk mendapatkan -fopsi pada MacOS karena itu * BSD di bawah selimut dan bukan Linux.
dragon788
Anda harus menambahkan tanda kutip ganda yang mengelilingi semua sisi kanan:DIR="$(dirname "$(readlink -f "$0")")"
hagello
60

pwddapat digunakan untuk menemukan direktori kerja saat ini, dan dirnameuntuk menemukan direktori file tertentu (perintah yang dijalankan, adalah $0, jadi dirname $0harus memberi Anda direktori skrip saat ini).

Namun, dirnamememberikan dengan tepat bagian direktori dari nama file, yang lebih mungkin daripada relatif terhadap direktori kerja saat ini. Jika skrip Anda perlu mengubah direktori karena alasan tertentu, maka output dari dirnamemenjadi tidak berarti.

Saya menyarankan yang berikut ini:

#!/bin/bash

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

echo "Directory is $directory"

Dengan cara ini, Anda mendapatkan direktori absolut, bukan relatif.

Karena skrip akan dijalankan dalam instance bash yang terpisah, tidak perlu mengembalikan direktori kerja setelahnya, tetapi jika Anda ingin mengubah kembali skrip Anda karena suatu alasan, Anda dapat dengan mudah menetapkan nilai pwdke variabel sebelum Anda ubah direktori, untuk penggunaan di masa mendatang.

Meski baru saja

cd `dirname $0`

Memecahkan skenario spesifik dalam pertanyaan, saya menemukan memiliki jalur absolut untuk lebih bermanfaat secara umum.

SpoonMeiser
sumber
9
Anda dapat melakukan semuanya dalam satu baris seperti ini: DIRECTORY = $ (cd dirname $0&& pwd)
dogbane
Ini tidak berfungsi jika skrip sumber skrip lain dan Anda ingin tahu nama yang terakhir.
reinierpost
52

Saya bosan datang ke halaman ini berulang-ulang untuk menyalin rekatkan satu baris dalam jawaban yang diterima. Masalah dengan itu adalah tidak mudah dipahami dan diingat.

Berikut ini skrip yang mudah diingat:

DIR="$(dirname "${BASH_SOURCE[0]}")"  # get the directory name
DIR="$(realpath "${DIR}")"    # resolve its full path if need be
Thamme Gowda
sumber
2
Atau, lebih jelasnya, pada satu baris: DIR=$(realpath "$(dirname "${BASH_SOURCE[0]}")")
agc
Mengapa ini bukan jawaban yang diterima? Apakah ada perbedaan menggunakan realpathdari menyelesaikan "secara manual" dengan loop readlink? Bahkan readlinkhalaman manual mengatakanNote realpath(1) is the preferred command to use for canonicalization functionality.
User9123
1
Ngomong-ngomong, bukankah seharusnya kita mendaftar realpathsebelumnya dirname, bukan sesudahnya? Jika file skrip itu sendiri adalah symlink ... Itu akan memberikan sesuatu seperti DIR="$(dirname "$(realpath "${BASH_SOURCE[0]}")")". Sebenarnya sangat dekat dengan jawaban yang diajukan Simon.
User9123
@ User9123 Saya pikir yang diterima adalah mencoba agar kompatibel dengan semua shell / distro yang populer. Terlebih lagi, tergantung pada apa yang Anda coba lakukan, dalam banyak kasus orang ingin mendapatkan direktori di mana symlink berada, bukan direktori sumber yang sebenarnya.
Wang
37

Saya tidak berpikir ini semudah yang lain. pwdtidak berfungsi, karena direktori saat ini belum tentu direktori dengan skrip. $0tidak selalu memiliki informasi juga. Pertimbangkan tiga cara berikut untuk menjalankan skrip:

./script

/usr/bin/script

script

Dalam cara pertama dan ketiga $0tidak memiliki informasi jalur lengkap. Di kedua dan ketiga, pwdtidak berfungsi. Satu-satunya cara untuk mendapatkan direktori dengan cara ketiga adalah menjalankan melalui jalur dan menemukan file dengan kecocokan yang benar. Pada dasarnya kode harus mengulang apa yang dilakukan OS.

Salah satu cara untuk melakukan apa yang Anda minta adalah dengan hanya meng-hardcode data dalam /usr/sharedirektori, dan merujuknya dengan path lengkapnya. Data tidak boleh ada di /usr/bindirektori, jadi ini mungkin yang harus dilakukan.

Jim
sumber
9
Jika Anda bermaksud menyangkal komentarnya, MEMBUKTIKAN bahwa skrip BISA mengakses tempat itu disimpan dengan contoh kode.
Richard Duerr
34
SCRIPT_DIR=$( cd ${0%/*} && pwd -P )
PM
sumber
Ini jauh lebih pendek dari jawaban yang dipilih. Dan tampaknya berhasil juga. Ini pantas mendapatkan 1.000 suara agar orang tidak mengabaikannya.
Patrick
2
Seperti banyak jawaban sebelumnya yang menjelaskan secara terperinci, tidak satu $0pun pwdtidak dijamin memiliki informasi yang benar, tergantung pada bagaimana skrip dipanggil.
IMSoP
34
$(dirname "$(readlink -f "$BASH_SOURCE")")
test11
sumber
Saya lebih suka, $BASH_SOURCElebih $0, karena ini eksplisit bahkan untuk pembaca yang tidak berpengalaman dalam bash. $(dirname -- "$(readlink -f -- "$BASH_SOURCE")")
blobmaster
32

Ini mendapatkan direktori aktif saat ini di Mac OS X 10.6.6:

DIR=$(cd "$(dirname "$0")"; pwd)
Pubguy
sumber
27

Ini khusus untuk Linux, tetapi Anda dapat menggunakan:

SELF=$(readlink /proc/$$/fd/255)
Steve Baker
sumber
1
Ini juga spesifik bash, tapi mungkin perilaku bash telah berubah? /proc/fd/$$/255tampaknya menunjuk ke tty, bukan ke direktori. Misalnya, dalam shell login saya saat ini, deskriptor file 0, 1, 2, dan 255 semuanya merujuk /dev/pts/4. Bagaimanapun, manual bash tidak menyebutkan fd 255, jadi mungkin tidak bijaksana untuk bergantung pada perilaku ini. \
Keith Thompson
2
Shell interaktif! = Skrip. Bagaimanapun realpath ${BASH_SOURCE[0]};juga tampaknya akan menjadi cara terbaik untuk pergi.
Steve Baker
23

Berikut ini adalah one-liner yang sesuai dengan POSIX:

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

# test
echo $SCRIPT_PATH
lamawithonel
sumber
4
Saya berhasil dengan ini ketika menjalankan skrip dengan sendirinya atau dengan menggunakan sudo, tetapi tidak ketika memanggil sumber ./script.sh
Michael R
Dan gagal ketika cddikonfigurasi untuk mencetak nama jalur baru.
Aaron Digulla
18

Saya mencoba semua ini dan tidak ada yang berhasil. Satu sangat dekat tetapi memiliki bug kecil yang memecahkannya dengan buruk; mereka lupa membungkus jalan dalam tanda kutip.

Juga banyak orang beranggapan Anda menjalankan skrip dari shell sehingga mereka lupa ketika Anda membuka skrip baru, skrip ini default untuk rumah Anda.

Coba direktori ini untuk ukuran:

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

Ini membuatnya benar terlepas dari bagaimana atau di mana Anda menjalankannya:

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

Jadi untuk membuatnya benar-benar berguna, inilah cara mengubah ke direktori skrip yang sedang berjalan:

cd "`dirname "$0"`"
Mike Bethany
sumber
4
Tidak berfungsi jika skrip berasal dari skrip lain.
reinierpost
Ini tidak berfungsi jika bagian terakhir dari $ 0 adalah tautan simbolis yang menunjuk ke entri direktori lain ( ln -s ../bin64/foo /usr/bin/foo).
hagello
17

Inilah cara sederhana dan benar:

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

Penjelasan:

  • ${BASH_SOURCE[0]}- jalur lengkap ke skrip. Nilai ini akan benar bahkan ketika skrip sedang bersumber, misalnya source <(echo 'echo $0')mencetak bash , sementara menggantinya dengan ${BASH_SOURCE[0]}akan mencetak path lengkap skrip. (Tentu saja, ini mengasumsikan Anda baik-baik saja bergantung pada Bash.)

  • readlink -f- Secara rekursif menyelesaikan setiap symlink di jalur yang ditentukan. Ini adalah ekstensi GNU, dan tidak tersedia pada (misalnya) sistem BSD. Jika Anda menjalankan Mac, Anda dapat menggunakan Homebrew untuk menginstal GNU coreutilsdan menggantikannya dengan ini greadlink -f.

  • Dan tentu saja dirnamemendapatkan direktori induk dari path.

James Ko
sumber
1
greadlink -fsayangnya tidak bekerja secara efektif ketika sourceskrip di Mac :(
Gabe Kopley
17

Cara terpendek dan paling elegan untuk melakukan ini adalah:

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

Ini akan bekerja pada semua platform dan sangat bersih.

Rincian lebih lanjut dapat ditemukan di " Direktori tempat skrip bash itu berada? ".

Atul
sumber
solusi bersih yang bagus, tetapi ini tidak akan berfungsi jika file tersebut disinkronkan.
ruuter
16

Saya akan menggunakan sesuatu seperti ini:

# 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
Nicolas
sumber
Ini yang asli! Bekerja dengan sederhana shjuga! Masalah dengan dirname "$0"solusi berbasis sederhana : Jika skrip berada dalam $PATHdan dipanggil tanpa jalur, mereka akan memberikan hasil yang salah.
Notinlist
@Notinlist Tidak begitu. Jika skrip ditemukan melalui PATH, $0akan berisi nama file absolut. Jika skrip dipanggil dengan nama file relatif atau absolut yang mengandung /, $0akan berisi itu.
Neil Mayhew
Tidak akan berfungsi untuk skrip bersumber.
Amit Naidu
16

Ini adalah sedikit revisi terhadap solusi yang ditunjukkan e-satis dan 3bcdnlklvc04a dalam jawaban mereka :

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

Ini harus tetap bekerja dalam semua kasus yang mereka daftarkan.

Ini akan mencegah popdsetelah gagal pushd, berkat konsolebox.

Fuwjax
sumber
Ini berfungsi sempurna untuk mendapatkan dirname "asli", bukan hanya nama symlink. Terima kasih!
Jay Taylor
1
Lebih baikSCRIPT_DIR=''; pushd "$(dirname "$(readlink -f "$BASH_SOURCE")")" > /dev/null && { SCRIPT_DIR=$PWD; popd > /dev/null; }
konsolebox
@konsolebox, apa yang Anda coba pertahankan? Saya biasanya penggemar inlining conditional logis, tetapi apa kesalahan spesifik yang Anda lihat di pushd? Saya akan mencocokkan lebih suka mencari cara untuk menanganinya secara langsung alih-alih mengembalikan SCRIPT_DIR yang kosong.
Fuwjax
@Fuwjax Latihan alami untuk menghindari melakukan popddalam kasus (bahkan ketika jarang) di mana pushdgagal. Dan jika pushdgagal, menurut Anda apa nilai dari SCRIPT_DIR? Tindakan dapat bervariasi tergantung pada apa yang tampak logis atau apa yang bisa disukai satu pengguna tetapi tentu saja, melakukan popditu salah.
konsolebox
Semua pushd popdbahaya itu bisa dihindari hanya dengan menjatuhkannya dan menggunakan cd+ pwdsebagai ganti perintah pengganti. SCRIPT_DIR=$(...)
Amit Naidu
16

Untuk sistem yang memiliki GNU coreutils readlink(mis. Linux):

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

Tidak perlu digunakan BASH_SOURCEsaat $0berisi nama file skrip.

pengguna1338062
sumber
3
kecuali skrip bersumber dari. atau 'sumber' dalam hal ini skrip apa pun akan tetap bersumber, atau, jika dari baris perintah, '-bash' (tty login) atau 'bash' (dipanggil melalui 'bash -l') atau '/ bin / bash '(dipanggil sebagai shell non-login interaktif)
osirisgothra
Saya menambahkan sepasang kutipan kedua di sekitar dirnamepanggilan. Diperlukan jika jalur direktori berisi spasi.
user1338062
14
#!/bin/sh
PRG="$0"

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

scriptdir=`dirname "$PRG"`
Monyet
sumber
Saya belum mengujinya di berbagai sistem. Tapi solusi ini adalah yang paling tidak langsung berfungsi di Ubuntu, untuk saya!
Natus Drew
$0tidak akan berfungsi untuk skrip bersumber
Amit Naidu
13

$_layak disebut sebagai alternatif $0. Jika Anda menjalankan skrip dari Bash, jawaban yang diterima dapat disingkat menjadi:

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

Perhatikan bahwa ini harus menjadi pernyataan pertama dalam skrip Anda.

hurrymaplelad
sumber
4
Rusak jika Anda sourceatau .skrip. Dalam situasi itu, $_akan berisi parameter terakhir dari perintah terakhir yang Anda jalankan sebelum .. $BASH_SOURCEbekerja setiap saat.
clacke
11

Saya telah membandingkan banyak jawaban yang diberikan, dan menghasilkan beberapa solusi yang lebih ringkas. Ini tampaknya menangani semua kasus tepi gila yang muncul dari kombinasi favorit Anda:

  • Jalur absolut atau jalur relatif
  • File dan direktori soft link
  • Doa sebagai script, bash script, bash -c script, source script, atau. script
  • Spasi, tab, baris baru, unicode, dll dalam direktori dan / atau nama file
  • Nama file dimulai dengan tanda hubung

Jika Anda menjalankan dari Linux, tampaknya menggunakan procgagang adalah solusi terbaik untuk menemukan sumber skrip yang saat ini sedang berjalan (dalam sesi interaktif, tautan menunjuk ke masing-masing /dev/pts/X):

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

Ini memiliki sedikit keburukan untuk itu, tetapi perbaikannya kompak dan mudah dimengerti. Kami tidak menggunakan primitif bash saja, tapi saya setuju dengan itu karena readlinkmenyederhanakan tugas. The echo Xmenambahkan Xke akhir string variabel sehingga setiap spasi membuntuti di nama file tidak dimakan, dan substitusi parameter ${VAR%X}pada akhir baris akan menyingkirkan X. Karena readlinkmenambahkan baris baru sendiri (yang biasanya akan dimakan dalam penggantian perintah jika bukan karena tipu daya kita sebelumnya), kita harus menyingkirkan itu juga. Ini paling mudah dilakukan dengan menggunakan $''skema penawaran, yang memungkinkan kita menggunakan urutan pelarian seperti\n untuk merepresentasikan baris baru (ini juga cara Anda dapat dengan mudah membuat direktori dan file yang dinamai secara licik)

Hal di atas harus mencakup kebutuhan Anda untuk menemukan skrip yang saat ini berjalan di Linux, tetapi jika Anda tidak memiliki sistem procfile yang Anda inginkan, atau jika Anda mencoba untuk menemukan jalur yang diselesaikan sepenuhnya dari beberapa file lain, maka mungkin Anda akan temukan kode di bawah ini bermanfaat. Ini hanya sedikit modifikasi dari liner satu di atas. Jika Anda bermain-main dengan direktori / nama file aneh, memeriksa output dengan keduanya lsdan readlinkinformatif, karena lsakan menampilkan jalur "disederhanakan", menggantikan ?hal-hal seperti baris baru.

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"
billyjmc
sumber
Saya dapatkan /dev/pts/30dengan bash di Ubuntu 14.10 Desktop.
Dan Dascalescu
@DanDascalescu Menggunakan one-liner? Atau potongan kode lengkap di bagian bawah? Dan apakah Anda memberi makan itu nama jalan yang rumit?
billyjmc
Satu baris ditambah baris lain untuk echo $resolved, saya disimpan sebagai d, chmod +x d, ./d.
Dan Dascalescu
@DanDascalescu Baris pertama dalam skrip Anda harus#!/bin/bash
billyjmc
10

Coba gunakan:

real=$(realpath $(dirname $0))
eeerahul
sumber
1
Yang ingin saya tahu adalah, mengapa cara ini tidak baik? Rasanya tidak buruk dan benar bagi saya. Adakah yang bisa menjelaskan mengapa itu diturunkan?
Shou Ya
7
realpath bukan utilitas standar.
Steve Bennett
2
Di Linux, realpath adalah utilitas standar (bagian dari paket GNU coreutils), tetapi itu bukan bash built-in (yaitu, fungsi yang disediakan oleh bash sendiri). Jika Anda menjalankan Linux, metode ini mungkin akan berfungsi, meskipun saya akan mengganti $0untuk ${BASH_SOURCE[0]}sehingga metode ini akan bekerja di mana saja, termasuk dalam suatu fungsi.
Doug Richardson
3
Urutan operasi dalam jawaban ini salah. Anda harus menyelesaikan symlink terlebih dahulu , kemudian lakukan dirnamekarena bagian terakhir dari $0mungkin symlink yang menunjuk ke file yang tidak ada di direktori yang sama dengan symlink itu sendiri. Solusi yang dijelaskan dalam jawaban ini hanya mendapatkan jalur direktori tempat symlink disimpan, bukan direktori target. Lebih jauh lagi, solusi ini tidak ada penawaran. Ini tidak akan berfungsi jika path berisi karakter khusus.
hagello
3
dir="$(realpath "$(dirname "${BASH_SOURCE[0]}")")"
Kostiantyn Ponomarenko
9

Coba solusi lintas-kompatibel berikut:

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

karena perintah seperti realpathatau readlinkmungkin tidak tersedia (tergantung pada sistem operasi).

Catatan: Di Bash, disarankan untuk menggunakan ${BASH_SOURCE[0]}alih-alih $0, jika tidak jalur dapat rusak saat sumber file ( source/ .).

Atau Anda dapat mencoba fungsi berikut di bash:

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

Fungsi ini membutuhkan 1 argumen. Jika argumen sudah memiliki path absolut, cetaklah sebagaimana adanya, jika tidak, cetak $PWDargumen variabel + nama file (tanpa ./awalan).

Terkait:

kenorb
sumber
Tolong jelaskan lebih lanjut tentang fungsi jalan.
Chris
1
realpathFungsi @Chris membutuhkan 1 argumen. Jika argumen sudah memiliki path absolut, cetaklah sebagaimana adanya, jika tidak cetak $PWD+ nama file (tanpa ./awalan).
kenorb
Solusi lintas-kompatibel Anda tidak berfungsi saat skrip disinkronkan.
Jakub Jirutka
9

Saya percaya saya punya yang ini. Saya terlambat ke pesta, tapi saya pikir beberapa akan menghargai itu ada di sini jika mereka menemukan utas ini. Komentar harus menjelaskan:

#!/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
Geoff Nixon
sumber
8

Ini adalah cara singkat untuk mendapatkan informasi skrip:

Folder dan file:

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

Menggunakan perintah ini:

    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`

Dan saya mendapat hasil ini:

     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

Lihat juga: https://pastebin.com/J8KjxrPF

Pengguna8461
sumber
Saya pikir jawaban saya baik-baik saja karena sulit menemukan edisi kerja yang sederhana. Di sini Anda dapat mengambil kode yang Anda suka misalnya cd + pwd, dirname + realpath atau dirname + readlink. Saya tidak yakin semua bagian sudah ada sebelumnya dan sebagian besar jawabannya rumit dan kelebihan beban. Di sini Anda dapat menghapus kode yang ingin Anda gunakan. Setidaknya tolong jangan menghapusnya seperti yang saya butuhkan di masa depan: D
User8461
8

Ini bekerja di bash-3.2:

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

Jika Anda memiliki ~/bindirektori di $PATH, Anda ada Adi dalam direktori ini. Sumber skrip ~/bin/lib/B. Anda tahu di mana skrip yang disertakan relatif terhadap yang asli, di libsubdirektori, tetapi tidak di mana itu relatif terhadap direktori pengguna saat ini.

Ini dipecahkan oleh yang berikut (di dalam A):

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

Tidak masalah di mana pengguna berada atau bagaimana ia memanggil skrip, ini akan selalu berfungsi.

Matt Tardiff
sumber
3
Intinya whichsangat bisa diperdebatkan. type,, hashdan bawaan lain melakukan hal yang sama dengan lebih baik di bash. whichJenisnya lebih portabel, meskipun sebenarnya tidak sama dengan yang whichdigunakan pada shell lain seperti tcsh, yang menjadikannya sebagai builtin.
Pasang kembali Monica, Tolong
"Selalu"? Tidak semuanya. whichmenjadi alat eksternal, Anda tidak memiliki alasan untuk percaya itu berperilaku identik dengan shell induk.
Charles Duffy
7

Solusi ringkas terbaik menurut saya adalah:

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

Tidak ada ketergantungan pada apa pun selain Bash. Penggunaan dirname, readlinkdan basenamepada akhirnya akan mengarah pada masalah kompatibilitas, jadi sebaiknya dihindari jika keadaan memungkinkan.

AsymLabs
sumber
2
Anda mungkin harus menambahkan garis miring itu: "$( cd "$( echo "${BASH_SOURCE[0]%/*}/" )"; pwd )". Anda akan memiliki masalah dengan direktori root jika tidak. Juga mengapa Anda bahkan harus menggunakan gema?
konsolebox
dirnamedan basenameapakah POSIX terstandarisasi, jadi mengapa tidak menggunakannya? Link: dirname,basename
myrdd
Mencegah dua proses garpu tambahan dan menempel ke shell built-in mungkin menjadi salah satu alasan mengapa.
Amit Naidu