Comment récupérer un chemin absolu donné


160

Existe-t-il une commande pour récupérer le chemin absolu étant donné le chemin relatif?

Par exemple, je veux que $ line contienne le chemin absolu de chaque fichier dans dir ./etc/

find ./ -type f | while read line; do
   echo $line
done


1
pas en double direct, mais similaire
mpapis


Une bien meilleure solution que n'importe laquelle de celles énumérées jusqu'à présent est ici comment-convertir-un-chemin-relatif-en-chemin-absolu-en-unix
Daniel Genin

Réponses:


67

utilisation:

find "$(pwd)"/ -type f

pour obtenir tous les fichiers ou

echo "$(pwd)/$line"

pour afficher le chemin complet (si le chemin relatif est important)


2
et si je spécifie un chemin relatif dans la recherche ??
nubme

17
puis voir les liens dans le commentaire de votre question, le meilleur moyen est probablement 'readlink -f $ line'
mpapis

3
Pourquoi utiliser $(pwd)à la place de $PWD? (oui, je sais que pwdc'est un builtin)
Adam Katz

179

Essayez realpath.

~ $ sudo apt-get install realpath  # may already be installed
~ $ realpath .bashrc
/home/username/.bashrc

Pour éviter de développer les liens symboliques, utilisez realpath -s.

La réponse vient de " la commande bash / fish pour imprimer le chemin absolu vers un fichier ".


11
realpathne semble pas disponible sur Mac (OS X 10.11 "El Capitan"). :-(
Laryx Decidua

realpathne semble pas non plus disponible sur CentOS 6
user5359531

6
sur osx, brew install coreutilsapporterarealpath
Kevin Chen

Sur mon Ubuntu 18.04, realpathest déjà présent. Je n'ai pas eu à l'installer séparément.
Acumenus

Étonnamment, realpathest disponible sur Git pour Windows (pour moi, du moins).
Andrew Keeton

104

Si vous avez installé le package coreutils, vous pouvez généralement l'utiliser readlink -f relative_file_namepour récupérer le package absolu (avec tous les liens symboliques résolus)


3
Le comportement pour cela est un peu différent de ce que l'utilisateur demande, il suivra également et résoudra les liens symboliques récursifs n'importe où dans le chemin. Vous pourriez ne pas vouloir cela dans certains cas.
ffledgling

1
@BradPeabody Cela fonctionne sur un Mac si vous installez coreutils depuis homebrew brew install coreutils. Cependant l'exécutable est précédé de ag:greadlink -f relative_file_name
Miguel Isla

2
Notez que la page de manuel de readlink (1) a comme première phrase de sa description: "Notez que realpath (1) est la commande préférée à utiliser pour la fonctionnalité de canonisation."
josch

1
vous pouvez utiliser -e au lieu de -f pour vérifier si le fichier / répertoire existe ou non
Iceman

69
#! /bin/sh
echo "$(cd "$(dirname "$1")"; pwd)/$(basename "$1")"

UPD Quelques explications

  1. Ce script obtient le chemin relatif comme argument "$1"
  2. Ensuite, nous obtenons une partie dirname de ce chemin (vous pouvez passer dir ou fichier à ce script):dirname "$1"
  3. Ensuite, nous cd "$(dirname "$1")dans ce répertoire relatif et obtenons le chemin absolu pour celui-ci en exécutant la pwdcommande shell
  4. Après cela, nous ajoutons le nom de base au chemin absolu:$(basename "$1")
  5. Comme étape finale , nous echone

8
Cette réponse utilise le meilleur idiome de Bash
Rondo

2
readlink est la solution simple pour linux, mais cette solution fonctionne aussi sur OSX, donc +1
thetoolman

1
Ce script n'est pas équivalent à quoi realpathou readlink -ffaire. Par exemple, cela ne fonctionne pas sur les chemins où le dernier composant est un lien symbolique.
josch

2
@josch: La question ne concerne pas la résolution des liens symboliques. Mais si vous voulez faire cela, vous pouvez fournir une -Poption pour pwdcommander:echo "$(cd "$(dirname "$1")"; pwd -P)/$(basename "$1")"
Eugen Konkov

4
J'aime la réponse, mais cela ne fonctionne que si l'utilisateur est autorisé à CD dans le répertoire. Cela n'est peut-être pas toujours possible.
Matthias B

34

Pour ce que ça vaut, j'ai voté pour la réponse choisie, mais je voulais partager une solution. L'inconvénient est qu'il ne s'agit que de Linux - j'ai passé environ 5 minutes à essayer de trouver l'équivalent OSX avant d'arriver à Stack overflow. Je suis sûr que c'est là-bas cependant.

Sous Linux, vous pouvez utiliser readlink -een tandem avec dirname.

$(dirname $(readlink -e ../../../../etc/passwd))

rendements

/etc/

Et puis vous utilisez dirnamela sœur de, basenamepour simplement obtenir le nom du fichier

$(basename ../../../../../passwd)

rendements

passwd

Mets le tout ensemble..

F=../../../../../etc/passwd
echo "$(dirname $(readlink -e $F))/$(basename $F)"

rendements

/etc/passwd

Vous êtes en sécurité si vous ciblez un répertoire, basenamene retournera rien et vous vous retrouverez simplement avec des doubles barres obliques dans la sortie finale.


Entrée excellente avec dirname, readlinket basename. Cela m'a aidé à trouver le chemin absolu d'un lien symbolique - pas sa cible.
kevinarpe

Ne fonctionne pas lorsque vous souhaitez retourner le chemin vers des liens symboliques (ce que je dois juste faire ...).
Tomáš Zato - Réintégrer Monica le

Comment pourriez-vous trouver le chemin absolu vers un chemin qui n'existe pas?
synthesizerpatel

@synthesizerpatel Assez facilement, j'aurais pensé; si je suis dedans /home/GKFXet que je tape touch newfile, alors avant d'appuyer sur Entrée, vous pourriez comprendre que je veux dire "create / home / GKFX / newfile", qui est un chemin absolu vers un fichier qui n'existe pas encore.
GKFX

25

Je pense que c'est le plus portable:

abspath() {                                               
    cd "$(dirname "$1")"
    printf "%s/%s\n" "$(pwd)" "$(basename "$1")"
    cd "$OLDPWD"
}

Cela échouera si le chemin n'existe pas.


3
Il n'est pas nécessaire de recadrer le cd. Voir stackoverflow.com/a/21188136/1504556 . Votre est la meilleure réponse sur cette page, à mon humble avis. Pour les personnes intéressées, le lien explique pourquoi cette solution fonctionne.
peterh le

Ce n'est pas très portable, dirnamec'est un utilitaire de base GNU, pas commun à tous les Unixen je crois.
einpoklum

@einpoklum dirnameest un utilitaire standard POSIX, voir ici: pubs.opengroup.org/onlinepubs/9699919799/utilities/dirname.html
Ernest A

Oh mon Dieu, merci. J'essaie de réparer la version qui utilise ${1##*/}depuis un jour maintenant, et maintenant que j'ai remplacé cette poubelle par basename "$1"elle semble enfin gérer correctement les chemins qui se terminent par /.
l3l_aze

NB, cela ne fait pas la bonne chose avec un chemin se terminant par../..
Alex Coventry

16

realpath est probablement le meilleur

Mais ...

La question initiale était très confuse au départ, avec un exemple mal lié à la question comme indiqué.

La réponse choisie répond en fait à l'exemple donné, et pas du tout à la question du titre. La première commande est cette réponse (est-ce vraiment? Je doute), et pourrait faire aussi bien sans le '/'. Et je ne vois pas ce que fait la deuxième commande.

Plusieurs problèmes sont mitigés:

  • changer un chemin relatif en chemin absolu, quoi qu'il dénote, peut-être rien. ( En règle générale, si vous exécutez une commande telle que touch foo/bar, le chemin d'accès foo/bardoit exister pour vous, et éventuellement être utilisé dans le calcul, avant que le fichier ne soit réellement créé. )

  • il peut y avoir plusieurs chemins absolus qui désignent le même fichier (ou fichier potentiel), notamment à cause de liens symboliques (symlinks) sur le chemin, mais éventuellement pour d'autres raisons (un périphérique peut être monté deux fois en lecture seule). On peut ou non vouloir résoudre explicitement ces liens symboliques.

  • arriver à la fin d'une chaîne de liens symboliques vers un fichier ou un nom sans lien symbolique. Cela peut ou non donner un nom de chemin absolu, selon la façon dont cela est fait. Et on peut, ou peut ne pas vouloir le résoudre en un chemin absolu.

La commande readlink foosans option donne une réponse uniquement si son argument fooest un lien symbolique, et cette réponse est la valeur de ce lien symbolique. Aucun autre lien n'est suivi. La réponse peut être un chemin relatif: quelle que soit la valeur de l'argument du lien symbolique.

Cependant, readlinka des options (-f -e ou -m) qui fonctionneront pour tous les fichiers, et donneront un chemin absolu (celui sans lien symbolique) au fichier réellement indiqué par l'argument.

Cela fonctionne bien pour tout ce qui n'est pas un lien symbolique, bien que l'on puisse souhaiter utiliser un chemin absolu sans résoudre les liens symboliques intermédiaires sur le chemin. Ceci est fait par la commanderealpath -s foo

Dans le cas d'un argument de lien symbolique, readlinkavec ses options résoudra à nouveau tous les liens symboliques sur le chemin absolu de l'argument, mais cela inclura également tous les liens symboliques qui peuvent être rencontrés en suivant la valeur de l'argument. Vous ne voudrez peut-être pas cela si vous souhaitez un chemin absolu vers le lien symbolique de l'argument lui-même, plutôt que vers celui auquel il peut être lié. Encore une fois, si fooest un lien symbolique, realpath -s fooobtiendra un chemin absolu sans résoudre les liens symboliques, y compris celui donné en argument.

Sans l' -soption, realpathfait à peu près la même chose que readlink, sauf pour simplement lire la valeur d'un lien, ainsi que plusieurs autres choses. Il n'est tout simplement pas clair pour moi pourquoi readlinka ses options, créant apparemment une redondance indésirable avec realpath.

Explorer le Web ne dit pas grand chose de plus, sauf qu'il peut y avoir des variations entre les systèmes.

Conclusion: realpathc'est la meilleure commande à utiliser, avec le plus de flexibilité, du moins pour l'utilisation demandée ici.


10

Ma solution préférée était celle de @EugenKonkov car elle n'impliquait pas la présence d'autres utilitaires (le package coreutils).

Mais il a échoué pour les chemins relatifs "." et "..", voici donc une version légèrement améliorée gérant ces cas particuliers.

cdCependant, cela échoue toujours si l'utilisateur n'a pas l'autorisation d' accéder au répertoire parent du chemin relatif.

#! /bin/sh

# Takes a path argument and returns it as an absolute path. 
# No-op if the path is already absolute.
function to-abs-path {
    local target="$1"

    if [ "$target" == "." ]; then
        echo "$(pwd)"
    elif [ "$target" == ".." ]; then
        echo "$(dirname "$(pwd)")"
    else
        echo "$(cd "$(dirname "$1")"; pwd)/$(basename "$1")"
    fi
}

7

La réponse d'Eugen n'a pas vraiment fonctionné pour moi, mais cela a fonctionné:

    absolute="$(cd $(dirname \"$file\"); pwd)/$(basename \"$file\")"

Remarque: votre répertoire de travail actuel n'est pas affecté.


Remarque: c'est un script. où $ 1 est le premier argument pour cela
Eugen Konkov

5

La meilleure solution, à mon humble avis, est celle publiée ici: https://stackoverflow.com/a/3373298/9724628 .

Cela nécessite python pour fonctionner, mais il semble couvrir tous ou la plupart des cas marginaux et être une solution très portable.

  1. Avec la résolution des liens symboliques:
python -c "import os,sys; print(os.path.realpath(sys.argv[1]))" path/to/file
  1. ou sans elle:
python -c "import os,sys; print(os.path.abspath(sys.argv[1]))" path/to/file

3

Dans le cas de find, il est probablement plus simple de donner simplement le chemin absolu dans lequel effectuer la recherche, par exemple:

find /etc
find `pwd`/subdir_of_current_dir/ -type f

3

Si vous utilisez bash sur Mac OS X qui n'a pas de realpath et que son readlink peut imprimer le chemin absolu, vous pouvez avoir le choix de coder votre propre version pour l'imprimer. Voici ma mise en œuvre:

(pure bash)

abspath(){
  local thePath
  if [[ ! "$1" =~ ^/ ]];then
    thePath="$PWD/$1"
  else
    thePath="$1"
  fi
  echo "$thePath"|(
  IFS=/
  read -a parr
  declare -a outp
  for i in "${parr[@]}";do
    case "$i" in
    ''|.) continue ;;
    ..)
      len=${#outp[@]}
      if ((len==0));then
        continue
      else
        unset outp[$((len-1))] 
      fi
      ;;
    *)
      len=${#outp[@]}
      outp[$len]="$i"
      ;;
    esac
  done
  echo /"${outp[*]}"
)
}

