Unix kabuk komut dosyası, komut dosyasının hangi dizinde bulunduğunu öğrenir mi?


511

Temelde ben komut dosyasını kabuk komut dosyası dosya konumu ile ilgili yolları ile çalıştırmak gerekir, nasıl geçerli dizini komut dosyası bulunduğu nerede aynı dizine değiştirebilirsiniz?


12
Bu gerçekten bir kopya mı? Bu soru bir "unix kabuk betiği", diğeri özellikle Bash hakkındadır.
michaeljt

2
@BoltClock: Bu soru yanlış kapatıldı. Bağlantılı soru Bash hakkında. Bu soru Unix kabuk programlama ile ilgilidir. Kabul edilen cevapların oldukça farklı olduğuna dikkat edin!
Dietrich Epp

@Dietrich Epp: Haklısın. Anlaşılan kişinin kabul edilen cevap seçimi ve [bash] etiketinin eklenmesi (muhtemelen buna yanıt olarak) beni bir bayrağa yanıt olarak mükerrer olarak işaretlememe neden oldu.
BoltClock


Yanıtlar:


568

Bash'de ihtiyacınız olan şeyi şu şekilde almalısınız:

#!/usr/bin/env bash

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

17
Komut dosyasını farklı bir dizindeki sembolik bir bağlantıyla çağırdıysanız bu çalışmaz. Bu işi yapmak için de kullanmanız gerekir readlink(aşağıdaki
al'ın

44
Bash'da bunun $BASH_SOURCEyerine kullanmak daha güvenlidir $0, çünkü $0her zaman bir komut dosyasının 'kaynaklanması' gibi çağrılan komut dosyasının yolunu içermez.
mklement0

2
$BASH_SOURCEBash'a özgüdür, soru genel olarak kabuk betiği ile ilgilidir.
Ha-Duong Nguyen

7
@auraham: CUR_PATH=$(pwd)veya pwdgeçerli dizini döndürür (komut dosyasının üst dizini olması gerekmez)!
Andreas Dietrich

5
@ Mklement0 önerilen, yöntemini denedim ve $BASH_SOURCEben ne gerek döndürür. Benim komut dosyası başka bir komut dosyası aradı, ediliyor $0döner .iken $BASH_SOURCEdöner (benim durumumda sağ alt dizin scripts).
David Rissato Cruz

401

Orijinal gönderi çözümü içerir (yanıtları yoksayın, yararlı bir şey eklemezler). İlginç iş readlinkseçeneği ile belirtilen unix komutu ile yapılır -f. Komut dosyası hem mutlak hem de göreli bir yolla çağrıldığında çalışır.

Bash, sh, ksh için:

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

Tcsh, csh için:

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

Ayrıca bakınız: https://stackoverflow.com/a/246128/59087


12
Not: Tüm sistemlerde yoktur readlink. Bu yüzden pushd / popd (bash için yerleşik) kullanmanızı tavsiye ettim.
docwhat

23
-fSeçeneği readlinkmuhtemelen OS X (Lion) ve BSD üzerinde farklı bir şey yok. stackoverflow.com/questions/1055671/…
Ergwun

9
@ Ergwun'un açıklamasını netleştirmek için: -fOS X'te hiç desteklenmiyor (Lion'dan itibaren); orada -f, örneğin en fazla bir dolaylı seviyeyi çözmeyle yapmak için bırakabilir pushd "$(dirname "$(readlink "$BASH_SOURCE" || echo "$BASH_SOURCE")")"veya bağlantılı yayında gösterildiği gibi kendi özyinelemeli symlink-takip komut dosyanızı yuvarlayabilirsiniz.
mklement0

2
Hala OP'nin neden mutlak yola ihtiyacı olacağını anlamıyorum. "." Bildiriliyor. komut dosyalarına göre dosyalara erişmek ve ./myscript.sh gibi komut dosyasını çağırdıysanız
Stefan Haberl

10
@StefanHaberl Mevcut çalışma dizininiz komut dosyasının konumundan farklıyken komut dosyasını çalıştırmanızın bir sorun olacağını düşünüyorum (örneğin sh /some/other/directory/script.sh), bu durumda .pwd'niz olurdu, değil/some/other/directory
Jon z

51

Bir cevap üzerine daha önce yapılan bir yorumda bunu söyledi, ancak diğer tüm cevapları kaçırmak kolaydır.

Bash kullanırken:

echo this file: "$BASH_SOURCE"
echo this dir: "$(dirname "$BASH_SOURCE")"

Bash Referans Kılavuzu, 5.2 Bash Değişkenleri


3
Sadece bu ortam yolunda komut dosyası ile çalışır, en çok oy alanlar çalışmaz. teşekkür ederim!
Temmuz

Bunun dirname "$BASH_SOURCE"yerine $ BASH_SOURCE içindeki boşlukları işlemek için kullanmalısınız .
Mygod

1
ShellCheck aracına göre dizini yazdırmanın daha açık bir yolu şu olacaktır: "$ (dirname" $ ​​{BASH_SOURCE {0}} ")" çünkü BASH_SOURCE bir dizi ve alt simge olmadan ilk öğe varsayılan olarak alınır .
Adrian M.

1
@AdrianM. , dizin için parantez değil, parantez istiyorsun:"$(dirname "${BASH_SOURCE[0]}")"
Hashbrown

Benim için iyi değil .. sadece bir nokta yazdırır (örneğin, şu anki dizin)
JL_SO

40

Bash kullandığınızı varsayarsak

#!/bin/bash

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

echo $current_dir
echo $script_dir

Bu komut dosyasının içinde bulunduğunuz dizini ve sonra komut dosyasının içinde bulunduğu dizini yazdırması gerekir. Örneğin /, komut dosyası ile çağrıldığında /home/mez/çıktı verir

/
/home/mez

Unutmayın, bir komutun çıkışından değişkenler atarken, komutu içine sarın $(ve )- veya istediğiniz çıktıyı alamayacağınızı unutmayın.


3
Komut dosyasını geçerli dizinden çağırdığımda bu çalışmaz.
Eric Wang

1
@EricWang her zaman geçerli dizinde bulunuyorsunuz.
ctrl-alt-delor

Bana göre, $ current_dir gerçekten komut dosyasını çağırdığım yoldur. Ancak, $ script_dir betiğin komutu değil, sadece bir nokta.
Michael

31

Eğer bash kullanıyorsanız ....

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

4
Sen yerini alabilir pushd/ popdile cd $(dirname "${0}")ve cd -onlar varsa, diğer kabukları üzerinde çalışmasını sağlamak için pwd -L.
docwhat

neden burada pushd ve popd kullanıyorsunuz?
qodeninja

1
Bu yüzden orijinal dizini bir değişkende saklamak zorunda değilim. Bu fonksiyonlarda çok kullandığım bir model. Gerçekten iyi yuvalar, ki bu iyidir.
Mart'ta docwhat

Betiğinizde bir değişkene başvurulup başvurulmadığına bakılmaksızın bellekte - değişkende - depolanıyor. Ayrıca, pushd ve popd yürütme maliyetinin, hem CPU döngülerinde hem de okunabilirlikte, yerel bir Bash değişkeni oluşturmamanın tasarruflarından çok daha ağır bastığına inanıyorum.
ingyhere

20

Marko'nun önerdiği gibi:

BASEDIR=$(dirname $0)
echo $BASEDIR

Komut dosyasını, komut dosyasının bulunduğu aynı dizinden yürütmediğiniz sürece çalışır, bu durumda '.' Değeri alırsınız.

Bu sorunu çözmek için şunu kullanın:

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

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

Artık komut dosyası dizinine başvurmak için komut dosyanız boyunca current_dir değişkenini kullanabilirsiniz. Ancak bunun yine de bağlantı bağlantısı sorunu olabilir.


20

Bu soru için en iyi cevap burada cevaplandı:
Bir Bash betiğinin kaynak dizinini içeriden almak

Ve budur:

DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"

Nereden çağrılırsa çağrılsın size komut dosyasının tam dizin adını verecek tek satırlık.

Nasıl çalıştığını anlamak için aşağıdaki komut dosyasını yürütebilirsiniz:

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


10

Bir POSIX oneliner yapalım:

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

BSD olanlar dahil Bourne uyumlu birçok mermi üzerinde test edildi.

Bildiğim kadarıyla ben yazarım ve bunu kamu malı haline getirdim. Daha fazla bilgi için bkz. Https://www.jasan.tk/posts/2017-05-11-posix_shell_dirname_replacement/


1
yazılı olarak, cd: too many argumentseğer yoldaki boşluklar ve döner $PWD. (bariz bir düzeltme, ama aslında kaç tane kenar durum olduğunu gösterir)
michael

1
Olur. @Michael'den yoldaki boşluklarla başarısız olduğu yorumu dışında ... Bunun için bir düzeltme var mı?
spechter

1
@ spechter evet, bunun için bir düzeltme var. Güncellenmiş jasan.tk/posix/2017/05/11/posix_shell_dirname_replacement
Ján Sáreník

@Der_Meister - lütfen daha spesifik olun. Veya jasan.tk adresinde jasan adresine bir e-posta yaz (ve muhtemelen şifrelemek)
Ján Sáreník

2
ln -s / home / der / 1 / test / home / der / 2 / test && / home / der / 2 / test => / home / der / 2 (bunun yerine orijinal komut dosyasının yolunu göstermelidir)
Der_Meister

10
BASE_DIR="$(cd "$(dirname "$0")"; pwd)";
echo "BASE_DIR => $BASE_DIR"

3
en güvenilir bash'a özgü olmayan yolu biliyorum.
eddygeek

9

Gerçek komut dosyası dizinini almak istiyorsanız (komut dosyasını bir symlink kullanarak mı yoksa doğrudan mı çağırdığınızdan bağımsız olarak) şunu deneyin:

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

Bu hem linux hem de macOS üzerinde çalışır. Burada kimsenin bahsettiğini göremedim realpath. Bu yaklaşımda herhangi bir dezavantaj olup olmadığından emin değilim.

macOS'ta coreutilskullanmak için yüklemeniz gerekir realpath. Ör: brew install coreutils.


6

GİRİŞ

Bu cevap , bu konunun çok kırık ama şok edici bir şekilde en çok oy alan cevabını düzeltir (TheMarko tarafından yazılmıştır):

#!/usr/bin/env bash

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

KENDİ ÜZERİNE "$ 0" dirname NEDEN ÇALIŞMIYOR?

dirname $ 0 yalnızca kullanıcı komut dosyasını çok özel bir şekilde başlatırsa çalışır. Bu cevabın başarısız olduğu ve senaryoyu çökerttiği birkaç durum bulabildim.

Her şeyden önce, bu cevabın nasıl çalıştığını anlayalım. Senaryo dizinini yaparak

dirname "$0"

$ 0 , komut dosyasını çağıran komutun ilk bölümünü temsil eder (temelde argümanlar olmadan girilen komuttur:

/some/path/./script argument1 argument2

0 $ = "/ bir / yol /./ komut dosyası"

dirname temelde bir dizede son / dizeyi bulur ve orada keser. Yani eğer yaparsanız:

  dirname /usr/bin/sha256sum

alacaksınız: / usr / bin

/ Usr / bin / sha256sum düzgün biçimlendirilmiş bir yol olduğu için bu örnek iyi çalışıyor ancak

  dirname "/some/path/./script"

iyi çalışmaz ve size:

  BASENAME="/some/path/." #which would crash your script if you try to use it as a path

Senaryonuzla aynı direkte olduğunuzu ve bunu bu komutla başlattığınızı varsayalım

./script   

Bu durumda 0 $ olur ./script ve $ 0 dizin adı verir:

. #or BASEDIR=".", again this will crash your script

Kullanımı:

sh script

Tam yol girilmeden bir BASEDIR = "verecektir."

Göreli dizinleri kullanma:

 ../some/path/./script

0 $ değerinde bir dirname verir:

 ../some/path/.

/ Some dizinindeyseniz ve komut dosyasını bu şekilde çağırıyorsanız (başlangıçta / bulunmadığını, yine göreceli bir yol olduğunu unutmayın):

 path/./script.sh

Bu değeri $ 0 dizin adı için alacaksınız:

 path/. 

ve ./path/./script (göreceli yolun başka bir biçimi) şunları verir:

 ./path/.

0 $ temelli çalışmanın yalnızca iki durumu , kullanıcının bir komut dosyası başlatmak için sh veya touch kullanmasıdır, çünkü her ikisi de 0 $ ile sonuçlanır:

 $0=/some/path/script

size dirname ile kullanabileceğiniz bir yol verecektir.

ÇÖZÜM

Yukarıda belirtilen durumların her birini hesaba katar ve tespit eder ve ortaya çıkarsa bir düzeltme uygularsınız:

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

4

Bu tek satır, kabuk komut dosyasının nerede olduğunu söyler, çalıştırdığınız veya kaynakladığınız önemli değildir . Ayrıca, söz konusu ise, söz konusu sembolik bağlantıları çözer:

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

Bu arada, / bin / bash kullandığınızı düşünüyorum .


2

Pek çok yanıt, hepsi akla yatkın, her biri pro ve con'un ve biraz farklı hedefleri (muhtemelen her biri için belirtilmelidir). Burada, tüm sistemlerde, hem açık hem de tüm sistemlerde çalışmanın (bash sürümleri veya readlinkveya pwdseçeneklerle ilgili herhangi bir varsayım yok) birincil amacını karşılayan ve gerçekleşmesini beklediğinizi makul bir şekilde yapan (ör. Sembolik bağlantıları çözmek) ilginç bir sorun, ancak genellikle aslında istediğiniz şey değildir), yollardaki boşluklar gibi kenar durumlarını ele alın, herhangi bir hatayı yok sayar ve herhangi bir sorun varsa aklı başında bir varsayılan kullanır.

Her bileşen ayrı ayrı kullanabileceğiniz ayrı bir değişkende saklanır:

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


-4

Hile yapmalı:

echo `pwd`/`dirname $0`

Nasıl çağrıldığına ve cwd'ye bağlı olarak çirkin görünebilir, ancak gitmeniz gereken yere ulaşmanızı sağlar (veya nasıl göründüğünü önemsiyorsanız dizeyi değiştirebilirsiniz).


1
stackoverflow kaçış sorunu burada: kesinlikle böyle görünmelidir: `pwd`/`dirname $0`ama yine de semboller başarısız olabilir
Andreas Dietrich
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.