Script Unix shell mencari tahu direktori mana file script berada?
511
Pada dasarnya saya perlu menjalankan skrip dengan jalur yang terkait dengan lokasi file skrip shell, bagaimana saya bisa mengubah direktori saat ini ke direktori yang sama dengan tempat file skrip berada?
Apakah itu benar-benar duplikat? Pertanyaan ini adalah tentang "skrip shell unix", yang lainnya secara khusus tentang Bash.
michaeljt
2
@BoltClock: Pertanyaan ini tidak benar ditutup. Pertanyaan yang ditautkan adalah tentang Bash. Pertanyaan ini adalah tentang pemrograman shell Unix. Perhatikan bahwa jawaban yang diterima sangat berbeda!
Dietrich Epp
@Dietrich Epp: Anda benar. Tampaknya pilihan penanya atas jawaban yang diterima dan penambahan tag [bash] (mungkin sebagai tanggapan terhadap itu) membuat saya menandai pertanyaan sebagai duplikat sebagai respons terhadap sebuah bendera.
Ini tidak berfungsi jika Anda telah memanggil skrip melalui tautan simbolik di direktori yang berbeda. Untuk membuatnya bekerja, Anda perlu menggunakan readlinkjuga (lihat jawaban al di bawah ini)
AndrewR
44
Dalam bash lebih aman untuk digunakan $BASH_SOURCEsebagai pengganti $0, karena $0tidak selalu berisi jalur skrip yang dipanggil, seperti ketika 'sumber' skrip.
mklement0
2
$BASH_SOURCEspesifik Bash, pertanyaannya adalah tentang skrip shell secara umum.
Ha-Duong Nguyen
7
@ auraham: CUR_PATH=$(pwd)atau pwdkembalikan direktori saat ini (yang tidak harus berupa dir induk skrip)!
Andreas Dietrich
5
Saya mencoba metode @ mklement0 yang direkomendasikan, menggunakan $BASH_SOURCE, dan mengembalikan apa yang saya butuhkan. Skrip saya dipanggil dari skrip lain, dan $0kembali .sambil $BASH_SOURCEmengembalikan subdirektori yang tepat (dalam kasus saya scripts).
David Rissato Cruz
402
Posting asli berisi solusi (abaikan tanggapan, mereka tidak menambahkan sesuatu yang berguna). Pekerjaan yang menarik dilakukan oleh perintah unix yang disebutkan readlinkdengan opsi -f. Bekerja ketika skrip dipanggil oleh jalur absolut dan relatif.
Untuk bash, sh, ksh:
#!/bin/bash # Absolute path to this script, e.g. /home/user/bin/foo.sh
SCRIPT=$(readlink -f "$0")# Absolute path this script is in, thus /home/user/bin
SCRIPTPATH=$(dirname "$SCRIPT")
echo $SCRIPTPATH
Untuk tcsh, csh:
#!/bin/tcsh# Absolute path to this script, e.g. /home/user/bin/foo.cshset SCRIPT=`readlink -f "$0"`# Absolute path this script is in, thus /home/user/binset SCRIPTPATH=`dirname "$SCRIPT"`
echo $SCRIPTPATH
Untuk mengklarifikasi komentar @ Ergwun: -fsama sekali tidak didukung pada OS X (pada Lion); di sana Anda dapat membatalkan -funtuk menyelesaikan paling banyak satu tingkat tipuan, misalnya pushd "$(dirname "$(readlink "$BASH_SOURCE" || echo "$BASH_SOURCE")")", atau Anda dapat menggulung skrip berikut symlink-rekursif Anda sendiri seperti yang ditunjukkan dalam posting yang ditautkan.
mklement0
2
Saya masih tidak mengerti, mengapa OP akan membutuhkan jalan absolut. Pelaporan "." seharusnya berfungsi dengan baik jika Anda ingin mengakses file relatif ke jalur skrip dan Anda memanggil skrip seperti ./myscript.sh
Stefan Haberl
10
@StefanHaberl Saya pikir ini akan menjadi masalah jika Anda menjalankan skrip saat direktori kerja Anda sekarang berbeda dari lokasi skrip (mis. sh /some/other/directory/script.sh), Dalam hal ini .akan menjadi pwd Anda, bukan/some/other/directory
Jon z
51
Komentar sebelumnya tentang jawaban mengatakannya, tetapi mudah untuk melewatkan di antara semua jawaban lainnya.
Saat menggunakan bash:
echo this file:"$BASH_SOURCE"
echo this dir:"$(dirname "$BASH_SOURCE")"
Hanya yang ini berfungsi dengan skrip di jalur lingkungan, yang paling banyak dipilih tidak berfungsi. Terima kasih!
Juli
Anda sebaiknya menggunakan dirname "$BASH_SOURCE"untuk menangani spasi dalam $ BASH_SOURCE.
Mygod
1
Cara yang lebih eksplisit untuk mencetak direktori, menurut alat ShellCheck, adalah: "$ (dirname" $ {BASH_SOURCE {0}} ")" karena BASH_SOURCE adalah array, dan tanpa subskrip, elemen pertama diambil secara default .
Adrian M.
1
@AdrianM. , Anda ingin tanda kurung, bukan kawat gigi, untuk indeks:"$(dirname "${BASH_SOURCE[0]}")"
Hashbrown
Tidak baik untuk saya..cetak hanya satu titik (yaitu direktori saat ini, mungkin)
Skrip ini harus mencetak direktori tempat Anda berada, dan kemudian direktori tempat skrip berada. Misalnya, saat memanggilnya /dengan skrip /home/mez/, ia menampilkan
//home/mez
Ingat, ketika menetapkan variabel dari output perintah, bungkus perintah di $(dan )- atau Anda tidak akan mendapatkan output yang diinginkan.
Ini tidak akan berfungsi ketika saya mengaktifkan skrip dari direktori saat ini.
Eric Wang
1
@ EricWang Anda selalu berada di direktori saat ini.
ctrl-alt-delor
Bagi saya, $ current_dir memang jalur yang saya gunakan untuk memanggil skrip. Namun, $ script_dir bukan direktori skrip, itu hanya sebuah titik.
Michael
31
Jika Anda menggunakan bash ....
#!/bin/bash
pushd $(dirname "${0}")>/dev/null
basedir=$(pwd -L)# Use "pwd -P" for the path without links. man bash for more info.
popd >/dev/null
echo "${basedir}"
Anda dapat mengganti pushd/ popddengan cd $(dirname "${0}")dan cd -untuk membuatnya bekerja pada shell lain, jika mereka memiliki pwd -L.
docwhat
mengapa Anda menggunakan pushd dan popd di sini?
qodeninja
1
Jadi saya tidak perlu menyimpan direktori asli dalam sebuah variabel. Ini adalah pola yang saya gunakan banyak fungsi dan semacamnya. Itu bersarang dengan sangat baik, yang bagus.
docwhat
Itu masih disimpan dalam memori - dalam suatu variabel - apakah suatu variabel direferensikan dalam skrip Anda atau tidak. Juga, saya percaya biaya mengeksekusi pushd dan popd jauh lebih besar daripada penghematan karena tidak membuat variabel Bash lokal dalam skrip Anda, baik dalam siklus CPU dan keterbacaan.
mana
20
Seperti yang dikatakan theMarko:
BASEDIR=$(dirname $0)
echo $BASEDIR
Ini berfungsi kecuali jika Anda menjalankan skrip dari direktori yang sama di mana skrip berada, dalam hal ini Anda mendapatkan nilai '.'
Anda sekarang dapat menggunakan variabel current_dir di seluruh skrip Anda untuk merujuk ke direktori skrip. Namun ini mungkin masih memiliki masalah symlink.
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}")" && pwd )"
One-liner yang akan memberi Anda nama direktori lengkap dari skrip di mana pun ia dipanggil.
Untuk memahami cara kerjanya, Anda dapat menjalankan skrip berikut:
#!/bin/bash
SOURCE="${BASH_SOURCE[0]}"while[-h "$SOURCE"];do# resolve $SOURCE until the file is no longer a symlink
TARGET="$(readlink "$SOURCE")"if[[ $TARGET ==/*]];then
echo "SOURCE '$SOURCE' is an absolute symlink to '$TARGET'"
SOURCE="$TARGET"else
DIR="$( dirname "$SOURCE" )"
echo "SOURCE '$SOURCE' is a relative symlink to '$TARGET' (relative to '$DIR')"
SOURCE="$DIR/$TARGET"# if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was locatedfidone
echo "SOURCE is '$SOURCE'"
RDIR="$( dirname "$SOURCE" )"
DIR="$( cd -P "$( dirname "$SOURCE")" && pwd )"if["$DIR"!="$RDIR"];then
echo "DIR '$RDIR' resolves to '$DIR'"fi
echo "DIR is '$DIR'"
seperti yang tertulis, cd: too many argumentsjika spasi di jalan, dan kembali $PWD. (perbaikan yang jelas, tetapi hanya menunjukkan berapa banyak tepi kasus sebenarnya ada)
michael
1
Akan membenarkan. Kecuali untuk komentar dari @michael bahwa ia gagal dengan spasi di path ... Apakah ada perbaikan untuk itu?
Ini berfungsi baik di linux dan macOS. Saya tidak bisa melihat orang di sini menyebutkan tentang realpath. Tidak yakin apakah ada kelemahan dalam pendekatan ini.
pada macOS, Anda perlu menginstal coreutilsuntuk menggunakan realpath. Misalnya: brew install coreutils.
MENGAPA MENGGUNAKAN dirname "$ 0" PADA SAJA BUKAN BEKERJA?
dirname $ 0 hanya akan berfungsi jika pengguna meluncurkan skrip dengan cara yang sangat spesifik. Saya dapat menemukan beberapa situasi di mana jawaban ini gagal dan membuat skrip mogok.
Pertama-tama, mari kita pahami bagaimana jawaban ini bekerja. Dia mendapatkan direktori script dengan melakukan
dirname "$0"
$ 0 mewakili bagian pertama dari perintah yang memanggil skrip (pada dasarnya itu adalah perintah yang dimasukkan tanpa argumen:
/some/path/./script argument1 argument2
$ 0 = "/ some / path /./ script"
dirname pada dasarnya menemukan yang terakhir / dalam sebuah string dan memotongnya di sana. Jadi, jika Anda melakukannya:
dirname /usr/bin/sha256sum
Anda akan mendapatkan: / usr / bin
Contoh ini berfungsi dengan baik karena / usr / bin / sha256sum adalah path yang diformat dengan benar tetapi
dirname "/some/path/./script"
tidak akan bekerja dengan baik dan akan memberi Anda:
BASENAME="/some/path/."#which would crash your script if you try to use it as a path
Katakanlah Anda berada di dir yang sama dengan skrip Anda dan Anda meluncurkannya dengan perintah ini
./script
$ 0 dalam situasi ini adalah ./script dan dirname $ 0 akan memberikan:
.#or BASEDIR=".", again this will crash your script
Menggunakan:
sh script
Tanpa memasukkan lintasan lengkap juga akan memberikan BASEDIR = "."
Menggunakan direktori relatif:
../some/path/./script
Memberikan dirname $ 0 dari:
../some/path/.
Jika Anda berada di direktori / some dan Anda memanggil skrip dengan cara ini (perhatikan tidak adanya / di awal, lagi-lagi jalur relatif):
path/./script.sh
Anda akan mendapatkan nilai ini untuk dirname $ 0:
path/.
dan ./path/./script (bentuk lain dari jalur relatif) memberikan:
./path/.
Hanya dua situasi di mana baseir $ 0 akan berfungsi adalah jika pengguna menggunakan sh atau sentuh untuk meluncurkan skrip karena keduanya akan menghasilkan $ 0:
$0=/some/path/script
yang akan memberi Anda jalan yang dapat Anda gunakan dengan dirname.
SOLUSINYA
Anda akan memiliki akun untuk dan mendeteksi setiap situasi yang disebutkan di atas dan menerapkan perbaikan untuk itu jika muncul:
#!/bin/bash#this script will only work in bash, make sure it's installed on your system.#set to false to not see all the echos
debug=true
if["$debug"= true ];then echo "\$0=$0";fi#The line below detect script's parent directory. $0 is the part of the launch command that doesn't contain the arguments
BASEDIR=$(dirname "$0")#3 situations will cause dirname $0 to fail: #situation1: user launches script while in script dir ( $0=./script)#situation2: different dir but ./ is used to launch script (ex. $0=/path_to/./script)#situation3: different dir but relative path used to launch scriptif["$debug"= true ];then echo 'BASEDIR=$(dirname "$0") gives: '"$BASEDIR";fiif["$BASEDIR"="."];then BASEDIR="$(pwd)";fi# fix for situation1
_B2=${BASEDIR:$((${#BASEDIR}-2))}; B_=${BASEDIR::1}; B_2=${BASEDIR::2}; B_3=${BASEDIR::3}# <- bash onlyif["$_B2"="/."];then BASEDIR=${BASEDIR::$((${#BASEDIR}-1))};fi#fix for situation2 # <- bash onlyif["$B_"!="/"];then#fix for situation3 #<- bash onlyif["$B_2"="./"];then#covers ./relative_path/(./)scriptif["$(pwd)"!="/"];then BASEDIR="$(pwd)/${BASEDIR:2}";else BASEDIR="/${BASEDIR:2}";fielse#covers relative_path/(./)script and ../relative_path/(./)script, using ../relative_path fails if current path is a symbolic linkif["$(pwd)"!="/"];then BASEDIR="$(pwd)/$BASEDIR";else BASEDIR="/$BASEDIR";fififiif["$debug"= true ];then echo "fixed BASEDIR=$BASEDIR";fi
One-liner ini memberitahukan di mana skrip shell berada, tidak masalah apakah Anda menjalankannya atau jika Anda membuatnya . Juga, ini menyelesaikan tautan simbolis yang terlibat, jika itu masalahnya:
Begitu banyak jawaban, semuanya masuk akal, masing-masing dengan tujuan pro dan kontra & sedikit berbeda (yang mungkin harus dinyatakan untuk masing-masing). Berikut adalah solusi lain yang memenuhi tujuan utama yaitu menjadi jelas dan bekerja di semua sistem, pada semua bash (tidak ada asumsi tentang versi bash, readlinkatau pwdopsi), dan melakukan apa yang Anda harapkan terjadi secara wajar (misalnya, menyelesaikan symlink adalah suatu masalah yang menarik, tetapi biasanya tidak seperti yang Anda inginkan), menangani kasus tepi seperti spasi di jalur, dll., mengabaikan kesalahan apa pun dan menggunakan standar waras jika ada masalah.
Setiap komponen disimpan dalam variabel terpisah yang dapat Anda gunakan secara individual:
# script path, filename, directory
PROG_PATH=${BASH_SOURCE[0]}# this script's name
PROG_NAME=${PROG_PATH##*/} # basename of script (strip path)
PROG_DIR="$(cd "$(dirname "${PROG_PATH:-$PWD}")" 2>/dev/null 1>&2 && pwd)"
Mungkin terlihat jelek tergantung pada bagaimana ia dipanggil dan cwd tetapi harus membuat Anda pergi ke mana Anda harus pergi (atau Anda dapat mengubah string jika Anda peduli bagaimana tampilannya).
Jawaban:
Di Bash, Anda harus mendapatkan yang Anda butuhkan seperti ini:
sumber
readlink
juga (lihat jawaban al di bawah ini)$BASH_SOURCE
sebagai pengganti$0
, karena$0
tidak selalu berisi jalur skrip yang dipanggil, seperti ketika 'sumber' skrip.$BASH_SOURCE
spesifik Bash, pertanyaannya adalah tentang skrip shell secara umum.CUR_PATH=$(pwd)
ataupwd
kembalikan direktori saat ini (yang tidak harus berupa dir induk skrip)!$BASH_SOURCE
, dan mengembalikan apa yang saya butuhkan. Skrip saya dipanggil dari skrip lain, dan$0
kembali.
sambil$BASH_SOURCE
mengembalikan subdirektori yang tepat (dalam kasus sayascripts
).Posting asli berisi solusi (abaikan tanggapan, mereka tidak menambahkan sesuatu yang berguna). Pekerjaan yang menarik dilakukan oleh perintah unix yang disebutkan
readlink
dengan opsi-f
. Bekerja ketika skrip dipanggil oleh jalur absolut dan relatif.Untuk bash, sh, ksh:
Untuk tcsh, csh:
Lihat juga: https://stackoverflow.com/a/246128/59087
sumber
readlink
. Itu sebabnya saya merekomendasikan menggunakan pushd / popd (built-in untuk bash).-f
pilihan untukreadlink
melakukan sesuatu yang berbeda pada OS X (Lion) dan mungkin BSD. stackoverflow.com/questions/1055671/…-f
sama sekali tidak didukung pada OS X (pada Lion); di sana Anda dapat membatalkan-f
untuk menyelesaikan paling banyak satu tingkat tipuan, misalnyapushd "$(dirname "$(readlink "$BASH_SOURCE" || echo "$BASH_SOURCE")")"
, atau Anda dapat menggulung skrip berikut symlink-rekursif Anda sendiri seperti yang ditunjukkan dalam posting yang ditautkan.sh /some/other/directory/script.sh)
, Dalam hal ini.
akan menjadi pwd Anda, bukan/some/other/directory
Komentar sebelumnya tentang jawaban mengatakannya, tetapi mudah untuk melewatkan di antara semua jawaban lainnya.
Saat menggunakan bash:
Manual Referensi Bash, 5.2 Variabel Bash
sumber
dirname "$BASH_SOURCE"
untuk menangani spasi dalam $ BASH_SOURCE."$(dirname "${BASH_SOURCE[0]}")"
Dengan asumsi Anda menggunakan bash
Skrip ini harus mencetak direktori tempat Anda berada, dan kemudian direktori tempat skrip berada. Misalnya, saat memanggilnya
/
dengan skrip/home/mez/
, ia menampilkanIngat, ketika menetapkan variabel dari output perintah, bungkus perintah di
$(
dan)
- atau Anda tidak akan mendapatkan output yang diinginkan.sumber
Jika Anda menggunakan bash ....
sumber
pushd
/popd
dengancd $(dirname "${0}")
dancd -
untuk membuatnya bekerja pada shell lain, jika mereka memilikipwd -L
.Seperti yang dikatakan theMarko:
Ini berfungsi kecuali jika Anda menjalankan skrip dari direktori yang sama di mana skrip berada, dalam hal ini Anda mendapatkan nilai '.'
Untuk menyiasatinya gunakan:
Anda sekarang dapat menggunakan variabel current_dir di seluruh skrip Anda untuk merujuk ke direktori skrip. Namun ini mungkin masih memiliki masalah symlink.
sumber
Jawaban terbaik untuk pertanyaan ini dijawab di sini:
Mendapatkan direktori sumber skrip Bash dari dalam
Dan itu adalah:
One-liner yang akan memberi Anda nama direktori lengkap dari skrip di mana pun ia dipanggil.
Untuk memahami cara kerjanya, Anda dapat menjalankan skrip berikut:
sumber
sumber
Mari menjadikannya POSIX oneliner:
Diuji pada banyak cangkang yang kompatibel dengan Bourne termasuk yang BSD.
Sejauh yang saya tahu saya adalah penulis dan saya memasukkannya ke dalam domain publik. Untuk info lebih lanjut, lihat: https://www.jasan.tk/posts/2017-05-11-posix_shell_dirname_replacement/
sumber
cd: too many arguments
jika spasi di jalan, dan kembali$PWD
. (perbaikan yang jelas, tetapi hanya menunjukkan berapa banyak tepi kasus sebenarnya ada)sumber
Jika Anda ingin mendapatkan direktori skrip aktual (terlepas dari apakah Anda menggunakan skrip menggunakan symlink atau langsung), cobalah:
Ini berfungsi baik di linux dan macOS. Saya tidak bisa melihat orang di sini menyebutkan tentang
realpath
. Tidak yakin apakah ada kelemahan dalam pendekatan ini.pada macOS, Anda perlu menginstal
coreutils
untuk menggunakanrealpath
. Misalnya:brew install coreutils
.sumber
PENGANTAR
Jawaban ini mengoreksi jawaban yang dipilih sangat rusak namun mengejutkan atas utas ini (ditulis oleh TheMarko):
MENGAPA MENGGUNAKAN dirname "$ 0" PADA SAJA BUKAN BEKERJA?
dirname $ 0 hanya akan berfungsi jika pengguna meluncurkan skrip dengan cara yang sangat spesifik. Saya dapat menemukan beberapa situasi di mana jawaban ini gagal dan membuat skrip mogok.
Pertama-tama, mari kita pahami bagaimana jawaban ini bekerja. Dia mendapatkan direktori script dengan melakukan
$ 0 mewakili bagian pertama dari perintah yang memanggil skrip (pada dasarnya itu adalah perintah yang dimasukkan tanpa argumen:
$ 0 = "/ some / path /./ script"
dirname pada dasarnya menemukan yang terakhir / dalam sebuah string dan memotongnya di sana. Jadi, jika Anda melakukannya:
Anda akan mendapatkan: / usr / bin
Contoh ini berfungsi dengan baik karena / usr / bin / sha256sum adalah path yang diformat dengan benar tetapi
tidak akan bekerja dengan baik dan akan memberi Anda:
Katakanlah Anda berada di dir yang sama dengan skrip Anda dan Anda meluncurkannya dengan perintah ini
$ 0 dalam situasi ini adalah ./script dan dirname $ 0 akan memberikan:
Menggunakan:
Tanpa memasukkan lintasan lengkap juga akan memberikan BASEDIR = "."
Menggunakan direktori relatif:
Memberikan dirname $ 0 dari:
Jika Anda berada di direktori / some dan Anda memanggil skrip dengan cara ini (perhatikan tidak adanya / di awal, lagi-lagi jalur relatif):
Anda akan mendapatkan nilai ini untuk dirname $ 0:
dan ./path/./script (bentuk lain dari jalur relatif) memberikan:
Hanya dua situasi di mana baseir $ 0 akan berfungsi adalah jika pengguna menggunakan sh atau sentuh untuk meluncurkan skrip karena keduanya akan menghasilkan $ 0:
yang akan memberi Anda jalan yang dapat Anda gunakan dengan dirname.
SOLUSINYA
Anda akan memiliki akun untuk dan mendeteksi setiap situasi yang disebutkan di atas dan menerapkan perbaikan untuk itu jika muncul:
sumber
One-liner ini memberitahukan di mana skrip shell berada, tidak masalah apakah Anda menjalankannya atau jika Anda membuatnya . Juga, ini menyelesaikan tautan simbolis yang terlibat, jika itu masalahnya:
Omong-omong, saya kira Anda menggunakan / bin / bash .
sumber
Begitu banyak jawaban, semuanya masuk akal, masing-masing dengan tujuan pro dan kontra & sedikit berbeda (yang mungkin harus dinyatakan untuk masing-masing). Berikut adalah solusi lain yang memenuhi tujuan utama yaitu menjadi jelas dan bekerja di semua sistem, pada semua bash (tidak ada asumsi tentang versi bash,
readlink
ataupwd
opsi), dan melakukan apa yang Anda harapkan terjadi secara wajar (misalnya, menyelesaikan symlink adalah suatu masalah yang menarik, tetapi biasanya tidak seperti yang Anda inginkan), menangani kasus tepi seperti spasi di jalur, dll., mengabaikan kesalahan apa pun dan menggunakan standar waras jika ada masalah.Setiap komponen disimpan dalam variabel terpisah yang dapat Anda gunakan secara individual:
sumber
Terinspirasi oleh jawaban blueyed
sumber
Itu harus melakukan trik:
Mungkin terlihat jelek tergantung pada bagaimana ia dipanggil dan cwd tetapi harus membuat Anda pergi ke mana Anda harus pergi (atau Anda dapat mengubah string jika Anda peduli bagaimana tampilannya).
sumber
`pwd`/`dirname $0`
tetapi mungkin masih gagal pada symlinks