Come ottenere la directory di origine di uno script Bash dallo script stesso


4955

Come posso ottenere il percorso della directory in cui si trova uno script Bash , all'interno di quello script?

Voglio usare uno script Bash come lanciatore per un'altra applicazione. Voglio cambiare la directory di lavoro in quella in cui si trova lo script Bash, così posso operare sui file in quella directory, in questo modo:

$ ./application

69
Nessuna delle soluzioni attuali funziona se sono presenti nuove righe alla fine del nome della directory : verranno rimosse dalla sostituzione del comando. Per ovviare a questo è possibile aggiungere un carattere non newline all'interno della sostituzione comando - DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd && echo x)"- e rimuoverlo senza una sostituzione comando - DIR="${DIR%x}".
l0b0

80
@ jpmc26 Ci sono due situazioni molto comuni: incidenti e sabotaggio. Una sceneggiatura non dovrebbe fallire in modi imprevedibili solo perché qualcuno, da qualche parte, ha fatto un mkdir $'\n'.
l0b0

24
chiunque lasci che le persone sabotino il loro sistema in quel modo non dovrebbe lasciarlo andare a colpire per rilevare tali problemi ... tanto meno assumere persone in grado di fare quel tipo di errore. Non ho mai avuto, nei 25 anni di utilizzo di bash, visto questo genere di cose accadere ovunque .... ecco perché abbiamo cose come il perl e pratiche come il controllo dei contaminanti (probabilmente sarò infiammato per aver detto che :)
osirisgothra,

3
@ l0b0 Considera che avresti bisogno della stessa protezione attiva dirnamee che la directory potrebbe iniziare con un -(es --help.). DIR=$(reldir=$(dirname -- "$0"; echo x); reldir=${reldir%?x}; cd -- "$reldir" && pwd && echo x); DIR=${DIR%?x}. Forse questo è eccessivo?
Score_Under

56
Consiglio vivamente di leggere queste FAQ Bash sull'argomento.
Rany Albeg Wein,

Risposte:


6574
#!/bin/bash

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

è un one-liner utile che ti darà il nome di directory completo dello script indipendentemente da dove viene chiamato.

Funzionerà finché l'ultimo componente del percorso utilizzato per trovare lo script non è un collegamento simbolico (i collegamenti alla directory sono OK). Se si desidera risolvere anche eventuali collegamenti allo script stesso, è necessaria una soluzione multilinea:

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

Quest'ultimo funziona con qualsiasi combinazione di alias, source, bash -c, link simbolici, ecc

Attenzione: se ti trovi cdin una directory diversa prima di eseguire questo snippet, il risultato potrebbe essere errato!

Inoltre, fai attenzione ai $CDPATHgotcha e agli effetti collaterali dell'output di stderr se l'utente ha sovrascritto in modo intelligente cd per reindirizzare invece l'output su stderr (incluse le sequenze di escape, come quando chiama update_terminal_cwd >&2su Mac). L'aggiunta >/dev/null 2>&1alla fine del cdcomando si prenderà cura di entrambe le possibilità.

Per capire come funziona, prova a eseguire questo modulo più dettagliato:

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

E stamperà qualcosa del tipo:

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
È possibile fondere questo approccio con la risposta user25866 per arrivare ad una soluzione che funziona con source <script>e bash <script>: DIR="$(cd -P "$(dirname "${BASH_SOURCE[0]}")" && pwd)".
Dan Molding,

21
A volte cdstampa qualcosa su STDOUT! Ad esempio, se il vostro $CDPATHha .. Per coprire questo caso, utilizzareDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" > /dev/null && pwd )"
user716468,

182
Questa risposta accettata non è ok, non funziona con i collegamenti simbolici ed è eccessivamente complessa. dirname $(readlink -f $0)è il comando giusto. Vedi gist.github.com/tvlooy/cbfbdb111a4ebad8b93e per un testcase
tvlooy

167
@tvlooy IMO la tua risposta non è esattamente OK così com'è, perché fallisce quando c'è uno spazio nel percorso. A differenza di un personaggio newline, questo non è improbabile o addirittura raro. dirname "$(readlink -f "$0")"non aggiunge complessità ed è la misura giusta più robusta per il minimo numero di problemi.
Adrian Günter,

11
@tvlooy il tuo commento non è compatibile con macOS (o probabilmente BSD in generale), mentre la risposta accettata è. readlink -f $0readlink: illegal option -- f.
Alexander Ljungberg,