(utilisez gawk)

abspath_gawk() {
    if [[ -n "$1" ]];then
        echo $1|gawk '{
            if(substr($0,1,1) != "/"){
                path = ENVIRON["PWD"]"/"$0
            } else path = $0
            split(path, a, "/")
            n = asorti(a, b,"@ind_num_asc")
            for(i in a){
                if(a[i]=="" || a[i]=="."){
                    delete a[i]
                }
            }
            n = asorti(a, b, "@ind_num_asc")
            m = 0
            while(m!=n){
                m = n
                for(i=1;i<=n;i++){
                    if(a[b[i]]==".."){
                        if(b[i-1] in a){
                            delete a[b[i-1]]
                            delete a[b[i]]
                            n = asorti(a, b, "@ind_num_asc")
                            break
                        } else exit 1
                    }
                }
            }
            n = asorti(a, b, "@ind_num_asc")
            if(n==0){
                printf "/"
            } else {
                for(i=1;i<=n;i++){
                    printf "/"a[b[i]]
                }
            }
        }'
    fi
}

(pure bsd awk)

#!/usr/bin/env awk -f
function abspath(path,    i,j,n,a,b,back,out){
  if(substr(path,1,1) != "/"){
    path = ENVIRON["PWD"]"/"path
  }
  split(path, a, "/")
  n = length(a)
  for(i=1;i<=n;i++){
    if(a[i]==""||a[i]=="."){
      continue
    }
    a[++j]=a[i]
  }
  for(i=j+1;i<=n;i++){
    delete a[i]
  }
  j=0
  for(i=length(a);i>=1;i--){
    if(back==0){
      if(a[i]==".."){
        back++
        continue
      } else {
        b[++j]=a[i]
      }
    } else {
      if(a[i]==".."){
        back++
        continue
      } else {
        back--
        continue
      }
    }
  }
  if(length(b)==0){
    return "/"
  } else {
    for(i=length(b);i>=1;i--){
      out=out"/"b[i]
    }
    return out
  }
}

