Script shell Unix scoprire quale directory risiede il file di script?


511

Fondamentalmente ho bisogno di eseguire lo script con percorsi relativi al percorso del file di script della shell, come posso cambiare la directory corrente nella stessa directory in cui risiede il file di script?


12
È davvero un duplicato? Questa domanda riguarda uno "script di shell unix", l'altro in particolare su Bash.
michaeljt,

2
@BoltClock: questa domanda è stata chiusa in modo improprio. La domanda collegata riguarda Bash. Questa domanda riguarda la programmazione della shell Unix. Si noti che le risposte accettate sono abbastanza diverse!
Dietrich Epp,

@Dietrich Epp: hai ragione. Sembra che la scelta della risposta accettata da parte del richiedente e l'aggiunta del tag [bash] (probabilmente in risposta a quello) mi abbiano portato a contrassegnare la domanda come duplicato in risposta a una bandiera.
BoltClock


Risposte:


568

In Bash, dovresti ottenere ciò di cui hai bisogno in questo modo:

#!/usr/bin/env bash

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

17
Questo non funziona se hai chiamato lo script tramite un collegamento simbolico in una directory diversa. Per fare quel lavoro devi anche usare readlink(vedi la risposta di al sotto)
AndrewR

44
In bash è più sicuro da usare $BASH_SOURCEal posto di $0, perché $0non contiene sempre il percorso dello script che viene invocato, come quando si "procura" uno script.
mklement0

2
$BASH_SOURCEè specifico di Bash, la domanda riguarda lo script di shell in generale.
Ha-Duong Nguyen,

7
@auraham: CUR_PATH=$(pwd)oppure pwdrestituisci la directory corrente (che non deve essere la directory principale degli script)!
Andreas Dietrich,

5
Ho provato il metodo @ mklement0 raccomandato, usando $BASH_SOURCEe restituisce ciò di cui avevo bisogno. Il mio script viene chiamato da un altro script e $0restituisce .mentre $BASH_SOURCErestituisce la sottodirectory corretta (nel mio caso scripts).
David Rissato Cruz,

402

Il post originale contiene la soluzione (ignora le risposte, non aggiungono nulla di utile). L'interessante lavoro viene svolto dal menzionato comando unix readlinkcon opzione -f. Funziona quando lo script viene chiamato da un percorso assoluto e relativo.

Per 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

Per 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

Vedi anche: https://stackoverflow.com/a/246128/59087


12
Nota: non tutti i sistemi hanno readlink. Ecco perché ho raccomandato di usare pushd / popd (built-in per bash).
docwhat,

23
L' -fopzione per readlinkfare qualcosa di diverso su OS X (Lion) e possibilmente BSD. stackoverflow.com/questions/1055671/...
Ergwun

9
Per chiarire il commento di @Ergwun: -fnon è supportato affatto su OS X (come da Lion); lì puoi o lasciar perdere -fper accontentarti di risolvere al massimo un livello di riferimento indiretto, ad esempio pushd "$(dirname "$(readlink "$BASH_SOURCE" || echo "$BASH_SOURCE")")", oppure puoi creare il tuo script ricorsivo che segue il link simbolico, come dimostrato nel post collegato.
mklement0

2
Ancora non capisco, perché l'OP avrebbe bisogno del percorso assoluto. Segnalazione "." dovrebbe funzionare bene se vuoi accedere ai file relativi al percorso degli script e hai chiamato lo script come ./myscript.sh
Stefan Haberl

10
@StefanHaberl Penso che sarebbe un problema se eseguissi lo script mentre la tua directory di lavoro attuale era diversa dalla posizione dello script (ad esempio sh /some/other/directory/script.sh), in questo caso .sarebbe il tuo pwd, non/some/other/directory
Jon z

51

Un precedente commento su una risposta lo diceva, ma è facile perdere tra tutte le altre risposte.

Quando si utilizza bash:

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

Manuale di riferimento di Bash, 5.2 Variabili di Bash


3
Solo questo funziona con lo script nel percorso ambiente, i più votati non funzionano. grazie!
luglio

Si dovrebbe usare dirname "$BASH_SOURCE"invece per gestire gli spazi in $ BASH_SOURCE.
Mygod

