Modo portatile per ottenere il percorso assoluto dello script?


29

Qual è un modo portatile per uno script (zsh) per determinare il suo percorso assoluto?

Su Linux uso qualcosa del genere

mypath=$(readlink -f $0)

... ma questo non è portatile. (Ad esempio, readlinksu darwin non riconosce la -fbandiera, né ha alcun equivalente.) (Inoltre, usare readlinkper questo è, certamente, un trucco piuttosto oscuro.)

Qual è un modo più portatile?


Risposte:


28

Con zsh, è solo:

mypath=$0:A

Ora, per altre shell, anche se realpath()e readlink()sono funzioni standard (quest'ultimo è una chiamata di sistema), realpathe readlinknon sono comando standard, anche se alcuni sistemi hanno uno o l'altro o entrambi con vari comportamenti e set di funzionalità.

Come spesso, per la portabilità, potresti voler ricorrere a perl:

abs_path() {
  perl -MCwd -le '
    for (@ARGV) {
      if ($p = Cwd::abs_path $_) {
        print $p;
      } else {
        warn "abs_path: $_: $!\n";
        $ret = 1;
      }
    }
    exit $ret' "$@"
}

Ciò si comporterebbe più come GNU readlink -fdi realpath()(GNU readlink -e) in quanto non si lamenterà se il file non esiste fino a quando il suo dirname lo fa.


Nota: questo non funziona per il tuo .zshrc: vedi invece questo post .
Bryce Guinta,

24

In zsh puoi fare quanto segue:

mypath=${0:a}

Oppure, per ottenere la directory in cui risiede lo script:

mydir=${0:a:h}

Fonte: pagina man zshexpn (1), sezione ESPANSIONE STORIA, modificatori di sottosezione (o semplicemente info -f zsh -n Modifiers).


Dolce! Ho cercato qualcosa del genere per anni e ho letto l'intero gruppo delle manpage di zsh che lo cercavano, ma non mi sarebbe mai venuto in mente di cercare in "Espansione della storia".
Vucar Timnärakrul,

1
L'equivalente di GNU readlink -fsarebbe piuttosto $0:A.
Stéphane Chazelas,

11

Lo uso da diversi anni ormai:

# The absolute, canonical ( no ".." ) path to this script
canonical=$(cd -P -- "$(dirname -- "$0")" && printf '%s\n' "$(pwd -P)/$(basename -- "$0")")

1
mi piace! finora, è abbastanza portatile. funziona su Solaris, OmniOS, Linux, Mac e persino Cygwin su Windows 2008.
Tim Kennedy

4
Dove non è equivalente a GNU readlink -fè quando lo script stesso è un collegamento simbolico.
Stéphane Chazelas,

Su Ubuntu 16.04 con zsh, se eseguo questo direttamente o nella subshell (come suggerito) mentre nella mia home directory ( /home/ville), stampa /home/ville/zsh.
Ville,

8

Questa sintassi dovrebbe essere portabile su qualsiasi Bourne shell stile di interprete (testato con bash, ksh88, ksh93, zsh, mksh, dashe busybox sh):

mypath=$(exec 2>/dev/null;cd -- $(dirname "$0"); unset PWD; /usr/bin/pwd || /bin/pwd || pwd)
echo mypath=$mypath

Questa versione aggiunge la compatibilità alla legacy shell AT&T Bourne (non POSIX):

mypath=`dirname "$0"`
mypath=`exec 2>/dev/null;(cd -- "$mypath") && cd -- "$mypath"|| cd "$mypath"; unset PWD; /usr/bin/pwd || /bin/pwd || pwd`
echo mypath=$mypath

Grazie. tuttavia, penso che disinserire $PWDpotrebbe essere eccessivo - puoi semplicemente impostarlo su una corrente assoluta come cd -P .. Dubito che funzionerebbe a bourneshell, ma dovrebbe funzionare in tutti quelli che hai testato per primo. fa per me comunque.
Mikeserv,

@moose Quale sistema operativo stai eseguendo?
jlliagre,

chi è l'alce? che cosa?
Mikeserv,

@mikeserv alce è un passante che ha postato un commento su qualche problema con zshe dirnamema rapidamente ritirare hir / il suo commento ...
jlliagre

Lo script Bourne Shell non funzionerà con una Bourne Shell poiché la Bourne Shell non utilizza getopt () per cd (1).
schily,

4

Supponendo che tu abbia realmente inteso il percorso assoluto, ovvero un percorso dalla directory principale:

case $0 in
  /*) mypath=$0;;
  *) mypath=$PWD/$0;;
esac

Funziona in qualsiasi shell in stile Bourne, comunque.

Se intendevi un percorso con tutti i collegamenti simbolici risolti, è una questione diversa. readlink -ffunziona su Linux (esclusi alcuni sistemi BusyBox ridotti), FreeBSD, NetBSD, OpenBSD e Cygwin, ma non su OS / X, AIX, HP / UX o Solaris. Se lo hai readlink, puoi chiamarlo in un ciclo:

realpath () {
  [ -e "$1" ] || return
  case $1 in
    /*) :;;
    *) set "$PWD/$1";;
  esac
  while [ -L "$1" ]; do
    set "${1%/*}" "$(readlink "$1")"
    case $2 in
      /*) set "$2";;
      *) if [ -z "$1" ]; then set "/$2"; else set "$(cd "$1" && pwd -P)/$2"; fi;;
    esac
  done
  case $1 in
    */.|*/..) set "$(cd "$1" && pwd -P)";;
    */./*|*/../*) set "$(cd "${1%/*}" && pwd -P)/${1##*/}"
  esac
  realpath=$1
}