BEGIN{
  if(ARGC>1){
    for(k=1;k<ARGC;k++){
      print abspath(ARGV[k])
    }
    exit
  }
}
{
  print abspath($0)
}

exemple:

$ abspath I/am/.//..//the/./god/../of///.././war
/Users/leon/I/the/war

1

Ce qu'ils ont dit, sauf find $PWDou (en bash) find ~+est un peu plus pratique.


1

Similaire à la réponse de @ ernest-a mais sans affecter $OLDPWDou définir une nouvelle fonction, vous pouvez déclencher un sous-shell(cd <path>; pwd)

$ pwd
/etc/apache2
$ cd ../cups 
$ cd -
/etc/apache2
$ (cd ~/..; pwd)
/Users
$ cd -
/etc/cups

1

Si le chemin relatif est un chemin de répertoire, essayez le mien, cela devrait être le meilleur:

absPath=$(pushd ../SOME_RELATIVE_PATH_TO_Directory > /dev/null && pwd && popd > /dev/null)

echo $absPath

Cette solution ne fonctionne que pour bash, voir aussi stackoverflow.com/a/5193087/712014
Michael

1
echo "mydir/doc/ mydir/usoe ./mydir/usm" |  awk '{ split($0,array," "); for(i in array){ system("cd "array[i]" && echo $PWD") } }'

