determinazione del percorso dello script shell di provenienza


80

C'è un modo per uno script di shell di provenienza di scoprire il percorso verso se stesso? Mi occupo principalmente di bash, anche se ho dei colleghi che usano tcsh.

Immagino che potrei non avere molta fortuna qui, poiché l'approvvigionamento causa l'esecuzione dei comandi nella shell corrente, quindi $0è ancora l'invocazione della shell corrente, non lo script di provenienza. Il mio pensiero migliore attualmente è quello di fare source $script $script, in modo che il primo parametro posizionale contenga le informazioni necessarie. Qualcuno ha un modo migliore?

Per essere chiari, sto acquistando lo script, non eseguendolo:

source foo.bash

domanda relativa che ha 4200+ upvotes: stackoverflow.com/q/59895/52074
Trevor Boyd Smith

Risposte:


65

In tcsh, $_all'inizio dello script conterrà la posizione se il file è stato fornito e lo $0contiene se è stato eseguito.

#!/bin/tcsh
set sourced=($_)
if ("$sourced" != "") then
    echo "sourced $sourced[2]"
endif
if ("$0" != "tcsh") then
    echo "run $0"
endif

In Bash:

#!/bin/bash
[[ $0 != $BASH_SOURCE ]] && echo "Script is being sourced" || echo "Script is being run"

Ho appena avuto occasione di usarlo in tcsh e ho notato che non funziona senza lo shebang. Sembra un po 'strano che il comportamento cambi se lo stai solo approvando, non eseguendolo ...
Cascabel,

Inoltre, la versione di tcsh non sembra funzionare se lo script proviene in modo non interattivo (ad esempio da un cshrc). Non riesco a trovare un modo per ottenere le informazioni in quel caso. qualche idea?
Cascabel,

Sourcing funziona per me senza lo shebang. > tcsh --version\n tcsh 6.14.00 (Astron) 2005-03-25 (i486-intel-linux) options wide,nls,dl,al,kan,rh,nd,color,filec. Per quanto riguarda l'approvvigionamento in modo non interattivo, il file di origine è incluso nel file principale come se ne facesse effettivamente parte (indistinguibilmente) come menzionato nella domanda originale. Penso che la soluzione alternativa ai parametri posizionali sia probabilmente l'approccio migliore. Tuttavia, la solita domanda è "perché vuoi farlo" e la solita risposta alla risposta è "non fare questo - fare questo posto" dove "questo" è spesso al negozio ...
Dennis Williamson

2
@clacke: lo trovo in tutte le versioni di Bash che ho provato da 2.05b a 4.2.37, incluso 4.1.9, .e che ha sourcefunzionato in modo identico a questo proposito. Si noti che è $_necessario accedere alla prima istruzione nel file, altrimenti conterrà l'ultimo argomento del comando precedente. Mi piace includere lo shebang come riferimento, quindi so per quale shell dovrebbe essere e per l'editor, quindi usa l'evidenziazione della sintassi.
Dennis Williamson,

1
Haha. Ovviamente stavo testando prima facendo source, poi facendo .. Mi scuso per essere incompetente. Sono davvero identici. Comunque $BASH_SOURCEfunziona.
clacke

30

Penso che potresti usare la $BASH_SOURCEvariabile. Restituisce il percorso che è stato eseguito:

pbm@tauri ~ $ /home/pbm/a.sh 
/home/pbm/a.sh
pbm@tauri ~ $ ./a.sh
./a.sh
pbm@tauri ~ $ source /home/pbm/a.sh 
/home/pbm/a.sh
pbm@tauri ~ $ source ./a.sh
./a.sh

Quindi nel prossimo passo dovremmo verificare se il percorso è relativo o no. Se non è relativo, va tutto bene. In tal caso, è possibile verificare il percorso con pwd, concatenare con /e $BASH_SOURCE.