1
Un modo più esplicito per stampare la directory sarebbe, secondo lo strumento ShellCheck, "$ (dirname" $ ​​{BASH_SOURCE {0}} ")" perché BASH_SOURCE è un array e senza il pedice, il primo elemento è preso di default .
Adrian M.,

1
@AdrianM. , vuoi parentesi, non parentesi graffe, per l'indice:"$(dirname "${BASH_SOURCE[0]}")"
Hashbrown,

Non va bene per me ... stampa solo un punto (cioè la directory corrente, presumibilmente)
JL_SO

40

Supponendo che stai usando bash

#!/bin/bash

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

echo $current_dir
echo $script_dir

Questo script dovrebbe stampare la directory in cui ci si trova e quindi la directory in cui si trova lo script. Ad esempio, quando lo si chiama /con lo script in /home/mez/, viene generato

/
/home/mez

Ricorda, quando assegni le variabili dall'output di un comando, includi il comando $(e )- o non otterrai l'output desiderato.


3
Questo non funzionerà quando invoco lo script dalla directory corrente.
Eric Wang

1
@EricWang sei sempre nella directory corrente.
ctrl-alt-delor,

Per me, $ current_dir è davvero il percorso da cui sto chiamando lo script. Tuttavia, $ script_dir non è la directory dello script, è solo un punto.
Michael,

31

Se stai usando 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}"

4
È possibile sostituire pushd/ popdcon cd $(dirname "${0}")e cd -per farlo funzionare su altre shell, se hanno a pwd -L.
docwhat,

perché dovresti usare pushd e popd qui?
qodeninja,

1
Quindi non devo memorizzare la directory originale in una variabile. È un modello che uso molto nelle funzioni e cose del genere. Nidifica davvero bene, il che è positivo.
docwhat il

Viene ancora archiviato in memoria, in una variabile, indipendentemente dal fatto che una variabile faccia riferimento o meno allo script. Inoltre, credo che il costo dell'esecuzione di pushd e popd superi di gran lunga i risparmi derivanti dalla mancata creazione di una variabile Bash locale nello script, sia nei cicli della CPU che nella leggibilità.
inghehere

20

Come suggerisce il Marko:

BASEDIR=$(dirname $0)
echo $BASEDIR

Funziona a meno che non si esegua lo script dalla stessa directory in cui risiede lo script, nel qual caso si ottiene un valore di "."

Per aggirare il problema, utilizzare:

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

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

Ora puoi utilizzare la variabile current_dir in tutto lo script per fare riferimento alla directory degli script. Tuttavia, questo potrebbe ancora avere il problema del collegamento simbolico.


20

La migliore risposta a questa domanda ha avuto risposta qui:
Ottenere la directory di origine di uno script Bash dall'interno

E questo è:

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

One-liner che ti darà il nome completo della directory dello script, indipendentemente da dove viene chiamato.

Per capire come funziona puoi eseguire il seguente script:

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

Facciamolo un oneliner POSIX:

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

Testato su molte shell compatibili con Bourne, comprese quelle BSD.

Per quanto ne so sono l'autore e lo metto di dominio pubblico. Per maggiori informazioni, consultare: https://www.jasan.tk/posts/2017-05-11-posix_shell_dirname_replacement/


1
come scritto, cd: too many argumentsse spazi nel percorso e ritorna $PWD. (una soluzione ovvia, ma mostra solo quanti casi limite ci sono effettivamente)
michael

1
Voterebbe. Tranne il commento di @michael che fallisce con gli spazi nel percorso ... C'è una soluzione per questo?
spettro il

1
@spechter sì, c'è una soluzione per questo. Vedi aggiornamento jasan.tk/posix/2017/05/11/posix_shell_dirname_replacement
Ján Sáreník

@Der_Meister - si prega di essere più specifici. Oppure scrivi (e possibilmente crittografare) un'email a jasan su 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 (dovrebbe invece mostrare il percorso dello script originale)
Der_Meister

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

3
il modo più affidabile non specifico di bash che conosco.
eddygeek,

10