3
Merci pour cet extrait de code, qui pourrait fournir une aide limitée à court terme. Une explication appropriée améliorerait considérablement sa valeur à long terme en montrant pourquoi c'est une bonne solution au problème, et la rendrait plus utile aux futurs lecteurs avec d'autres questions similaires. Veuillez modifier votre réponse pour ajouter des explications, y compris les hypothèses que vous avez formulées.
Toby Speight

1

Une amélioration de la version plutôt sympa de @ ernest-a:

absolute_path() {
    cd "$(dirname "$1")"
    case $(basename $1) in
        ..) echo "$(dirname $(pwd))";;
        .)  echo "$(pwd)";;
        *)  echo "$(pwd)/$(basename $1)";;
    esac
}

Cela traite correctement le cas où se trouve le dernier élément du chemin .., auquel cas la "$(pwd)/$(basename "$1")"réponse de in @ ernest-a apparaîtra comme accurate_sub_path/spurious_subdirectory/...


0

Si vous souhaitez transformer une variable contenant un chemin relatif en un chemin absolu, cela fonctionne:

   dir=`cd "$dir"`

"cd" fait écho sans changer le répertoire de travail, car exécuté ici dans un sous-shell.


2
Sur bash-4.3-p46, cela ne fonctionne pas: le shell imprime une ligne vide lorsque je coursdir=`cd ".."` && echo $dir
Michael