Se non lo hai readlink, puoi approssimarlo con ls -n, ma questo funziona solo se lsnon altera alcun carattere non stampabile nel nome del file.

poor_mans_readlink () {
  if [ -L "$1" ]; then
    set -- "$1" "$(LC_ALL=C command ls -n -- "$2"; echo z)"
    set -- "${2%??}"
    set -- "${2#*"$1 -> "}"
  fi
  printf '%s\n' "$1"
}

(Il supplemento zè nel caso in cui la destinazione del collegamento finisca in una nuova riga, la cui sostituzione del comando altrimenti verrebbe a mancare. La realpathfunzione non gestisce quel caso per i nomi di directory, comunque.)


1
Conosci lsun'implementazione che manipola i caratteri non stampabili quando l'output non va su un terminale.
Stéphane Chazelas,

1
@ StéphaneChazelas touch Stéphane; LC_ALL=C busybox ls Stéphane | catSt??phane(è se il nome è in UTF-8, latin1 ti dà un singolo ?). Penso di averlo visto anche su vecchi uffici commerciali.
Gilles 'SO- smetti di essere malvagio' l'

@ StéphaneChazelas Ho corretto diversi bug ma non sono stato ampiamente testato. Fammi sapere se in alcuni casi fallisce ancora (a parte la mancanza di autorizzazioni di esecuzione in alcune directory, non ho intenzione di occuparmi di quel caso limite).
Gilles 'SO- smetti di essere malvagio' il

@Gilles - che cos'è busybox? secondo git busybox ls non ha avuto un cambio di codice dal 2011. My busybox ls- circa 2013 - non fa questa cosa. Questo uno - circa 2012 - fa . Questo potrebbe spiegare perché. Hai creato il tuo busyboxcon il supporto Unicode per includere il supporto wchar? Potresti provare, altrimenti controlla le opzioni di compilazione nel mkinitcpio busyboxpacchetto.
Mikeserv,

Gilles - credo di aver inizialmente giudicato male questa risposta - o almeno una parte di essa. Mentre credo fermamente che i tuoi mantra nomi di file siano assolutamente fallaci , sicuramente il tuo poor_mans_readlink è molto ben fatto. Se mi farai la gentilezza di fare una modifica - qualsiasi modifica lo farà - e di farmi un ping dopo, vorrei invertire il mio voto su questo.
Mikeserv,

