come ottenere la directory degli script in POSIX sh?

Ho il seguente codice nel mio script bash. Ora voglio usarlo in POSIX sh. Quindi come convertirlo? Grazie.

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

La controparte di POSIX-shell ( sh ) di $BASH_SOURCE è $0 . vedi in basso per informazioni di base

Avvertenza : la differenza cruciale è che se lo script è stato originato (caricato nella shell corrente con . ) , I frammenti di seguito non funzioneranno correttamente. spiegazione più sotto

Nota che ho cambiato DIR in dir nei frammenti seguenti, perché è meglio non usare nomi di variabili maiuscole in modo da evitare conflitti con variabili d’ambiente e variabili speciali di shell.
Il prefisso CDPATH= prende il posto di > /dev/null nel comando originale: $CDPATH è impostato su una stringa nulla in modo da garantire che il cd non echi mai nulla.

Nel caso più semplice, ciò avverrà (l’ equivalente del comando dell’OP ):

 dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) 

Se si desidera anche risolvere il percorso della directory risultante fino alla destinazione finale nel caso in cui la directory e / oi suoi componenti siano collegamenti simbolici , aggiungere -P al comando pwd :

 dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd -P) 

Avvertenza : NON è la stessa cosa che trovare la vera directory di origine dello script :
Diciamo che il tuo script foo è collegato simbolicamente a /usr/local/bin/foo nel $PATH , ma il suo vero percorso è /foodir/bin/foo .
Quanto sopra riporterà ancora /usr/local/bin , perché la risoluzione dei link simbolici ( -P ) viene applicata alla directory , /usr/local/bin , piuttosto che allo script stesso.

Per trovare la vera directory di origine dello script , dovresti ispezionare il percorso dello script per vedere se si tratta di un link simbolico e, in tal caso, seguire i collegamenti simbolici (catena di) al file di destinazione finale e quindi estrarre il percorso della directory da il percorso canonico del file di destinazione .

Il readlink -f di GNU readlink -f (meglio: readlink -e ) potrebbe farlo per te, ma readlink non è un’utilità POSIX.
Mentre le piattaforms BSD, incluso OSX, hanno readlink un’utilità di readlink , su OSX non supporta la funzionalità di -f . Detto questo, per mostrare quanto sia semplice l’attività diventa se readlink -f è disponibile: dir=$(dirname "$(readlink -f -- "$0")") .

Di fatto, non esiste un’utilità POSIX per la risoluzione dei collegamenti simbolici di file . Ci sono modi per ovviare a questo, ma sono ingombranti e non completamente robusti:

La seguente funzione di shell conforms a POSIX implementa il readlink -e di GNU readlink -e ed è una soluzione ragionevolmente solida che fallisce solo in due casi readlink -e :

  • percorsi con newline incorporate (molto raro)
  • nomi di file contenenti stringhe letterali -> (anche rari)

Con questa funzione, definita rreadlink , definita, la seguente determina il vero percorso di origine della directory dello script :

 dir=$(dirname -- "$(rreadlink "$0")") 

