web-dev-qa-db-fra.com

Manière portable pour obtenir le chemin absolu de script?

Qu'est-ce qu'une façon portable pour un script (ZSH) de déterminer son chemin absolu?

Sur Linux j'utilise quelque chose comme

mypath=$(readlink -f $0)

... Mais ce n'est pas portable. (E.G., readlink sur Darwin ne reconnaît pas le -f Drapeau, ni équivalent.) (En outre, en utilisant readlink pour cela est, certes, un joli hack.)

Qu'est-ce qui est une manière plus portable?

31
kjo

Avec zsh, c'est juste:

mypath=$0:A

Maintenant, pour d'autres coquilles, bien que realpath() et readlink() sont des fonctions standard (ce dernier étant un appel système), realpath et readlink ne sont pas la commande standard , bien que certains systèmes aient l'un ou l'autre ou à la fois avec divers comportements et ensemble de fonctions.

Aussi souvent, pour la portabilité, vous voudrez peut-être recourir à Perl:

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

Cela se comporterait plus comme celui de GNU readlink -f Que realpath() (GNU readlink -e) En ce sens qu'il ne s'agira pas si le fichier n'existe pas tant que son dirname n'existe pas.

31
Stéphane Chazelas

Dans ZSH, vous pouvez faire ce qui suit:

mypath=${0:a}

Ou, pour obtenir le répertoire dans lequel le script réside:

mydir=${0:a:h}

Source: ZSHEXPN (1) page man, extension de l'historique de la section, modificateurs de sous-section (ou simplement info -f zsh -n Modifiers).

27
mrmenken

J'utilise cela depuis plusieurs années maintenant:

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

Cette syntaxe doit être portable à tout interpréteur de style de coque de Bourne (testé avec bash, ksh88, ksh93, zsh, mksh, dash et busybox sh):

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

Cette version ajoute une compatibilité à l'héritage AT & T Bourne Shell (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
9
jlliagre

En supposant que vous signifiais vraiment le chemin absolu, c'est-à-dire un chemin de l'annuaire racine:

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

Cela fonctionne dans n'importe quelle coquille de style Bourne, au fait.

Si vous vouliez dire un chemin avec tous les liens symboliques résolus, c'est une question différente. readlink -f Fonctionne sur Linux (à l'exclusion des systèmes d'actionnaires dépouillés), FreeBSD, NetBSD, OpenBSD et Cygwin, mais pas sur OS/X, AIX, HP/UX ou Solaris. Si vous avez readlink, vous pouvez l'appeler dans une boucle:

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
}

Si vous n'avez pas readlink, vous pouvez l'approcher de ls -n, mais cela ne fonctionne que si ls _ ne mange aucun caractère non imprimable dans le nom de fichier.

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"
}

(Le supplément z est au cas où la cible de liaison se termine dans une nouvelle ligne, quelle substitution de commande mangerait autrement. La fonction realpath ne gère pas ce cas pour les noms de répertoires, à la manière. )

Si vous avez exécuté des autorisations sur le répertoire actuel - ou sur le répertoire à partir duquel vous avez exécuté votre script shell - si vous souhaitez qu'un chemin absolu d'un répertoire tout ce dont vous avez besoin est cd.

Étape 10 de cd SPEC

Si l'option -P est en vigueur, la variable d'environnement $PWD doit être définie sur la chaîne qui serait émise par pwd -P. Si l'autorisation est insuffisante sur le nouveau répertoire ou sur n'importe quel parent de ce répertoire, pour déterminer le répertoire de travail actuel, la valeur de la variable d'environnement $PWD est indéterminée.

et sur pwd -P

Le chemin d'accès à la sortie standard ne doit contenir aucun composant qui fait référence à des fichiers de type symbolique de type. S'il y a plusieurs chemins de chemin que l'utilitaire pwd pourrait écrire sur la sortie standard, un commencement avec un seul caractère/slash et un ou plusieurs départ avec deux caractères/slash, il doit écrire le chemin de départ avec un seul/Strash personnage. Le chemin doit contenir des caractères inutiles/slashs après le premier ou les deux caractères/slash.

C'est parce que cd -P doit définir le répertoire de travail actuel sur ce que pwd -P devrait-être imprimer et ​​que cd - doit imprimer le $OLDPWD que les travaux suivants:

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

SORTIR

