Un moyen portable pour obtenir le chemin absolu du script?

29

Qu'est-ce qu'un moyen portable pour un script (zsh) de déterminer son chemin absolu?

Sous Linux, j'utilise quelque chose comme

mypath=$(readlink -f $0)

... mais ce n'est pas portable. (Par exemple, readlinksur darwin ne reconnaît pas le -fdrapeau, ni n'a d'équivalent.) (En outre, en utilisantreadlink pour cela est, certes, un hack d'aspect assez obscur.)

Qu'est-ce qui est plus portable?

kjo
la source
1
Voir aussi $ 0 inclura-t-il toujours le chemin du script?
Stéphane Chazelas

Réponses:

28

Avec zsh, c'est juste:

mypath=$0:A

Maintenant , pour d' autres coquilles, bien realpath()et readlink()sont des fonctions standard (ce dernier étant un appel système), realpathet readlinkne sont pas commande standard, bien que certains systèmes ont l' un ou l'autre ou les deux avec différents comportements et fonctionnalités.

Comme 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 GNU readlink -fque realpath()(GNU readlink -e) dans la mesure où il ne se plaindra pas si le fichier n'existe pas tant que son nom de répertoire existe.

Stéphane Chazelas
la source
Remarque: cela ne fonctionne pas pour votre .zshrc: consultez plutôt cet article .
Bryce Guinta
24

Dans zsh, vous pouvez effectuer les opérations suivantes:

mypath=${0:a}

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

mydir=${0:a:h}

Source: page de manuel zshexpn (1), section EXPANSION D'HISTOIRE, sous-section Modificateurs (ou simplement info -f zsh -n Modifiers).

mrmenken
la source
Doux! Je cherchais quelque chose comme ça depuis des lustres et ai lu tout le tas des pages de manuel de zsh à la recherche, mais il ne m'aurait jamais été venu de chercher sous 'Expansion historique'.
Vucar Timnärakrul
1
L'équivalent de GNU readlink -fserait plutôt $0:A.
Stéphane Chazelas
11

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")")
user17591
la source
1
je l'aime! jusqu'à présent, c'est assez portable. fonctionne sur Solaris, OmniOS, Linux, Mac et même Cygwin sur Windows 2008.
Tim Kennedy
4
Là où il n'est pas équivalent à GNU, readlink -fc'est quand le script lui-même est un lien symbolique.
Stéphane Chazelas
Sur Ubuntu 16.04 avec zsh, si j'exécute cela directement ou dans le sous-shell (comme suggéré) dans mon répertoire personnel ( /home/ville), il s'imprime /home/ville/zsh.
Ville
8

Cette syntaxe doit être portable à un interprète de style Bourne shell (testé avec bash, ksh88, ksh93, zsh, mksh, dashet busybox sh):

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

Cette version ajoute la compatibilité au shell AT&T Bourne hérité (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
jlliagre
la source
Merci. cependant, je pense que le désarmement $PWDpeut être exagéré - vous pouvez simplement le régler sur un courant absolu comme cd -P .. Je doute que cela fonctionnerait en bourneshell - mais cela devrait fonctionner dans tous ceux que vous avez testés pour la première fois. fait pour moi de toute façon.
mikeserv
@moose Quel OS utilisez-vous?
jlliagre
qui est l'orignal? quelle?
mikeserv
@mikeserv orignal est un passant qui a posté un commentaire au sujet de certains problème avec zshet dirnamemais retirer rapidement Hir / son commentaire ...
jlliagre
Votre script Bourne Shell ne fonctionnera pas avec un Bourne Shell car le Bourne Shell n'utilise pas getopt () pour cd (1).
schily
4

En supposant que vous vouliez vraiment dire le chemin absolu, c'est-à-dire un chemin depuis le répertoire racine:

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

Cela fonctionne d'ailleurs dans n'importe quel shell de style Bourne.

Si vous vouliez dire un chemin avec tous les liens symboliques résolus, c'est une autre affaire. readlink -ffonctionne sur Linux (à l'exception de certains systèmes BusyBox allégés), FreeBSD, NetBSD, OpenBSD et Cygwin, mais pas sur OS / X, AIX, HP / UX ou Solaris. Si c'est le cas readlink, vous pouvez l'appeler en 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'en avez pas readlink, vous pouvez l'approcher avec ls -n, mais cela ne fonctionne que si lsne modifie 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"
}