rreadlink() codice sorgenteluogo prima delle chiamate ad esso negli script:

 rreadlink() ( # Execute the function in a *subshell* to localize variables and the effect of `cd`. target=$1 fname= targetDir= CDPATH= # Try to make the execution environment as predictable as possible: # All commands below are invoked via `command`, so we must make sure that `command` # itself is not redefined as an alias or shell function. # (Note that command is too inconsistent across shells, so we don't use it.) # `command` is a *builtin* in bash, dash, ksh, zsh, and some platforms do not even have # an external utility version of it (eg, Ubuntu). # `command` bypasses aliases and shell functions and also finds builtins # in bash, dash, and ksh. In zsh, option POSIX_BUILTINS must be turned on for that # to happen. { \unalias command; \unset -f command; } >/dev/null 2>&1 [ -n "$ZSH_VERSION" ] && options[POSIX_BUILTINS]=on # make zsh find *builtins* with `command` too. while :; do # Resolve potential symlinks until the ultimate target is found. [ -L "$target" ] || [ -e "$target" ] || { command printf '%s\n' "ERROR: '$target' does not exist." >&2; return 1; } command cd "$(command dirname -- "$target")" # Change to target dir; necessary for correct resolution of target path. fname=$(command basename -- "$target") # Extract filename. [ "$fname" = '/' ] && fname='' # !! curiously, `basename /` returns '/' if [ -L "$fname" ]; then # Extract [next] target path, which may be defined # *relative* to the symlink's own directory. # Note: We parse `ls -l` output to find the symlink target # which is the only POSIX-compliant, albeit somewhat fragile, way. target=$(command ls -l "$fname") target=${target#* -> } continue # Resolve [next] symlink target. fi break # Ultimate target reached. done targetDir=$(command pwd -P) # Get canonical dir. path # Output the ultimate target's canonical path. # Note that we manually resolve paths ending in /. and /.. to make sure we have a normalized path. if [ "$fname" = '.' ]; then command printf '%s\n' "${targetDir%/}" elif [ "$fname" = '..' ]; then # Caveat: something like /var/.. will resolve to /private (assuming /[email protected] -> /private/var), ie the '..' is applied # AFTER canonicalization. command printf '%s\n' "$(command dirname -- "${targetDir}")" else command printf '%s\n' "${targetDir%/}/$fname" fi ) 

Per essere robusto e prevedibile, la funzione usa il command per assicurare che solo i builtin di shell o le utilità esterne siano chiamati (ignora gli overload nelle forms di alias e funzioni).
È stato testato nelle versioni recenti delle seguenti shell: bash , dash , ksh , zsh .


Come gestire le invocazioni originarie :

tl; dr :

Usando solo le funzioni POSIX :

  • Non è ansible determinare il percorso dello script in una chiamata originaria (tranne in zsh , che tuttavia non agisce normalmente come sh ).
  • È ansible rilevare se il proprio script è stato originato SOLO se lo script è stato originato direttamente dalla shell (come in un profilo shell / file di inizializzazione, possibilmente attraverso una catena di sourcings), confrontando $0 con il nome dell’eseguibile shell / percorso (tranne in zsh , dove, come notato, $0 è veramente il percorso dello script corrente). Al contrario (tranne che in zsh ), uno script proveniente da un altro script che è stato invocato direttamente , contiene il percorso di tale script in $0 .
  • Per risolvere questi problemi, bash , ksh e zsh dispongono di funzionalità non standard che consentono di determinare il percorso di script effettivo anche in scenari originari e anche di determinare se uno script è stato originato o meno; per esempio, in bash , $BASH_SOURCE contiene sempre il percorso dello script in esecuzione, indipendentemente dal fatto che sia stato originato o meno, e [[ $0 != "$BASH_SOURCE" ]] può essere usato per verificare se lo script è stato originato.

Per mostrare perché questo non può essere fatto , analizziamo il comando dalla risposta di Walter A :

  # NOT recommended - see discussion below. DIR=$( cd -P -- "$(dirname -- "$(command -v -- "$0")")" && pwd -P ) 
  • (Due a parte:
    • Usando -P due volte è ridondante – è sufficiente usarlo con pwd .
    • Il comando manca il silenziamento del potenziale output di stdout del cd , se è necessario impostare $CDPATH .)
  • command -v -- "$0"
    • command -v -- "$0" è progettato per coprire uno scenario aggiuntivo: se lo script viene originato da una shell intertriggers , $0 genere contiene il semplice nome del file eseguibile della shell ( sh ), nel qual caso dirname restituirebbe semplicemente . (perché è ciò che dirname invariabilmente quando viene fornito un argomento senza un componente del percorso ). command -v -- "$0" restituisce il percorso assoluto della shell attraverso una ricerca $PATH ( /bin/sh ). Si noti, tuttavia, che le shell di login su alcune piattaforms (ad esempio OSX) hanno il loro nome di file preceduto da - in $0 ( -sh ), nel qual caso il command -v -- "$0" non funziona come previsto (restituisce una stringa vuota ).
    • Viceversa, il command -v -- "$0" può comportarsi male in due scenari in cui l’eseguibile della shell, sh , viene invocato direttamente , con lo script come argomento:
      • se lo script stesso non è eseguibile: command -v -- "$0" può restituire una stringa vuota , a seconda di quale shell specifica agisce come sh su un dato sistema: bash , ksh e zsh restituiscono una stringa vuota; solo dash echo $0
        Le specifiche POSIX per command non dice esplicitamente se il command -v , se applicato a un percorso del file system , deve restituire solo i file eseguibili, che è ciò che bash , ksh e zsh fanno, ma si può sostenere che è implicito proprio dal command ; curiosamente, dash , che di solito è il cittadino POSIX più conforms, devia dallo standard qui. Al contrario, ksh è il cittadino modello solitario qui, perché è l’unico che segnala solo i file eseguibili e li segnala con un percorso assoluto (anche se non normalizzato), come richiesto dalla specifica.
      • se lo script è eseguibile, ma non nel $PATH , e l’invocazione utilizza il suo semplice nome file (ad esempio, sh myScript ), il command -v -- "$0" restituirà anche la stringa vuota , tranne nel dash .
    • Dato che la directory dello script non può essere determinata quando lo script viene originato , perché $0 non contiene quell’informazione (tranne che in zsh , che di solito non funziona come sh ) – non c’è una buona soluzione a questo problema.
      • Restituire il percorso della directory dell’eseguibile della shell in quella situazione è di uso limitato – dopotutto non è la directory dello script – tranne forse per utilizzare successivamente quel percorso in un test per determinare se lo script è stato originato o meno .
        • Un approccio più affidabile sarebbe semplicemente testare $0 direttamente: [ "$0" = "sh" ] || [ "$0" = "-sh" ] || [ "$0" = "/bin/sh" ] [ "$0" = "sh" ] || [ "$0" = "-sh" ] || [ "$0" = "/bin/sh" ]
      • Tuttavia, anche questo non funziona se lo script viene originato da un altro script (che è stato direttamente richiamato), perché $0 quindi contiene semplicemente il percorso dello script di sourcing .
    • Data l’utilità limitata del command -v -- "$0" negli scenari originari e il fatto che rompa due scenari non acquistati, il mio voto è NON per usarlo , il che ci lascia con:
      • Tutti gli scenari non acquistati sono coperti.
      • Nelle invocazioni di origine , non è ansible determinare il percorso dello script e, nella migliore delle ipotesi, in circostanze limitate, è ansible rilevare se si verifica o meno la fonte di approvvigionamento:
        • Se originato direttamente dalla shell (come da un profilo di shell / file di inizializzazione), $dir finisce per contenere . , se l’eseguibile della shell è stato invocato come un semplice nome file (l’applicazione di dirname a un semplice nome file restituisce sempre . ), o altrimenti il ​​percorso della directory dell’eseguibile della shell. . non può essere distinto in modo affidabile da una chiamata non originaria dalla directory corrente.
        • Se originato da un altro script (anch’esso originariamente non originario), $0 contiene il percorso di quel copione e lo script da cui è stato originato non ha modo di dire se questo è il caso.

Informazioni di base:

POSIX definisce il comportamento di $0 rispetto agli script di shell qui .

In sostanza, $0 dovrebbe riflettere il percorso del file di script come specificato , il che implica:

  • NON fare affidamento su $0 contiene un percorso assoluto .
  • $0 contiene un percorso assoluto solo se:

    • si specifica esplicitamente un percorso assoluto ; per esempio:
      • ~/bin/myScript (assumendo che lo script stesso sia eseguibile)
      • sh ~/bin/myScript
    • invochi uno script eseguibile con un semplice nome file , che richiede che sia eseguibile sia nel $PATH ; dietro le quinte, il sistema trasforma myScript in un percorso assoluto e quindi lo esegue; per esempio:
      • myScript # executes /home/jdoe/bin/myScript, for instance
  • In tutti gli altri casi, $0 rifletterà il percorso dello script come specificato :

    • Quando si invoca esplicitamente sh con uno script, questo può essere un semplice nome file (ad esempio, sh myScript ) o un percorso relativo (ad esempio, sh ./myScript )
    • Quando si richiama direttamente uno script eseguibile, questo può essere un percorso relativo (ad esempio, ./myScript – si noti che un semplice nome di file solo trova gli script nel $PATH ).

In pratica, bash , dash , ksh e zsh mostrano tutti questo comportamento.

Al contrario, POSIX non impone il valore di $0 durante l’ acquisizione di uno script (utilizzando l’apposita utility integrata . (“Punto”)), quindi non si può fare affidamento su di esso e, in pratica, il comportamento differisce tra le shell.

  • Pertanto, non puoi usare ciecamente $0 quando il tuo script viene originato e ti aspetti un comportamento standardizzato.
    • In pratica, bash , dash e ksh lasciano $0 intatti quando si esegue il sourcing degli script, ovvero $0 contiene il valore $0 del chiamante o, più precisamente, il valore $0 del chiamante più recente nella catena di chiamate che non è stato originariamente creato; quindi, $0 può puntare o all’eseguibile della shell o al percorso di un altro script (invocato direttamente) che ha originato quello corrente.
    • Al contrario, zsh , come il solitario dissenter, in realtà segnala il percorso dello script corrente in $0 . Al contrario, $0 non fornirà alcuna indicazione sul fatto che lo script sia stato originato o no.
    • In breve: usando solo le funzionalità POSIX, non si può né sapere in modo affidabile se lo script è in fase di acquisizione, né quale sia il percorso dello script a portata di mano, né quale sia la relazione di $0 con il percorso dello script corrente.
  • Se è necessario gestire questa situazione, è necessario identificare la shell specifica a portata di mano e accedere alle sue caratteristiche specifiche non standard :
    • bash , ksh e zsh offrono tutti i loro metodi per ottenere il percorso dello script in esecuzione, anche quando è stato originato.

Per completezza: il valore di $0 in altri contesti :

  • All’interno di una funzione shell , POSIX impone che $0 rimanga invariato ; quindi, qualunque valore abbia al di fuori della funzione, avrà anche dentro .
    • In pratica bash , dash e ksh si comportano in questo modo.
    • Ancora una volta, zsh è l’unico dissidente e riporta il nome della funzione .
  • In una shell che accetta una stringa di comando tramite l’opzione -c all’avvio, è il primo operando (argomento non opzionale) che imposta $0 ; per esempio:
    • sh -c 'echo \$0: $0 \$1: $1' foo one # -> '$0: foo $1: one'
    • bash , dash , ksh e zsh comportano tutti in questo modo.
  • Altrimenti , in una shell che non esegue un file di script , $0 è il valore del primo argomento passato dal processo padre della shell – in genere, questo è il nome o il percorso della shell (ad es. sh , o /bin/sh ); ciò comprende:
    • una shell intertriggers
      • Avvertenza : alcune piattaforms, in particolare OSX, creano sempre shell di login durante la creazione di shell interattive e anteporre il nome della shell prima di posizionarla in $0 , in modo da segnalare alla shell che si tratta di una shell _login; quindi, per impostazione predefinita, $0 riporta -bash , non bash , in shell interattive su OSX.
    • una shell che legge i comandi da stdin
      • questo vale anche per eseguire il piping di un file di script nella shell tramite stdin (ad es. sh < myScript )
    • bash , dash , ksh e zsh comportano tutti in questo modo.

@City ha risposto

 DIR=$( cd -P -- "$(dirname -- "$(command -v -- "$0")")" && pwd -P ) 

lavori. L’ho usato anch’io.
Ho trovato il comando su https://stackoverflow.com/questions/760110/can-i-get-the-absolute-path-to-the-cu rrent-script-in-kornshell .