0

Il s'agit d'une solution chaînée de toutes les autres, par exemple, en cas d' realpathéchec, soit parce qu'elle n'est pas installée, soit parce qu'elle se termine avec un code d'erreur, la solution suivante est tentée jusqu'à ce qu'elle obtienne le bon chemin.

#!/bin/bash

function getabsolutepath() {
    local target;
    local changedir;
    local basedir;
    local firstattempt;

    target="${1}";
    if [ "$target" == "." ];
    then
        printf "%s" "$(pwd)";

    elif [ "$target" == ".." ];
    then
        printf "%s" "$(dirname "$(pwd)")";

    else
        changedir="$(dirname "${target}")" && basedir="$(basename "${target}")" && firstattempt="$(cd "${changedir}" && pwd)" && printf "%s/%s" "${firstattempt}" "${basedir}" && return 0;
        firstattempt="$(readlink -f "${target}")" && printf "%s" "${firstattempt}" && return 0;
        firstattempt="$(realpath "${target}")" && printf "%s" "${firstattempt}" && return 0;

        # If everything fails... TRHOW PYTHON ON IT!!!
        local fullpath;
        local pythoninterpreter;
        local pythonexecutables;
        local pythonlocations;

        pythoninterpreter="python";
        declare -a pythonlocations=("/usr/bin" "/bin");
        declare -a pythonexecutables=("python" "python2" "python3");

        for path in "${pythonlocations[@]}";
        do
            for executable in "${pythonexecutables[@]}";
            do
                fullpath="${path}/${executable}";

                if [[ -f "${fullpath}" ]];
                then
                    # printf "Found ${fullpath}\\n";
                    pythoninterpreter="${fullpath}";
                    break;
                fi;
            done;

            if [[ "${pythoninterpreter}" != "python" ]];
            then
                # printf "Breaking... ${pythoninterpreter}\\n"
                break;
            fi;
        done;

        firstattempt="$(${pythoninterpreter} -c "import os, sys; print( os.path.abspath( sys.argv[1] ) );" "${target}")" && printf "%s" "${firstattempt}" && return 0;
        # printf "Error: Could not determine the absolute path!\\n";
        return 1;
    fi
}

printf "\\nResults:\\n%s\\nExit: %s\\n" "$(getabsolutepath "./asdfasdf/ asdfasdf")" "${?}"

0

Vous pouvez utiliser la substitution de chaîne bash pour n'importe quel chemin relatif $ line:

line=$(echo ${line/#..\//`cd ..; pwd`\/})
line=$(echo ${line/#.\//`pwd`\/})
echo $line

La substitution de base avant de chaîne suit la formule
$ {string / # substring / replacement}
qui est bien discutée ici: https://www.tldp.org/LDP/abs/html/string-manipulation.html

Le \caractère annule le /lorsque nous voulons qu'il fasse partie de la chaîne que nous trouvons / remplaçons.


0

Je n'ai pas pu trouver une solution parfaitement portable entre Mac OS Catalina, Ubuntu 16 et Centos 7, j'ai donc décidé de le faire avec python inline et cela a bien fonctionné pour mes scripts bash.

to_abs_path() {
  python -c "import os; print os.path.abspath('$1')"
}

to_abs_path "/some_path/../secrets"
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.