876

Utilizzare dirname "$0":

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

l'utilizzo da pwdsolo non funzionerà se non si esegue lo script dalla directory in cui è contenuto.

[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
Per la portabilità oltre bash, $ 0 potrebbe non essere sempre sufficiente. Potrebbe essere necessario sostituire "type -p $ 0" per farlo funzionare se il comando è stato trovato sul percorso.
Darron,

10
@Darron: puoi usarlo solo type -pse lo script è eseguibile. Questo può anche aprire un buco sottile se lo script viene eseguito usando bash test2.she c'è un altro script con lo stesso nome eseguibile da qualche altra parte.
D.Shawley,

90
@Darron: ma poiché la domanda è taggata bashe la linea di hash-bang menziona esplicitamente /bin/bashdirei che è abbastanza sicuro dipendere da bashismi.
Joachim Sauer,

34
+1, ma il problema con l'utilizzo dirname $0è che se la directory è la directory corrente, otterrai .. Va bene a meno che tu non cambi le directory nello script e ti aspetti di usare il percorso che hai ottenuto dirname $0come se fosse assoluto. Per ottenere il percorso assoluto: pushd `dirname $0` > /dev/null, SCRIPTPATH=`pwd`, popd > /dev/null: pastie.org/1489386 (Ma sicuramente c'è un modo migliore per espandere questa strada?)
TJ Crowder

9
@TJ Crowder Non sono sicuro che dirname $0sia un problema se lo assegni a una variabile e poi lo usi per lanciare uno script simile $dir/script.sh; Immagino che questo sia il caso d'uso per questo tipo di cose il 90% delle volte. ./script.shfunzionerebbe benissimo.
matt b

516

Il dirnamecomando è il più semplice, analizzando semplicemente il percorso fino al nome del file fuori dalla $0variabile (nome dello script):

dirname "$0"

Ma, come ha sottolineato Matt B , il percorso restituito è diverso a seconda di come viene chiamato lo script. pwdnon fa il lavoro perché ti dice solo qual è la directory corrente, non in quale directory si trova lo script. Inoltre, se viene eseguito un collegamento simbolico a uno script, otterrai un percorso (probabilmente relativo) a dove risiede il collegamento, non lo script reale.

Alcuni altri hanno menzionato il readlinkcomando, ma nella sua forma più semplice è possibile utilizzare:

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

readlinkrisolverà il percorso dello script in un percorso assoluto dalla radice del filesystem. Pertanto, qualsiasi percorso contenente punti singoli o doppi, tilde e / o collegamenti simbolici verrà risolto in un percorso completo.

Ecco uno script che dimostra ciascuno di questi 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))"

Eseguendo questo script nella mia home directory, usando un percorso relativo:

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

Ancora una volta, ma usando l'intero percorso dello script:

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

Ora cambiare directory:

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

E infine usando un link simbolico per eseguire lo script:

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

13
readlinknon sarà disponibile in alcune piattaforme nell'installazione predefinita. Cerca di evitare di usarlo se puoi
TL

43
fai attenzione a citare tutto per evitare problemi di spazi bianchi:export SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
Catskul,

13
In OSX Yosemite 10.10.1 -fnon è riconosciuto come opzione per readlink. Utilizzando stat -finvece fa il lavoro. Grazie
cucu8,

11
In OSX, c'è greadlink, che è fondamentalmente il fatto readlinkche siamo tutti familiari. Ecco una versione indipendente dalla piattaforma:dir=`greadlink -f ${BASH_SOURCE[0]} || readlink -f ${BASH_SOURCE[0]}`
robert

6
Buona chiamata, @robert. Cordiali saluti, greadlinkpuò essere facilmente installato tramite 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

Funziona con tutte le versioni, incluso

  • quando chiamato tramite collegamento soft a profondità multipla,
  • quando il file è
  • quando lo script viene chiamato dal comando " source" aka .(punto) operatore.
  • quando arg $0viene modificato dal chiamante.
  • "./script"
  • "/full/path/to/script"
  • "/some/path/../../another/path/script"
  • "./some/folder/script"

In alternativa, se lo script bash stesso è un collegamento simbolico relativo, si desidera seguirlo e restituire il percorso completo dello script collegato:

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_PATHviene dato nel percorso completo, indipendentemente da come viene chiamato.
Assicurati solo di individuarlo all'inizio dello script.