2
E nota che sourcecerca $PATHse il nome dato non contiene a /. L'ordine di ricerca dipende dalle opzioni della shell, fare riferimento al manuale per i dettagli.
Gilles,

1
Quindi, qualcosa del genere mydir="$(cd "$(dirname "$BASH_SOURCE")"; pwd)"avrebbe funzionato?
Kevin Cantu,

Grazie, una risposta rapida e utile. Dennis vince il segno di spunta verde per aver dato anche una risposta. @Gilles: Giusto, l'ho trovato nella documentazione. Fortunatamente per il mio caso d'uso, quasi sicuramente non devo preoccuparmene.
Cascabel,

18

Per completezza e per il bene dei ricercatori, ecco cosa fanno ... È un wiki della comunità, quindi sentiti libero di aggiungere altri equivalenti della shell (ovviamente $ BASH_SOURCE sarà diverso).

test.sh:

#! /bin/sh
called=$_
echo $called
echo $_
echo $0
echo $BASH_SOURCE

test2.sh:

#! /bin/sh
source ./test.sh

bash:

$./test2.sh
./test2.sh
./test2.sh
./test2.sh
./test.sh
$ sh ./test2.sh
/bin/sh
/bin/sh
./test2.sh
./test.sh

trattino

$./test2.sh
./test2.sh
./test2.sh
./test2.sh

$/bin/sh ./test2.sh
/bin/sh
/bin/sh
./test2.sh

$

zsh

$ ./test2.sh
./test.sh
./test.sh
./test.sh

$ zsh test.sh

echo
test.sh

$

1
Non capisco: perché called=$_; echo $called; echo $_? Questa stampa non verrà stampata $_due volte?
Ciro Santilli 14 改造 中心 法轮功 六四 事件

5
@CiroSantilli: non sempre, leggi il manuale di Bash sul $_parametro speciale: "All'avvio della shell, imposta il percorso assoluto usato per invocare la shell o lo script della shell in esecuzione come passato nell'ambiente o nell'elenco degli argomenti. Successivamente, si espande fino all'ultimo argomento del comando precedente, dopo l'espansione. Impostare anche sul percorso completo utilizzato per richiamare ciascun comando eseguito e inserito nell'ambiente esportato a quel comando. Quando si controlla la posta, questo parametro contiene il nome del file di posta. "
Adam Rosenfield,

Il problema è che il file di origine ha un'intestazione #! /bin/shche lo rende inutile per l'origine. Ciò avvia una nuova istanza di /bin/sh, imposta le variabili, quindi esce da quell'istanza, lasciando invariata l'istanza chiamante.
JamesThomasMoon1979

2
@ JamesThomasMoon1979: di cosa stai parlando? Tutto ciò che inizia con #uno script di shell è un commento.  #!(shebang) ha il suo significato speciale solo come prima riga di una sceneggiatura eseguita.   Come prima riga di un file che proviene, è solo un commento.
Scott,

17

Questa soluzione si applica solo a bash e non a tcsh. Si noti che la risposta fornita comunemente ${BASH_SOURCE[0]}non funzionerà se si tenta di trovare il percorso all'interno di una funzione.

Ho trovato che questa linea funziona sempre, indipendentemente dal fatto che il file provenga o venga eseguito come script.