(L'extra zest dans le cas où la cible du lien se termine par une nouvelle ligne, ce que la substitution de commande mangerait autrement. La realpathfonction ne gère pas ce cas pour les noms de répertoire, soit dit en passant.)

Gilles 'SO- arrête d'être méchant'
la source
1
Connaissez-vous une lsimplémentation qui modifie les caractères non imprimables lorsque la sortie ne va pas à un terminal.
Stéphane Chazelas
1
@ StéphaneChazelas touch Stéphane; LC_ALL=C busybox ls Stéphane | catSt??phane(c'est si le nom est en UTF-8, latin1 vous en donne un ?). Je pense que je l'ai vu aussi sur les Unices commerciaux plus anciens.
Gilles 'SO- arrête d'être méchant'
@ StéphaneChazelas J'ai corrigé plusieurs bugs mais je n'ai pas été testé intensivement. Faites-moi savoir si cela échoue toujours dans certains cas (à part le manque d'autorisations d'exécution dans certains répertoires, je ne vais pas traiter ce cas de bord).
Gilles 'SO- arrête d'être méchant'
@Gilles - qu'est busybox-ce que c'est? selon git busybox ls n'a pas eu de changement de code depuis 2011. Mon busybox ls- vers 2013 - ne fait pas cette chose. Celui- ci - vers 2012 - le fait . Cela pourrait expliquer pourquoi. Avez-vous construit votre busyboxsupport avec Unicode - pour inclure le support wchar? Vous voudrez peut-être essayer, sinon vérifier les options de construction dans le mkinitcpio busyboxpackage.
mikeserv
Gilles - Je crois que j'ai initialement mal évalué cette réponse - ou du moins une partie de celle-ci. Alors que je crois fermement que votre mantra de noms de fichiers est une erreur totale, votre pauvre_man_readlink est certainement très bien fait. Si vous me faites la gentillesse de faire un montage - n'importe quel montage fera l'affaire - et de me cingler par la suite, je voudrais inverser mon vote à ce sujet.
mikeserv
1

À condition que vous ayez des autorisations d'exécution sur le répertoire courant - ou sur le répertoire à partir duquel vous avez exécuté votre script shell - si vous voulez un chemin absolu vers un répertoire, tout ce dont vous avez besoin est cd.

Étape 10 de cdla spécification de

Si l' -Poption est en vigueur, la $PWDvariable d'environnement doit être définie sur la chaîne qui serait sortie par pwd -P. S'il n'y a pas suffisamment d'autorisations sur le nouveau répertoire ou sur l'un des parents de ce répertoire pour déterminer le répertoire de travail en cours, la valeur de la $PWDvariable d'environnement n'est pas spécifiée.

Et sur pwd -P

Le chemin d'accès écrit sur la sortie standard ne doit contenir aucun composant faisant référence à des fichiers de type lien symbolique. S'il existe plusieurs chemins d'accès que l' pwdutilitaire pourrait écrire sur la sortie standard, un commençant par un caractère simple / barre oblique et un ou plusieurs commençant par deux caractères / barre oblique, il doit écrire le nom de chemin commençant par un caractère simple / barre oblique. Le chemin d'accès ne doit contenir aucun caractère / barre oblique inutile après le ou les deux premiers caractères / barre oblique.

C'est parce que cd -Pdoit définir le répertoire de travail en cours sur ce qui pwd -Pdevrait autrement être imprimé et qui cd -doit imprimer le $OLDPWDque cela fonctionne:

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

SORTIE

/home/mikeserv/test/ln

attendez ...

cd -P . ; cd . ; cd -

SORTIE

/home/mikeserv/test/dir

Et quand cd -j'imprime avec j'imprime $OLDPWD. cddéfinit $PWDdès que je suis cd -P . $PWDmaintenant un chemin absolu vers /- donc je n'ai pas besoin d'autres variables. Et en fait, je devrais même pas besoin de la fuite , .mais il y a un comportement déterminé de réinitialisation $PWDà $HOMEdans un shell interactif quand cdest sans fioritures. C'est donc juste une bonne habitude à développer.

Donc, simplement faire ce qui précède sur le chemin d'accès ${0%/*}devrait être plus que suffisant pour vérifier $0le chemin d'accès de, mais dans le cas qui $0est lui-même un lien logiciel, vous ne pouvez probablement pas y changer de répertoire, malheureusement.

Voici une fonction qui gérera 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 le shell actuel - sans invoquer de sous-shell - bien qu'il existe des sous-shell invoqués pour les erreurs et les liens logiciels qui ne pointent pas vers des répertoires. Cela dépend d'un shell compatible POSIX lset d'un _function()espace compatible POSIX ainsi que d'un espace de noms propre . Il fonctionnera toujours très bien sans ce dernier, bien qu'il puisse remplacer alors unsetcertaines fonctions shell actuelles dans ce cas. En général, toutes ces dépendances devraient être disponibles de manière assez fiable sur une machine Unix.

Appelé avec ou sans arguments, la première chose qu'il fait est réinitialisé $PWDà sa valeur canonique - il résout tous les liens qu'il contient vers leurs cibles si nécessaire. Appelé sans arguments et c'est à peu près tout; mais appelé avec eux et il résoudra et canonisera le chemin pour chacun ou bien affichera un message pour stderrpourquoi pas.

Parce qu'il fonctionne principalement dans le shell actuel, il devrait être capable de gérer une liste d'arguments de n'importe quelle longueur. Il recherche également la $_zdlmvariable (qui est également unsetaffichée lorsqu'elle est terminée) et imprime sa valeur d' \néchappement C immédiatement à droite de chacun de ses arguments, chacun étant toujours suivi également d'un seul caractère de ligne électronique.

Il change beaucoup de répertoire, mais, à part le mettre à sa valeur canonique, il n'affecte pas $PWD, bien $OLDPWDqu'il ne puisse en aucun cas être compté quand il est terminé.

Il essaie de quitter chacun de ses arguments dès qu'il le peut. Il essaie d'abord d' cdentrer $1. S'il le peut, il affiche le chemin canonique de l'argument vers stdout. S'il ne le peut pas, il vérifie qu'il $1existe et n'est pas un lien logiciel. Si c'est vrai, il s'imprime.

De cette façon, il gère tout argument de type de fichier auquel le shell a les droits d'adresser, sauf s'il $1s'agit d'un lien symbolique qui ne pointe pas vers un répertoire. Dans ce cas, il appelle la whileboucle dans un sous-shell.

Il appelle lsà lire le lien. Le répertoire courant doit d'abord être modifié à sa valeur initiale afin de gérer de manière fiable tous les chemins référents et ainsi, dans le sous-shell de substitution de commandes, la fonction:

cd -...ls...echo /

Il supprime le plus à gauche de lsla sortie de pour contenir entièrement le nom du lien et la chaîne ->. Bien que j'aie d'abord essayé d'éviter de le faire shiftet $IFSil s'avère que c'est la méthode la plus fiable aussi proche que possible. C'est la même chose que le pauvre_man_readlink de Gilles - et c'est bien fait.

Il répétera ce processus en boucle jusqu'à ce que le nom de fichier renvoyé ne lssoit définitivement pas un lien logiciel. À ce stade, il canonise ce chemin comme précédemment avec cdpuis 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 \
    . ..      

SORTIE

/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

SORTIE

/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' -> ...
mikeserv
la source
0

que diriez-vous d'un one-liner sympathique quand theres python disponible pour empêcher de redéfinir un algorithme?

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

identique à /programming//a/7305217

Antonio Daniel Rodriguez
la source