Questo commento e codice Copyleft, licenza selezionabile ai sensi della GPL 2.0 o successive o CC-SA 3.0 (CreativeCommons Condividi allo stesso modo) o successive. (c) 2008. Tutti i diritti riservati. Nessuna garanzia di alcun tipo. Sei stato avvertito.
http://www.gnu.org/licenses/gpl-2.0.txt
http://creativecommons.org/licenses/by-sa/3.0/
18eedfe1c99df68dc94d4a94712a71aaa8e1e9e36cacf421b9463dd2bbaa02906d0d6656


4
Bello! Potrebbe essere abbreviato sostituendo "pushd [...] popd / dev / null" con SCRIPT_PATH = readlink -f $(dirname "${VIRTUAL_ENV}");
e-soddisfa il

5
E invece di usare pushd ...; non sarebbe meglio usare $ (cd dirname "${SCRIPT_PATH}"&& pwd)? Ma comunque ottima sceneggiatura!
ovanes,

6
È pericoloso che uno script cdesca dalla sua directory corrente nella speranza di cdtornare di nuovo più tardi: lo script potrebbe non avere il permesso di riportare la directory nella directory che era corrente quando è stata invocata. (Lo stesso vale per pushd / popd)
Adrian Pronk,

7
readlink -fè specifico per GNU. BSD readlinknon ha questa opzione.
Kara Brightwell,

3
Cosa c'è con tutte le sottotitoli inutili? ([ ... ])è meno efficiente di [ ... ]e non c'è alcun vantaggio derivante dall'isolamento offerto in cambio di quel colpo di prestazione qui.
Charles Duffy,

110

Risposta breve:

`dirname $0`

o ( preferibilmente ):

$(dirname "$0")

17
Non funzionerà se si procede allo script. "source my / script.sh"
Arunprasad Rajkumar,

Lo uso sempre nei miei script bash che automatizzano le cose e spesso invocano altri script nella stessa directory. Non userei mai sourcesu questi ed cd $(dirname $0)è facile da ricordare.
kqw,

16
@vidstige: ${BASH_SOURCE[0]}invece di $0funzionerà consource my/script.sh
Timothy Jones,

@TimothyJones che fallirà il 100% delle volte se proviene da una shell diversa da bash. ${BASH_SOURCE[0]}non è affatto soddisfacente. ${BASH_SOURCE:-0}è molto meglio.
Mathieu CAROFF,

106

Puoi usare $BASH_SOURCE:

#!/bin/bash

scriptdir=`dirname "$BASH_SOURCE"`

Nota che devi usare #!/bin/bashe non #!/bin/shperché è un'estensione di Bash.


14
Quando lo faccio ./foo/script, allora lo $(dirname $BASH_SOURCE)è ./foo.
Fino al

1
@Till, in questo caso possiamo usare il realpathcomando per ottenere il percorso completo di ./foo/script. Quindi dirname $(realpath ./foo/script) fornirà il percorso della sceneggiatura.
Purushothaman Poovai,

73

Questo dovrebbe farlo:

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

Funziona con collegamenti simbolici e spazi nel percorso.

Vedi le pagine man per dirnamee readlink.

Dalla traccia dei commenti sembra non funzionare con Mac OS. Non ho idea del perché. Eventuali suggerimenti?


6
con la tua soluzione, invocando lo script come ./script.shspettacoli .anziché il percorso completo della directory
Bruno Negrão Zica

5
Non esiste un'opzione -f per readlink su MacOS. Usa statinvece. Tuttavia, mostra .se ci si trova in questa "dir".
Denis The Menace,

2
È necessario installare coreutilsda Homebrew e utilizzare greadlinkper ottenere l' -fopzione su MacOS perché è * BSD sotto le copertine e non Linux.
dragon788,

Dovresti aggiungere doppie virgolette che circondano tutto il lato destro:DIR="$(dirname "$(readlink -f "$0")")"
hagello

60

pwdpuò essere usato per trovare la directory di lavoro corrente e dirnameper trovare la directory di un determinato file (il comando che è stato eseguito è $0, quindi dirname $0dovrebbe darti la directory dello script corrente).

Tuttavia, dirnamefornisce esattamente la parte della directory del nome file, che molto probabilmente sarà relativa alla directory di lavoro corrente. Se per qualche motivo lo script deve cambiare directory, l'output da dirnamediventa insignificante.

Suggerisco quanto segue:

#!/bin/bash

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

echo "Directory is $directory"

In questo modo, ottieni una directory assoluta, piuttosto che relativa.

Poiché lo script verrà eseguito in un'istanza bash separata, non è necessario ripristinare la directory di lavoro in seguito, ma se si desidera tornare allo script per qualche motivo, è possibile assegnare facilmente il valore di pwda una variabile prima di cambia directory, per uso futuro.

Anche se solo

cd `dirname $0`

risolve lo scenario specifico nella domanda, trovo che avere il percorso assoluto verso più più utile in generale.


9
Puoi fare tutto in una riga come questa: DIRECTORY = $ (cd dirname $0&& pwd)
dogbane,

Questo non funziona se lo script genera un altro script e si desidera conoscere il nome di quest'ultimo.
reinierpost,

52

Sono stanco di venire su questa pagina più e più volte per copiare e incollare la copertina nella risposta accettata. Il problema è che non è facile da capire e ricordare.

Ecco uno script facile da ricordare:

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

2
O, più oscuramente, su una riga: DIR=$(realpath "$(dirname "${BASH_SOURCE[0]}")")
agc

Perché questa non è la risposta accettata? C'è qualche differenza usando realpathdalla risoluzione "manuale" con un ciclo di readlink? Anche la readlinkpagina man diceNote realpath(1) is the preferred command to use for canonicalization functionality.
User9123

1
E comunque non dovremmo applicare realpathprima dirname, non dopo? Se il file di script stesso è un collegamento simbolico ... Darebbe qualcosa di simile DIR="$(dirname "$(realpath "${BASH_SOURCE[0]}")")". In realtà molto vicino alla risposta proposta da Simon.
Utente 9123

@ User9123 Penso che quello accettato sia tentato di essere compatibile con tutte le shell / distro popolari. Inoltre, a seconda di ciò che si sta tentando di fare, nella maggior parte dei casi le persone desiderano ottenere la directory in cui si trova il collegamento simbolico anziché la directory della fonte effettiva.
Wang,

37

Non penso che sia così facile come altri hanno capito. pwdnon funziona, poiché la directory corrente non è necessariamente la directory con lo script. $0non sempre ha neanche le informazioni. Considera i seguenti tre modi per invocare uno script:

./script

/usr/bin/script

script

Nel primo e terzo modo $0non ha le informazioni sul percorso completo. Nel secondo e terzo, pwdnon funziona. L'unico modo per ottenere la directory nel terzo modo sarebbe quello di eseguire il percorso e trovare il file con la corrispondenza corretta. Fondamentalmente il codice dovrebbe rifare ciò che fa il sistema operativo.

Un modo per fare ciò che stai chiedendo sarebbe semplicemente codificare i dati nella /usr/sharedirectory e fare riferimento a loro per il suo percorso completo. I dati non dovrebbero comunque trovarsi nella /usr/bindirectory, quindi questa è probabilmente la cosa da fare.


9
Se intendete confutare il suo commento, PROVATE che uno script PU access accedere a dove è archiviato con un esempio di codice.
Richard Duerr,

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

Questo è molto più breve della risposta scelta. E sembra funzionare altrettanto bene. Questo merita 1000 voti solo per non trascurare le persone.
Patrick,

2
Come molte delle risposte precedenti spiegare in dettaglio, né $0ne pwdsono garantiti per avere le informazioni giuste, a seconda di come lo script viene richiamato.
IMSoP,

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

Io preferisco, $BASH_SOURCEsopra $0, perché è esplicito anche per i lettori non ben versato in bash. $(dirname -- "$(readlink -f -- "$BASH_SOURCE")")
blobmaster

32

Questo ottiene l'attuale directory di lavoro su Mac OS X 10.6.6:

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

27

Questo è specifico di Linux, ma è possibile utilizzare:

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

1
È anche specifico per Bash, ma forse il comportamento di Bash è cambiato? /proc/fd/$$/255sembra puntare al tty, non a una directory. Ad esempio, nella mia shell di login corrente, i descrittori di file 0, 1, 2 e 255 fanno tutti riferimento /dev/pts/4. In ogni caso, il manuale di bash non menziona fd 255, quindi probabilmente non è saggio dipendere da questo comportamento. \
Keith Thompson

2
Shell interattiva! = Script. Comunque realpath ${BASH_SOURCE[0]};sembra essere il modo migliore per andare.
Steve Baker,

23