/home/mikeserv/test/ln

attends-toi ...

cd -P . ; cd . ; cd -

SORTIR

/home/mikeserv/test/dir

Et quand je imprime avec cd -, je suis IMPRESSION $OLDPWD. cd définit $PWD dès que je cd -P .$PWD est maintenant un chemin absolu de / - donc je n'ai pas besoin d'autres variables. Et en fait, je n'aurais même pas besoin de la fuite ., mais il existe un comportement spécifié de la réinitialisation $PWD à $HOME dans une coque interactive lorsque cd n'est pas donné. Donc, c'est juste une bonne habitude de se développer.

Donc, juste faire ce qui précède sur le chemin dans ${0%/*} doit être plus que suffisant pour vérifier le chemin $0 'S, mais dans le cas où $0 est lui-même une liaison douce, vous ne pouvez probablement pas modifier le répertoire, malheureusement.

Voici une fonction qui va gérer cela:

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                                    
}

Il s'efforce de faire autant que possible dans la coque actuelle - sans invoquer un sous-vase - bien que des sous-armes soient invoquées pour des erreurs et des liens mous qui ne signent pas aux répertoires. Cela dépend d'une coque compatible POSIX et d'un compatible POSIX ls ainsi que d'un espace de noms Nettoyant _function(). Il fonctionnera toujours bien sans ce dernier, bien qu'il puisse écraser alors unset quelques fonctions de shell actuelles dans ce cas. En général, toutes ces dépendances devraient être disponibles assez de manière fiable sur une machine UNIX.

Appelé avec ou sans arguments la première chose qu'elle fait est réinitialisée $PWD à sa valeur canonique - il résout les liens qui y figurent dans leurs objectifs si nécessaire. Appelé sans arguments et c'est à ce sujet; mais a appelé avec eux et cela résoudra et canonicalisera le chemin de chemin pour chacun ou d'impression d'un message à stderr pourquoi pas.

Parce que cela fonctionne principalement dans la coque actuelle, il devrait être en mesure de gérer une liste d'arguments de n'importe quelle longueur. Il cherche également la variable $_zdlm__ (que ce soit aussi unsets quand elle est passée) et imprime sa valeur c-échappée immédiatement à droite de chacun de ses arguments, chacun est toujours suivi également par un seul caractère \newline.

Il fait beaucoup de répertoire changeant, mais autre que de la définir à sa valeur canonique, cela n'affecte pas $PWD, bien que $OLDPWD ne puisse par aucun moyen de compter sur le moment où il se passe.

Il essaie de quitter chacun de ses arguments dès qu'il pourrait. Il essaie d'abord de cd dans $1. Si cela peut, il imprime le chemin canonique de l'argument à stdout. S'il ne peut pas vérifier que $1 existe et n'est pas un lien mou. Si vrai, il imprime.

De cette manière, il gère tout argument de type de fichier que la Shell a des autorisations à résoudre sauf si $1 est un lien symbolique qui ne pointe pas vers un répertoire. Dans ce cas, il appelle while boucle dans un sous-vase.

Il appelle ls pour lire le lien. Le répertoire actuel doit être remplacé par sa valeur initiale afin de gérer de manière fiable tout chemin de référent, de sorte que, dans le sous-groupe de substitution de commande, la fonction fait:

cd -...ls...echo /

Il lande à gauche de la sortie de ls't peu importe que cela doit contenir complètement le nom de la liaison et la chaîne ->. Bien que j'ai essayé d'abord d'éviter cela avec shift et $IFS il s'avère que c'est la méthode la plus fiable aussi proche que possible. C'est la même chose de Gilles pauvres_mans_readlink fait - et c'est bien fait.

Il répète ce processus dans une boucle jusqu'à ce que le nom de fichier renvoyé de ls ne soit définitivement pas un lien mou. À ce stade, il canonalise ce chemin comme avant avec cd puis imprime.

Exemple d'utilisation:

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 \
    . ..      

SORTIR

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

Ou peut-être ...

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

zdlm=\\0 zpath * | cat -A

SORTIR

/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' -> ...
2
mikeserv

qu'en est-il d'une doublure unique sympathique lorsque theres python disponible pour empêcher de redéfinir un algorithme?

function readlink { python -c "import os.path; print os.path.realpath('$1')"; }

même que https://stackoverflow.com/a/7305217

0