1

A condizione che tu abbia i permessi di esecuzione sulla directory corrente - o sulla directory da cui hai eseguito lo script della shell - se vuoi un percorso assoluto in una directory tutto ciò di cui hai bisogno è cd.

Passaggio 10 delle cdspecifiche

Se l' -Popzione è attiva, la $PWDvariabile di ambiente deve essere impostata sulla stringa che verrebbe prodotta da pwd -P. Se l'autorizzazione non è sufficiente sulla nuova directory, o su qualsiasi genitore di quella directory, per determinare la directory di lavoro corrente, il valore della $PWDvariabile di ambiente non è specificato.

E via pwd -P

Il percorso scritto nello standard output non deve contenere alcun componente che faccia riferimento a file di tipo link simbolico. Se esistono più percorsi che l' pwdutilità potrebbe scrivere sullo standard output, uno che inizia con un carattere singolo / barra e uno o più che iniziano con due caratteri / barra, allora deve scrivere il percorso che inizia con un carattere singolo / barra. Il nome del percorso non deve contenere caratteri non necessari / barra dopo il primo o due caratteri / barra.

È perché cd -Pdeve impostare la directory di lavoro corrente su ciò che pwd -Pdovrebbe altrimenti stampare e che cd -deve stampare $OLDPWDquanto segue:

mkdir ./dir
ln -s ./dir ./ln
cd ./ln ; cd . ; cd -

PRODUZIONE

/home/mikeserv/test/ln

aspettalo...

cd -P . ; cd . ; cd -

PRODUZIONE

/home/mikeserv/test/dir

E quando stampo con cd -sto stampando $OLDPWD. cdimposta $PWDnon appena cd -P . $PWDora sono un percorso assoluto per /- quindi non ho bisogno di altre variabili. E in realtà, devo neppure bisogno della finale .ma c'è un comportamento specificata di reset $PWDa $HOMEin una shell interattiva quando cdè disadorna. Quindi è solo una buona abitudine da sviluppare.

Quindi semplicemente fare quanto sopra sul percorso in ${0%/*}dovrebbe essere più che sufficiente per verificare $0il percorso, ma nel caso che $0sia esso stesso un soft-link, probabilmente non è possibile cambiare directory in esso, sfortunatamente.

Ecco una funzione che gestirà ciò:

zpath() { cd -P . || return
    _out() { printf "%s$_zdlm\n" "$PWD/${1##*/}"; }
    _cd()  { cd -P "$1" ; } >/dev/null 2>&1
    while [ $# -gt 0 ] && _cd .
    do  if     _cd "$1" 
        then   _out
        elif ! [ -L "$1" ] && [ -e "$1" ] 
        then   _cd "${1%/*}"; _out "$1"
        elif   [ -L "$1" ]
        then   ( while set -- "${1%?/}"; _cd "${1%/*}"; [ -L "${1##*/}" ]
                 do    set " $1" "$(_cd -; ls -nd -- "$1"; echo /)"
                       set -- "${2#*"$1" -> }"
                 done; _out "$1" 
    );  else   ( PS4=ERR:\ NO_SUCH_PATH; set -x; : "$1" )
    fi; _cd -; shift; done
    unset -f _out _cd; unset -v _zdlm                                    
}

Si sforza di fare tutto ciò che potrebbe nella shell corrente - senza invocare una subshell - anche se ci sono subshells invocate per errori e collegamenti software che non puntano alle directory. Dipende da una shell compatibile con POSIX e compatibile lscon POSIX e da uno _function()spazio dei nomi pulito . Funzionerà ancora bene senza quest'ultimo, anche se potrebbe sovrascrivere quindi unsetalcune funzioni shell correnti in quel caso. In generale, tutte queste dipendenze dovrebbero essere disponibili in modo abbastanza affidabile su una macchina Unix.