Ecco un one-liner conforme POSIX:

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

# test
echo $SCRIPT_PATH

4
Ho avuto successo con questo quando ho eseguito uno script da solo o usando sudo, ma non quando ho chiamato source ./script.sh
Michael R

E non riesce quando cdè configurato per stampare il nuovo nome del percorso.
Aaron Digulla,

18

Ho provato tutti questi e nessuno ha funzionato. Uno era molto vicino ma aveva un piccolo insetto che lo ha rotto gravemente; hanno dimenticato di avvolgere il percorso tra virgolette.

Inoltre, molte persone presumono che tu stia eseguendo lo script da una shell, quindi si dimenticano quando apri un nuovo script per impostazione predefinita a casa tua.

Prova questa directory per dimensioni:

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

Questo funziona bene indipendentemente da come o dove lo si esegue:

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

Quindi, per renderlo effettivamente utile, ecco come passare alla directory dello script in esecuzione:

cd "`dirname "$0"`"

4
Non funziona se lo script proviene da un altro script.
reinierpost,

Questo non funziona se l'ultima parte di $ 0 è un collegamento simbolico che punta a una voce di un'altra directory ( ln -s ../bin64/foo /usr/bin/foo).
hagello,

17

Ecco il modo semplice e corretto:

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

Spiegazione:

  • ${BASH_SOURCE[0]}- il percorso completo della sceneggiatura. Il valore di questo sarà corretto anche quando lo script è di provenienza, ad esempio source <(echo 'echo $0')stampa bash , mentre sostituendolo con ${BASH_SOURCE[0]}verrà stampato l'intero percorso dello script. (Naturalmente, questo presuppone che tu stia prendendo una dipendenza da Bash.)

  • readlink -f- Risolve ricorsivamente eventuali collegamenti simbolici nel percorso specificato. Questa è un'estensione GNU e non disponibile su (ad esempio) sistemi BSD. Se stai usando un Mac, puoi usare Homebrew per installare GNU coreutilse sostituirlo con greadlink -f.

  • E ovviamente dirnameottiene la directory principale del percorso.