Se si desidera ottenere la directory di script effettiva (indipendentemente dal fatto che si stia invocando lo script utilizzando un collegamento simbolico o direttamente), provare:

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

Funziona sia su Linux che su MacOS. Non ho visto nessuno menzionato qui realpath. Non sono sicuro se ci siano degli svantaggi in questo approccio.

su macOS, devi installare coreutilsper usare realpath. Ad esempio: brew install coreutils.


6

INTRODUZIONE

Questa risposta corregge la risposta molto rotta ma sorprendentemente votata per primo di questa discussione (scritta da TheMarko):

#!/usr/bin/env bash

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

PERCHÉ USARE dirname "$ 0" SU NON È PROPRIO?

dirname $ 0 funzionerà solo se l'utente avvia lo script in un modo molto specifico. Sono stato in grado di trovare diverse situazioni in cui questa risposta fallisce e blocca lo script.

Prima di tutto, capiamo come funziona questa risposta. Sta ottenendo la directory degli script facendo

dirname "$0"

$ 0 rappresenta la prima parte del comando che chiama lo script (in pratica è il comando inserito senza gli argomenti:

/some/path/./script argument1 argument2

$ 0 = "/ qualche / percorso /./ script"

dirname fondamentalmente trova l'ultimo / in una stringa e lo tronca lì. Quindi se lo fai:

  dirname /usr/bin/sha256sum

otterrai: / usr / bin

Questo esempio funziona bene perché / usr / bin / sha256sum è un percorso formattato correttamente ma

  dirname "/some/path/./script"

non funzionerebbe bene e ti darebbe:

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

Supponi di essere nella stessa directory del tuo script e di avviarlo con questo comando

./script   

$ 0 in questa situazione sarà ./script e dirname $ 0 darà:

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

usando:

sh script

Senza inserire il percorso completo, verrà anche fornito un BASEDIR = "."

Utilizzando le relative directory:

 ../some/path/./script

Fornisce un dirname $ 0 di:

 ../some/path/.

Se ti trovi nella directory / some e chiami lo script in questo modo (nota l'assenza di / all'inizio, di nuovo un percorso relativo):

 path/./script.sh

Otterrai questo valore per dirname $ 0:

 path/. 

e ./path/./script (un'altra forma del percorso relativo) fornisce:

 ./path/.

Le uniche due situazioni in cui basedir $ 0 funzionerà è se l'utente usa sh o touch per avviare uno script perché entrambi comporteranno $ 0:

 $0=/some/path/script

che ti darà un percorso che puoi usare con dirname.

LA SOLUZIONE

Dovresti tenere conto e rilevare ognuna delle situazioni sopra menzionate e applicare una correzione se si presenta:

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

Questo one-liner indica dove si trova lo script della shell, non importa se lo hai eseguito o se lo hai fatto . Inoltre, risolve tutti i collegamenti simbolici coinvolti, se questo è il caso:

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

A proposito, suppongo che tu stia usando / bin / bash .


2

Così tante risposte, tutte plausibili, ognuna con obiettivi di pro e contro e leggermente diversi (che probabilmente dovrebbero essere dichiarati per ciascuno). Ecco un'altra soluzione che soddisfa un obiettivo primario di essere chiari e di lavorare su tutti i sistemi, su tutti i bash (nessun presupposto sulle versioni bash, o readlinkopwd opzioni) e ragionevolmente fa quello che ti aspetteresti che accada (ad esempio, risolvere i collegamenti simbolici è un problema interessante, ma di solito non è quello che si desidera), gestire casi limite come spazi nei percorsi, ecc., ignora eventuali errori e utilizza un valore predefinito sano in caso di problemi.

Ogni componente è memorizzato in una variabile separata che è possibile utilizzare singolarmente:

# 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

Questo dovrebbe fare il trucco:

echo `pwd`/`dirname $0`

Potrebbe sembrare brutto a seconda di come è stato invocato e del CWD, ma dovrebbe portarti dove devi andare (o puoi modificare la stringa se ti interessa come appare).


1
StackOverflow fuga problema qui: sicuramente dovrebbe apparire così: `pwd`/`dirname $0`ma potrebbe ancora fallire sui collegamenti simbolici
Andreas Dietrich
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.