Chiamata con o senza argomenti, la prima cosa che fa è reimpostare il $PWDsuo valore canonico - risolve tutti i collegamenti in essa contenuti ai loro obiettivi, se necessario. Chiamato senza argomenti e questo è tutto; ma chiamato con loro e risolverà e canonicalizzerà il percorso per ognuno oppure stampa un messaggio sul stderrperché no.

Poiché funziona principalmente nella shell corrente, dovrebbe essere in grado di gestire un elenco di argomenti di qualsiasi lunghezza. Cerca anche la $_zdlmvariabile (che è anche unsetquando è passata) e stampa il suo valore con escape in C immediatamente a destra di ciascuno dei suoi argomenti, ognuno dei quali è sempre seguito anche da un singolo \ncarattere di ewline.

Fa molte modifiche alla directory, ma, oltre a impostarlo sul suo valore canonico, non influisce $PWD, sebbene $OLDPWDnon possa in alcun modo essere contato quando è passato.

Tenta di abbandonare ciascuno dei suoi argomenti il ​​prima possibile. Per prima cosa cerca di cdentrare $1. Se ci riesce, stampa il percorso canonico dell'argomento stdout. In caso contrario, verifica che $1esista e non sia un collegamento software. Se vero, stampa.

In questo modo gestisce qualsiasi argomento relativo al tipo di file che la shell ha i permessi da affrontare a meno che non $1sia un collegamento simbolico che non punta a una directory. In tal caso, chiama whileloop in una subshell.

Chiama lsper leggere il link. La directory corrente deve essere cambiata prima al suo valore iniziale per gestire in modo affidabile qualsiasi percorso di riferimento e quindi, nella sottostruttura di sostituzione comandi la funzione fa:

cd -...ls...echo /

Elimina dalla sinistra lsdell'output il minimo necessario per contenere completamente il nome e la stringa del collegamento ->. Mentre all'inizio ho cercato di evitare di farlo shifte $IFSrisulta che questo è il metodo più affidabile il più vicino possibile. Questa è la stessa cosa che fa il povero_mans_readlink di Gilles - ed è ben fatto.

Ripeterà questo processo in un ciclo fino a quando il nome file restituito lsnon è sicuramente un collegamento software. A quel punto canonicalizza quel percorso come prima con cdpoi le stampe.

Esempio di utilizzo:

zpath \
    /tmp/script \   #symlink to $HOME/test/dir/script.sh
    ln \            #symlink to ./dir/
    ln/nl \         #symlink to ../..
    /dev/fd/0 \     #currently a here-document like : dash <<\HD
    /dev/fd/1 \     #(zlink) | dash
    file \          #regular file                                             
    doesntexist \   #doesnt exist
    /dev/disk/by-path/pci-0000:00:16.2-usb-0:3:1.0-scsi-0:0:0:0 \
    /dev/./././././././null \
    . ..      

PRODUZIONE

/home/mikeserv/test/dir/script.sh
/home/mikeserv/test/dir/
/home/mikeserv/test/
/tmp/zshtpKRVx (deleted)
/proc/17420/fd/pipe:[1782312]
/home/mikeserv/test/file
ERR: NO_SUCH_PATH: doesntexist
/dev/sdd
/dev/null
/home/mikeserv/test/
/home/mikeserv/

O forse ...

ls
dir/  file  file?  folder/  link@  ln@  script*  script3@  script4@

zdlm=\\0 zpath * | cat -A

PRODUZIONE

/home/mikeserv/test/dir/^@$               
/home/mikeserv/test/file^@$
/home/mikeserv/test/file$
^@$
/home/mikeserv/test/folder/^@$
/home/mikeserv/test/file$               #'link' -> 'file\n'
^@$
/home/mikeserv/test/dir/^@$             #'ln' -> './dir'
/home/mikeserv/test/script^@$
/home/mikeserv/test/dir/script.sh^@$    #'script3' -> './dir/script.sh'
/home/mikeserv/test/dir/script.sh^@$    #'script4' -> '/tmp/script' -> ...

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.