1
greadlink -fpurtroppo non funziona in modo efficace quando si esegue sourcelo script su Mac :(
Gabe Kopley il

17

Il modo più breve ed elegante per farlo è:

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

Funzionerebbe su tutte le piattaforme ed è super pulito.

Maggiori dettagli sono disponibili in "In quale directory si trova lo script bash? ".


ottima soluzione pulita, ma non funzionerà se il file è simbolizzato.
ruuter,

16

Vorrei usare qualcosa del genere:

# 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

Questo è quello vero! Funziona anche con semplici sh! Problema con dirname "$0"soluzioni basate su semplici : se lo script si trova in $PATHe viene invocato senza percorso, daranno risultati errati.
Notinlist,

@Notinlist Non è così. Se lo script viene trovato tramite PATH, $0conterrà il nome file assoluto. Se lo script viene invocato con un nome file relativo o assoluto contenente a /, $0conterrà quello.
Neil Mayhew,

Non funzionerà per uno script di provenienza.
Amit Naidu,

16

Questa è una leggera revisione della soluzione e-satis e 3bcdnlklvc04a ha sottolineato nella loro risposta :

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

Questo dovrebbe funzionare comunque in tutti i casi che hanno elencato.

Ciò impedirà popddopo un fallimento pushd, grazie a konsolebox.


Funziona perfettamente per ottenere il dirname "reale", piuttosto che solo il nome di un collegamento simbolico. Grazie!
Jay Taylor,

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

@konsolebox, cosa stai cercando di difendere? In genere sono un fan dei condizionali logici in linea, ma qual è stato l'errore specifico che stavi vedendo nel pushd? Preferirei piuttosto trovare un modo per gestirlo direttamente invece di restituire un SCRIPT_DIR vuoto.
Fuwjax

@Fuwjax Pratica naturale per evitare di fare popdin casi (anche quando rari) in cui pushdfallisce. E in caso di pushdinsuccesso, quale pensi dovrebbe essere il valore SCRIPT_DIR? L'azione può variare in base a ciò che può sembrare logico o a ciò che un utente potrebbe preferire, ma certamente, fare popdè sbagliato.
konsolebox,

Tutti questi pushd popdpericoli potrebbero essere evitati semplicemente lasciandoli cadere e usando invece cd+ pwdracchiuso in una sostituzione di comando. SCRIPT_DIR=$(...)
Amit Naidu,

16

Per i sistemi con coreutils GNU readlink(es. Linux):

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

Non è necessario utilizzare BASH_SOURCEquando $0contiene il nome file dello script.


3
a meno che la sceneggiatura non sia stata fornita. o 'source', nel qual caso sarà comunque qualunque script lo abbia, oppure, se dalla riga di comando, '-bash' (tty login) o 'bash' (invocato tramite 'bash -l') o '/ bin / bash '(invocato come shell interattiva senza accesso)
osirisgothra,

Ho aggiunto una seconda coppia di citazioni intorno alla dirnamechiamata. Necessario se il percorso della directory contiene spazi.
user1338062,

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

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

scriptdir=`dirname "$PRG"`

Non l'ho testato su diversi sistemi. Ma questa soluzione è quella che funziona subito almeno su Ubuntu, per me!
Natus Drew

$0non funzionerà per uno script di provenienza
Amit Naidu,

13

$_vale la pena menzionare come alternativa a $0. Se stai eseguendo uno script da Bash, la risposta accettata può essere abbreviata in:

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

Nota che questa deve essere la prima affermazione nel tuo script.


4
Si rompe se tu sourceo .la sceneggiatura. In quelle situazioni, $_conterrebbe l'ultimo parametro dell'ultimo comando eseguito prima di .. $BASH_SOURCEfunziona ogni volta.
clacke,

11

Ho confrontato molte delle risposte fornite e ho trovato alcune soluzioni più compatte. Questi sembrano gestire tutti i casi pazzi che derivano dalla tua combinazione preferita di:

  • Percorsi assoluti o percorsi relativi
  • Collegamenti software per file e directory
  • Invocazione come script, bash script, bash -c script,source script , o. script
  • Spazi, schede, newline, unicode, ecc. Nelle directory e / o nel nome file
  • Nomi di file che iniziano con un trattino

Se stai eseguendo da Linux, sembra che l'utilizzo procdell'handle sia la soluzione migliore per individuare l'origine completamente risolta dello script attualmente in esecuzione (in una sessione interattiva, il collegamento punta al rispettivo /dev/pts/X):

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

Questo ha un po 'di bruttezza, ma la correzione è compatta e facile da capire. Non stiamo usando solo primitivi bash, ma sto bene perché readlinksemplifica notevolmente l'attività. Il echo Xaggiunge un Xalla fine della stringa variabile in modo che lo spazio bianco finale nel nome del file non viene consumato, e il parametro di sostituzione ${VAR%X}alla fine della linea si libera del X. Perché per rappresentare newline (è anche così che puoi creare facilmente directory e file con nomi subdoli).readlink aggiunge una sua nuova riga (che normalmente verrebbe consumata nella sostituzione del comando se non fosse per il nostro inganno precedente), dobbiamo sbarazzarci anche di quello. Questo è più facile da realizzare usando lo $''schema di quotazione, che ci consente di usare sequenze di escape come\n

Quanto sopra dovrebbe coprire le tue esigenze per individuare lo script attualmente in esecuzione su Linux, ma se non hai il procfilesystem a tua disposizione, o se stai cercando di individuare il percorso completamente risolto di qualche altro file, forse potresti trova utile il codice qui sotto. È solo una leggera modifica rispetto al sopra uno strato. Se stai giocando con strani nomi / directory, controlla l'output con entrambi lsed readlinkè informativo, così come l' lsoutput di percorsi "semplificati", sostituendo ?cose come newline.

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"

Ottengo /dev/pts/30con bash sul desktop Ubuntu 14.10.
Dan Dascalescu,

@DanDascalescu Usando il one-liner? O lo snippet di codice completo in fondo? E gli stavi dando qualche nome difficile?
Billyjmc,

L'una linea più un'altra linea echo $resolved, ho salvato come d, chmod +x d, ./d.
Dan Dascalescu,

@DanDascalescu La prima riga della tua sceneggiatura deve essere#!/bin/bash
billyjmc

10

Prova a usare:

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

1
Tutto quello che voglio sapere è, perché in questo modo non è buono? Non mi è sembrato male e corretto per me. Qualcuno potrebbe spiegare perché è downvoted?
Shou Ya,

7
realpath non è un'utilità standard.
Steve Bennett,

2
Su Linux, realpath è un'utilità standard (parte del pacchetto GNU coreutils), ma non è un built-in bash (cioè una funzione fornita da bash stesso). Se stai eseguendo Linux, questo metodo probabilmente funzionerà, anche se lo sostituirei $0in ${BASH_SOURCE[0]}modo che questo metodo funzioni ovunque, anche in una funzione.
Doug Richardson,

3
L'ordine delle operazioni in questa risposta è errato. Devi prima risolvere il link simbolico, quindi fallo dirnameperché l'ultima parte di $0potrebbe essere un link simbolico che punta a un file che non si trova nella stessa directory del link simbolico stesso. La soluzione descritta in questa risposta ottiene solo il percorso della directory in cui è memorizzato il collegamento simbolico, non la directory della destinazione. Inoltre, questa soluzione non contiene citazioni. Non funzionerà se il percorso contiene caratteri speciali.
hagello,

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

9

Prova la seguente soluzione cross-compatibile:

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

come comandi come realpatho readlinkpotrebbero non essere disponibili (a seconda del sistema operativo).

Nota: in Bash, si consiglia di utilizzare ${BASH_SOURCE[0]}invece di $0, altrimenti il ​​percorso può interrompersi durante l'approvvigionamento del file ( source/ .).

In alternativa puoi provare la seguente funzione in bash:

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

Questa funzione accetta 1 argomento. Se l'argomento ha già un percorso assoluto, stampalo così com'è, altrimenti stampa l' $PWDargomento variabile + nomefile (senza ./prefisso).

Relazionato:


Spiega di più sulla funzione realpath.
Chris,

1
La realpathfunzione @Chris accetta 1 argomento. Se l'argomento ha già un percorso assoluto, stampalo così com'è, altrimenti stampa $PWD+ nomefile (senza ./prefisso).
Kenorb,

La tua soluzione cross-compatibile non funziona quando lo script è collegato in modo simbolico.
Jakub Jirutka,

9

Credo di avere questo. Sono in ritardo alla festa, ma penso che alcuni apprezzeranno il fatto di essere qui se si imbattono in questa discussione. I commenti dovrebbero spiegare:

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

Questi sono modi brevi per ottenere informazioni sugli script:

Cartelle e file:

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

Utilizzando questi comandi:

    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`

E ho ottenuto questo risultato:

     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

Vedi anche: https://pastebin.com/J8KjxrPF



Penso che la mia risposta sia ok perché è difficile trovare una semplice edizione funzionante. Qui puoi prendere il codice che ti piace, ad esempio cd + pwd, dirname + realpath o dirname + readlink. Non sono sicuro che tutte le parti esistano prima e che la maggior parte delle risposte siano complesse e sovraccariche. Qui puoi estrarre il codice che ti piace usare. Almeno, per favore, non rimuoverlo come ho bisogno in futuro: D
Utente 8461

8

Funziona in bash-3.2:

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

Se hai una ~/bindirectory nella tua $PATH, hai Adentro questa directory. Fonte la sceneggiatura ~/bin/lib/B. Sai dove lo script incluso è relativo a quello originale, nella libsottodirectory, ma non dove è relativo alla directory corrente dell'utente.

Questo è risolto dal seguente (interno A):

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

Non importa dove si trovi l'utente o come chiama lo script, funzionerà sempre.


3
Il punto whichè molto discutibile. type, hashe altri builtin fanno la stessa cosa meglio in bash. whichè un po 'più portatile, anche se in realtà non è lo stesso whichusato in altre shell come TCS, che lo ha incorporato.
Ripristina Monica Please,

"Sempre"? Affatto. whichessendo uno strumento esterno, non hai motivo di credere che si comporti in modo identico alla shell genitore.
Charles Duffy,

7

La migliore soluzione compatta a mio avviso sarebbe:

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

Non si fa affidamento su altro che Bash. L'uso di dirname, readlinke basenamealla fine porterà a problemi di compatibilità, quindi è meglio evitare, se possibile.


2
Probabilmente si dovrebbe aggiungere barra a che: "$( cd "$( echo "${BASH_SOURCE[0]%/*}/" )"; pwd )". In caso contrario, avresti problemi con la directory principale. Inoltre, perché devi usare l'eco?
konsolebox,

dirnamee basenamePOSIX sono standardizzati, quindi perché evitare di usarli? Links: dirname,basename
myrdd

Prevenire due fork di processo aggiuntivi e attenersi agli incorporamenti della shell può essere uno dei motivi.
Amit Naidu,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.