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?

William Yeung
sumber
12
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.
BoltClock
2
Saya pikir jawaban ini lebih baik: Mendapatkan direktori sumber skrip Bash dari dalam
Alan Zhiliang Feng

Jawaban:

568

Di Bash, Anda harus mendapatkan yang Anda butuhkan seperti ini:

#!/usr/bin/env bash

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

Lihat juga: https://stackoverflow.com/a/246128/59087

Al.
sumber
12
Catatan: Tidak semua sistem memiliki readlink. Itu sebabnya saya merekomendasikan menggunakan pushd / popd (built-in untuk bash).
docwhat
23
The -fpilihan untuk readlinkmelakukan sesuatu yang berbeda pada OS X (Lion) dan mungkin BSD. stackoverflow.com/questions/1055671/…
Ergwun
9
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")"

Manual Referensi Bash, 5.2 Variabel Bash

Daniel
sumber
3
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)
JL_SO
40

Dengan asumsi Anda menggunakan bash

#!/bin/bash

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

echo $current_dir
echo $script_dir

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.

Mez
sumber
3
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}"
docwhat
sumber
4
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 '.'

Untuk menyiasatinya gunakan:

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

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

Anda sekarang dapat menggunakan variabel current_dir di seluruh skrip Anda untuk merujuk ke direktori skrip. Namun ini mungkin masih memiliki masalah symlink.

ranamalo
sumber
20

Jawaban terbaik untuk pertanyaan ini dijawab di sini:
Mendapatkan direktori sumber skrip Bash dari dalam

Dan itu adalah:

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 located
  fi
done
echo "SOURCE is '$SOURCE'"
RDIR="$( dirname "$SOURCE" )"
DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
if [ "$DIR" != "$RDIR" ]; then
  echo "DIR '$RDIR' resolves to '$DIR'"
fi
echo "DIR is '$DIR'"
Alexandro de Oliveira
sumber
14
cd $(dirname $(readlink -f $0))
kebiru-biruan
sumber
10

Mari menjadikannya POSIX oneliner:

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

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/

Ján Sáreník
sumber
1
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?
spechter
1
@Spechter ya, ada perbaikan untuk itu. Lihat pembaruan jasan.tk/posix/2017/05/11/posix_shell_dirname_replacement
Ján Sáreník
@Der_Meister - harap lebih spesifik. Atau tulis (dan mungkin enkripsi) email ke jasan di jasan.tk
Ján Sáreník
2
ln -s / home / der / 1 / test / home / der / 2 / test && / home / der / 2 / test => / home / der / 2 (harus menunjukkan jalur ke skrip asli)
Der_Meister
10
BASE_DIR="$(cd "$(dirname "$0")"; pwd)";
echo "BASE_DIR => $BASE_DIR"
pencarian
sumber
3
cara non-bash-spesifik paling dapat diandalkan yang saya tahu.
eddygeek
10

Jika Anda ingin mendapatkan direktori skrip aktual (terlepas dari apakah Anda menggunakan skrip menggunakan symlink atau langsung), cobalah:

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

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.

Rohith
sumber
6

PENGANTAR

Jawaban ini mengoreksi jawaban yang dipilih sangat rusak namun mengejutkan atas utas ini (ditulis oleh TheMarko):

#!/usr/bin/env bash

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

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

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

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

if [ "$debug" = true ]; then echo "fixed BASEDIR=$BASEDIR";fi
aturan kebun
sumber
4

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:

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

Omong-omong, saya kira Anda menggunakan / bin / bash .

Richard Gomes
sumber
2

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)"
michael
sumber
1

Terinspirasi oleh jawaban blueyed

read < <(readlink -f $0 | xargs dirname)
cd $REPLY
Steven Penny
sumber
-4

Itu harus melakukan trik:

echo `pwd`/`dirname $0`

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

kenorb
sumber
1
masalah melarikan diri stackoverflow di sini: itu pasti akan terlihat seperti ini: `pwd`/`dirname $0`tetapi mungkin masih gagal pada symlinks
Andreas Dietrich