echo ${BASH_SOURCE[${#BASH_SOURCE[@]} - 1]}

Se si desidera seguire i collegamenti simbolici, utilizzare readlinkil percorso sopra riportato, in modo ricorsivo o non ricorsivo.

Ecco uno script per provarlo e confrontarlo con altre soluzioni proposte. Invocalo come source test1/test2/test_script.sho bash test1/test2/test_script.sh.

#
# Location: test1/test2/test_script.sh
#
echo $0
echo $_
echo ${BASH_SOURCE}
echo ${BASH_SOURCE[${#BASH_SOURCE[@]} - 1]}

cur_file="${BASH_SOURCE[${#BASH_SOURCE[@]} - 1]}"
cur_dir="$(dirname "${cur_file}")"
source "${cur_dir}/func_def.sh"

function test_within_func_inside {
    echo ${BASH_SOURCE}
    echo ${BASH_SOURCE[${#BASH_SOURCE[@]} - 1]}
}

echo "Testing within function inside"
test_within_func_inside

echo "Testing within function outside"
test_within_func_outside

#
# Location: test1/test2/func_def.sh
#
function test_within_func_outside {
    echo ${BASH_SOURCE}
    echo ${BASH_SOURCE[${#BASH_SOURCE[@]} - 1]}
}

Il motivo per cui il one-liner funziona è spiegato dall'uso della BASH_SOURCEvariabile d'ambiente e del suo associato FUNCNAME.

BASH_SOURCE

Una variabile di matrice i cui membri sono i nomi dei file di origine in cui sono definiti i nomi delle funzioni shell corrispondenti nella variabile di matrice FUNCNAME. La funzione shell $ {FUNCNAME [$ i]} è definita nel file $ {BASH_SOURCE [$ i]} e chiamata da $ {BASH_SOURCE [$ i + 1]}.

FUNCNAME

Una variabile di matrice contenente i nomi di tutte le funzioni della shell attualmente nello stack delle chiamate di esecuzione. L'elemento con indice 0 è il nome di qualsiasi funzione shell attualmente in esecuzione. L'elemento più in basso (quello con l'indice più alto) è "principale". Questa variabile esiste solo quando è in esecuzione una funzione shell. Le assegnazioni a FUNCNAME non hanno alcun effetto e restituiscono uno stato di errore. Se FUNCNAME non è impostato, perde le sue proprietà speciali, anche se viene successivamente ripristinato.

Questa variabile può essere utilizzata con BASH_LINENO e BASH_SOURCE. Ogni elemento di FUNCNAME ha elementi corrispondenti in BASH_LINENO e BASH_SOURCE per descrivere lo stack di chiamate. Ad esempio, $ {FUNCNAME [$ i]} è stato chiamato dal file $ {BASH_SOURCE [$ i + 1]} al numero di riga $ {BASH_LINENO [$ i]}. Il chiamante incorporato visualizza lo stack di chiamate corrente utilizzando queste informazioni.

[Fonte: manuale di Bash]


Questa soluzione ha funzionato per me in bash mentre la risposta selezionata ha funzionato solo in modo intermittente. Non ho mai capito perché a volte funzionasse e non altri (forse non stavo prestando abbastanza attenzione alla shell di approvvigionamento).
Jim2B,

13

Questo ha funzionato per me in bash, dash, ksh e zsh:

if test -n "$BASH" ; then script=$BASH_SOURCE
elif test -n "$TMOUT"; then script=${.sh.file}
elif test -n "$ZSH_NAME" ; then script=${(%):-%x}
elif test ${0##*/} = dash; then x=$(lsof -p $$ -Fn0 | tail -1); script=${x#n}
else script=$0
fi

echo $script

Output per queste shell:

BASH source: ./myscript
ZSH source: ./myscript
KSH source: /home/pbrannan/git/theme/src/theme/web/myscript
DASH source: /home/pbrannan/git/theme/src/theme/web/myscript
BASH: ./myscript
ZSH: ./myscript
KSH: /home/pbrannan/git/theme/src/theme/web/myscript
DASH: ./myscript

Ho provato a farlo funzionare per csh / tcsh, ma è troppo difficile; Mi attengo a POSIX.


1

Sono stato un po 'confuso dalla risposta wiki della community (di Shawn J. Goff), quindi ho scritto una sceneggiatura per risolvere le cose. A proposito $_, ho trovato questo: utilizzo di _come variabile d'ambiente passata a un comando . È una variabile di ambiente, quindi è facile testarne il valore in modo errato.

Di seguito è riportato lo script, quindi viene generato. Sono anche in questo senso .

test-shell-default-variables.sh

#!/bin/bash

# test-shell-default-variables.sh

# Usage examples (you might want to `sudo apt install zsh ksh`):
#
#  ./test-shell-default-variables.sh dash bash
#  ./test-shell-default-variables.sh dash bash zsh ksh
#  ./test-shell-default-variables.sh dash bash zsh ksh | less -R

# `-R` in `less -R` to have less pass escape sequences directly to the terminal
# so we have colors.


# The "invoking with name `sh`" tests are commented because for every shell I
# tested (dash, bash, zsh and ksh), the output was the same as that of dash.

# The `test_expression` function also work with expansion changes. You can try
# lines like `test_expression '{BASH_SOURCE:-$0}'`.

echolor() {
    echo -e "\e[1;36m$@\e[0m"
}

tell_file() {
    echo File \`"$1"\` is:
    echo \`\`\`
    cat "$1"
    echo \`\`\`
    echo
}

SHELL_ARRAY=("$@")

test_command() {
    for shell in "${SHELL_ARRAY[@]}"
    do
        prepare "$shell"
        cmd="$(eval echo $1)"
        # echo "cmd: $cmd"
        printf '%-4s: ' "$shell"
        { env -i $cmd 2>&1 1>&3 | sed 's/^/[err]/'; } 3>&1
        teardown
    done
    echo
}

prepare () {
    shell="$1"
    PATH="$PWD/$shell/sh:$PATH"
}

teardown() {
    PATH="${PATH#*:}"
}


###
### prepare
###
for shell in "${SHELL_ARRAY[@]}"
do
    mkdir "$shell"
    ln -sT "/bin/$shell" "$shell/sh"
done

echo > printer.sh
echo '. ./printer.sh' > sourcer.sh
rm linked.sh &>/dev/null; ln -sT "printer.sh" "linked.sh"

tell_file sourcer.sh

###
### run
###
test_expression() {
    local expr="$1"

    # prepare
    echo "echo $expr" > printer.sh
    tell_file printer.sh

    # run
    cmd='$shell ./printer.sh'
    echolor "\`$cmd\` (simple invocation) ($expr):"
    test_command "$cmd"

    # cmd='sh ./printer.sh'
    # echolor "\`$cmd\` (when executable name is \`sh\`) ($expr):"
    # test_command "$cmd"

    cmd='$shell ./sourcer.sh'
    echolor "\`$cmd\` (via sourcing) ($expr):"
    test_command "$cmd"

    # cmd='sh ./sourcer.sh'
    # echolor "\`$cmd\` (via sourcing, when name is \`sh\`) ($expr):"
    # test_command "$cmd"

    cmd='$shell ./linked.sh'
    echolor "\`$cmd\` (via symlink) ($expr):"
    test_command "$cmd"

    # cmd='sh ./linked.sh'
    # echolor "\`$cmd\` (via symlink, when name is \`sh\`) ($expr):"
    # test_command "$cmd"

    echolor "------------------------------------------"
    echo
}

test_expression '$BASH_SOURCE'
test_expression '$0'
test_expression '$(/bin/true x y; true a b c; echo $_)' # Rq: true is a builtin
test_expression '$_'

###
### teardown
###
for shell in "${SHELL_ARRAY[@]}"
do
    rm "$shell/sh"
    rm -d "$shell"
done

rm sourcer.sh
rm linked.sh
rm printer.sh

Uscita di ./test-shell-default-variables.sh {da,ba,z,k}sh

File `sourcer.sh` is:
```
. ./printer.sh
```

File `printer.sh` is:
```
echo $BASH_SOURCE
```

`$shell ./printer.sh` (simple invocation) ($BASH_SOURCE):
dash: 
bash: ./printer.sh
zsh : 
ksh : 

`$shell ./sourcer.sh` (via sourcing) ($BASH_SOURCE):
dash: 
bash: ./printer.sh
zsh : 
ksh : 

`$shell ./linked.sh` (via symlink) ($BASH_SOURCE):
dash: 
bash: ./linked.sh
zsh : 
ksh : 

------------------------------------------

File `printer.sh` is:
```
echo $0
```

`$shell ./printer.sh` (simple invocation) ($0):
dash: ./printer.sh
bash: ./printer.sh
zsh : ./printer.sh
ksh : ./printer.sh

`$shell ./sourcer.sh` (via sourcing) ($0):
dash: ./sourcer.sh
bash: ./sourcer.sh
zsh : ./printer.sh
ksh : ./sourcer.sh

`$shell ./linked.sh` (via symlink) ($0):
dash: ./linked.sh
bash: ./linked.sh
zsh : ./linked.sh
ksh : ./linked.sh

------------------------------------------

File `printer.sh` is:
```
echo $(/bin/true x y; true a b c; echo $_)
```

`$shell ./printer.sh` (simple invocation) ($(/bin/true x y; true a b c; echo $_)):
dash: 
bash: c
zsh : c
ksh : 

`$shell ./sourcer.sh` (via sourcing) ($(/bin/true x y; true a b c; echo $_)):
dash: 
bash: c
zsh : c
ksh : 

`$shell ./linked.sh` (via symlink) ($(/bin/true x y; true a b c; echo $_)):
dash: 
bash: c
zsh : c
ksh : 

------------------------------------------

File `printer.sh` is:
```
echo $_
```

`$shell ./printer.sh` (simple invocation) ($_):
dash: 
bash: bash
zsh : 
ksh : 

`$shell ./sourcer.sh` (via sourcing) ($_):
dash: 
bash: bash
zsh : ./printer.sh
ksh : 

`$shell ./linked.sh` (via symlink) ($_):
dash: 
bash: bash
zsh : 
ksh : 

------------------------------------------

Che cosa abbiamo imparato?

$BASH_SOURCE

  • $BASH_SOURCE funziona in bash e solo in bash.
  • L'unica differenza $0è quando il file corrente è stato originato da un altro file. In tal caso, $BASH_PROFILEcontiene il nome del file di origine, anziché quello del file di origine.

$0

  • In zsh, $0ha lo stesso valore di $BASH_SOURCEin bash.

$_

  • $_ non viene toccato da trattino e ksh.
  • In bash e zsh, $_decade fino all'ultimo argomento dell'ultima chiamata.
  • bash si inizializza $_su "bash".
  • zsh lascia $_intatta. (durante l'approvvigionamento, è solo il risultato della regola "ultimo argomento").

link simbolici

  • Quando uno script viene chiamato tramite un collegamento simbolico, nessuna variabile contiene alcun riferimento alla destinazione del collegamento, solo il suo nome.

ksh

  • Per quanto riguarda questi test, ksh si comporta come un trattino.

sh

  • Quando bash o zsh viene chiamato tramite un link simbolico chiamato sh, per quanto riguarda quei test, si comporta come un trattino.

0

Per la shell bash, ho trovato la risposta di @Dennis Williamson molto utile, ma non ha funzionato nel caso di sudo. Questo fa:

if ( [[ $_ != $0 ]] && [[ $_ != $SHELL ]] ); then
    echo "I'm being sourced!"
    exit 1
fi

0

Per rendere il tuo script compatibile con bash e zsh invece di usare le istruzioni if ​​puoi semplicemente scrivere ${BASH_SOURCE[0]:-${(%):-%x}}. Il valore risultante verrà preso da BASH_SOURCE[0]quando è definito e ${(%):-%x}}quando BASH_SOURCE [0] non è definito.


0

tl; dr script=$(readlink -e -- "${BASH_SOURCE}") (ovviamenteper bash )


$BASH_SOURCE casi test

file dato /tmp/source1.sh

echo '$BASH_SOURCE '"(${BASH_SOURCE})"
echo 'readlink -e $BASH_SOURCE'\
     "($(readlink -e -- "${BASH_SOURCE}"))"

source il file in diverse maniere

source a partire dal /tmp

$> cd /tmp

$> source source1.sh
$BASH_SOURCE (source1.sh)
readlink -e $BASH_SOURCE (/tmp/source1.sh)

$> source ./source1.sh
$BASH_SOURCE (./source1.sh)
readlink -e $BASH_SOURCE (/tmp/source1.sh)

$> source /tmp/source1.sh
$BASH_SOURCE (/tmp/source1.sh)
readlink -e $BASH_SOURCE (/tmp/source1.sh)

source a partire dal /

cd /
$> source /tmp/source1.sh
$0 (bash)
$BASH_SOURCE (/tmp/source1.sh)
readlink -e $BASH_SOURCE (/tmp/source1.sh)

sourceda diversi percorsi relativi /tmp/ae/var

$> cd /tmp/a

$> source ../source1.sh
$BASH_SOURCE (../source1.sh)
readlink -e $BASH_SOURCE (/tmp/source1.sh)

$> cd /var

$> source ../tmp/source1.sh
$BASH_SOURCE (../tmp/source1.sh)
readlink -e $BASH_SOURCE (/tmp/source1.sh)

per quanto riguarda $0

in tutti i casi, se lo script aveva il comando aggiunto

echo '$0 '"(${0})"

quindi sourcela sceneggiatura viene sempre stampata

$0 (bash)

tuttavia , se lo script è stato eseguito , ad es

$> bash /tmp/source1.sh

quindi $0sarebbe valore stringa /tmp/source1.sh.

$0 (/tmp/source1.sh)
$BASH_SOURCE (/tmp/source1.sh)
readlink -e $BASH_SOURCE (/tmp/source1.sh)

0

questa risposta descrive come lsofe un po 'di magia grep è l'unica cosa che sembra avere la possibilità di lavorare per file di origine nidificati sotto tcsh:

/usr/sbin/lsof +p $$ | grep -oE /.\*source_me.tcsh

-2
wdir="$PWD"; [ "$PWD" = "/" ] && wdir=""
case "$0" in
  /*) scriptdir="${0%/*}";;
  *) scriptdir="$wdir/${0#./}"; scriptdir="${scriptdir%/*}";;
esac
echo "$scriptdir"

Forse questo non funzionerà con collegamenti simbolici o file di origine ma funzionerà con file normali. Preso come riferimento per. @kenorb Nessun dirname, readlink, BASH_SOURCE.


1
È stato spiegato nella domanda che $0ti fornisce informazioni sullo script attualmente in esecuzione , non di provenienza.
Scott

-3

In realtà, "dirname $ 0" ti porterà al percorso dello script, ma devi interpretarlo un po ':

$ cat bash0
#!/bin/bash
echo \$0=$0
dirname $0
$ bash0    # "." appears in PATH right now.
$0=./bash0
.
$ ./bash0
$0=./bash0
.
$ $PWD/bash0
$0=/home/00/bediger/src/ksh/bash0
/home/00/bediger/src/ksh
$ $PWD/../ksh/bash0
$0=/home/00/bediger/src/ksh/../ksh/bash0
/home/00/bediger/src/ksh/../ksh
$ ../ksh/bash0
$0=../ksh/bash0
../ksh

Devi prepararti a gestire "." come nome della directory in alcune circostanze comuni. Sperimenterei un po ', poiché ricordo il dirname incorporato in ksh che fa le cose in modo leggermente diverso quando "." appare in PERCORSO.


4
Questo è uno script di provenienza, non uno script eseguito. $0contiene semplicemente "bash" per una shell interattiva, e questo è tutto ciò che lo script di provenienza vede.
Cascabel,
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.