it-swarm.it

determinazione del percorso dello script Shell di provenienza

Esiste un modo per uno script 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 è fare source $script $script, in modo che il primo parametro posizionale contenga le informazioni necessarie. Qualcuno ha un modo migliore?

Per essere chiari, sono sourcing lo script, non eseguirlo:

source foo.bash
86
Cascabel

In tcsh, $_ all'inizio dello script conterrà la posizione se il file è stato originato e $0 lo contiene 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"

Penso che potresti usare $BASH_SOURCE variabile. Restituisce il percorso che è stato eseguito:

[email protected] ~ $ /home/pbm/a.sh 
/home/pbm/a.sh
[email protected] ~ $ ./a.sh
./a.sh
[email protected] ~ $ source /home/pbm/a.sh 
/home/pbm/a.sh
[email protected] ~ $ 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, possiamo controllare il percorso con pwd, concatenare con / e $BASH_SOURCE.

32
pbm

Questa soluzione si applica solo a bash e non a tcsh. Nota che la risposta comunemente fornita ${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 vuoi seguire i symlink usa readlink sul percorso che trovi sopra, in modo ricorsivo o non ricorsivo.

Ecco uno script per provarlo e confrontarlo con altre soluzioni proposte. Invocalo come source test1/test2/test_script.sh o 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 di BASH_SOURCE variabile d'ambiente e 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 Shell attualmente nello stack di 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]

21
gkb0986

Per completezza e per il bene dei ricercatori, ecco cosa fanno ... È un wiki della comunità, quindi sentiti libero di aggiungere altri equivalenti di 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

$
18
Shawn J. Goff

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.

16
Paul Brannan

Sono stato un po 'confuso dalla risposta della community wiki (di Shawn J. Goff), quindi ho scritto una sceneggiatura per risolvere le cose. Di $_, Ho trovato questo: tilizzo 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 this Gist .

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;[email protected]\e[0m"
}

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

Shell_ARRAY=("[email protected]")

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

Output 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 con $0 è quando il file corrente è stato fornito da un altro file. In quel caso, $BASH_PROFILE contiene il nome del file di origine, piuttosto che quello del file di origine.

$0

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

$_

  • $_ non viene toccato da trattino e ksh.
  • In bash e zsh, $_ decade all'ultimo argomento dell'ultima chiamata.
  • bash inizializza $_ colpire".
  • foglie zsh $_ non trattato. (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.
2
Mathieu CAROFF

questa risposta descrive come lsof e un po 'di grep magic è 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
0
Patrick Maupin

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


$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 modi diversi

source da /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 da /

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

source da diversi percorsi relativi /tmp/a e /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 source lo script viene sempre stampato

$0 (bash)

comunque, se lo script è stato eseguito , ad es.

$> bash /tmp/source1.sh

poi $0 sarebbe un valore stringa /tmp/source1.sh.

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

Per la bash Shell, 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
Matt

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
dols3m

La parte più difficile è trovare il file attualmente di provenienza è per dash Shell utilizzato come sostituzione sh in Ubuntu. Il seguente frammento di codice può essere utilizzato nello script di provenienza per determinarne il percorso assoluto. Testato in bash, zsh e dash invocato sia come dash che sh.

NB: dipende dal moderno realpath (1) utility from GNU coreutils

NB: anche le opzioni lsof (1) dovrebbero essere verificate perché consigli simili sia da questa che da altre pagine non hanno funzionato per me su Ubuntu 18 e 19, quindi ho dovuto reinventarlo.

getShellName() {
    [ -n "$BASH" ] && echo ${BASH##/*/} && return
    [ -n "$ZSH_NAME" ] && echo $ZSH_NAME && return
    echo ${0##/*/}
}

getCurrentScript() {
    local result
    case "$(getShellName)" in
        bash )  result=${BASH_SOURCE[0]}
                ;;
        zsh )   emulate -L zsh
                result=${funcfiletrace[1]%:*}
                ;;
        dash | sh )
                result=$(
                    lsof -p $$ -Fn  \
                    | tail --lines=1  \
                    | xargs --max-args=2  \
                    | cut --delimiter=' ' --fields=2
                )
                result=${result#n}
                ;;
        * )     result=$0
                ;;
    esac
    echo $(realpath $result)
}
0
maoizm