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
nubme
la source
7
duplication possible de Conversion de chemin relatif en chemin absolu ou ceci .
Suspendu jusqu'à nouvel ordre.
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)

mpapis
la source
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 ".

epere4
la source
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)

Moritz Bunkus
la source
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
Eugen Konkov
la source
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.

synthétiseurpatel
la source
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.

Ernest A
la source
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.

babou
la source
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
}
changement de hachage
la source
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é.

biomiker
la source
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
MrSegFaulty
la source
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
Arkku
la source
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
Miaou
la source
1

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

Tobu
la source
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
fakedrake
la source
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
Shunfang Lan
la source
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") } }'
Josh Barton
la source
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/...

Alex Coventry
la source
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.

Éric H.
la source
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")" "${?}"
utilisateur
la source
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.

Ivoire Blakley
la source
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"
openCivilisation
la source