Come recuperare il percorso assoluto dato relativo


160

Esiste un comando per recuperare il percorso assoluto dato il percorso relativo?

Ad esempio, voglio che $ line contenga il percorso assoluto di ciascun file in dir ./etc/

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


1
non stretto duplicato, ma simile
mpapis


Una soluzione molto migliore di quella elencata finora è qui come convertire-percorso-relativo-assoluto-percorso-in-unix
Daniel Genin,

Risposte:


67

uso:

find "$(pwd)"/ -type f

per ottenere tutti i file o

echo "$(pwd)/$line"

per visualizzare il percorso completo (se il percorso relativo è importante)


2
cosa succede se specifico un percorso relativo nella ricerca ??
nubme,

17
quindi vedi i link nei commenti alla tua domanda, il modo migliore probabilmente è 'readlink -f $ line'
mpapis

3
Perché usare $(pwd)al posto di $PWD? (sì, lo so che pwdè incorporato)
Adam Katz,

178

Prova realpath.

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

Per evitare di espandere i collegamenti simbolici, utilizzare realpath -s.

La risposta arriva dal " comando bash / fish per stampare il percorso assoluto di un file ".


11
realpathnon sembra essere disponibile sul Mac (OS X 10.11 "El Capitan"). :-(
Laryx Decidua,

realpathnon sembra essere disponibile neanche su CentOS 6
user5359531

6
su osx, brew install coreutilsporteràrealpath
Kevin Chen il

Sul mio Ubuntu 18.04, realpathè già presente. Non ho dovuto installarlo separatamente.
Acumenus,

Sorprendentemente, realpathè disponibile su Git per Windows (almeno per me).
Andrew Keeton,

104

Se hai installato il pacchetto coreutils puoi generalmente usarlo readlink -f relative_file_nameper recuperare quello assoluto (con tutti i link simbolici risolti)


3
Il comportamento per questo è un po 'diverso da quello che l'utente chiede, seguirà e risolverà anche i collegamenti simbolici ricorsivi ovunque nel percorso. Potresti non volerlo in alcuni casi.
Sfogliando il

1
@BradPeabody Funziona su un Mac se installi coreutils da homebrew brew install coreutils. Tuttavia l'eseguibile è anteposto da un ag:greadlink -f relative_file_name
Miguel Isla

2
Si noti che la pagina di manuale di readlink (1) ha come prima frase la sua descrizione: "Nota realpath (1) è il comando preferito da usare per la funzionalità di canonicalizzazione".
Josch

1
è possibile utilizzare al posto di -e -f per controllare se la directory / file esiste o no
Iceman

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

UPD Alcune spiegazioni

  1. Questo script ottiene il percorso relativo come argomento "$1"
  2. Quindi otteniamo dirname parte di quel percorso (puoi passare dir o file a questo script):dirname "$1"
  3. Quindi entriamo cd "$(dirname "$1")in questa directory relativa e ne otteniamo il percorso assoluto eseguendo il pwdcomando shell
  4. Dopodiché aggiungiamo basename al percorso assoluto:$(basename "$1")
  5. Come passo finale che echosi

8
Questa risposta usa il miglior linguaggio di Bash
Rondo

2
readlink è la soluzione semplice per Linux, ma questa soluzione funziona anche su OSX, quindi +1
thetoolman

1
Questo script non equivale a cosa realpatho readlink -ffare. Ad esempio, non funziona su percorsi in cui l'ultimo componente è un collegamento simbolico.
Jos

2
@josch: la domanda non riguarda la risoluzione dei link simbolici. Ma se vuoi farlo puoi fornire -Pun'opzione per pwdcomandare:echo "$(cd "$(dirname "$1")"; pwd -P)/$(basename "$1")"
Eugen Konkov il

4
Mi piace la risposta, ma funziona solo se l'utente è autorizzato a eseguire il cd nella directory. Questo potrebbe non essere sempre possibile.
Matthias B,

34

Per quello che vale, ho votato per la risposta che è stata scelta, ma volevo condividere una soluzione. Il rovescio della medaglia è che è solo Linux - ho impiegato circa 5 minuti a cercare l'equivalente OSX prima di arrivare allo stack overflow. Sono sicuro che è là fuori però.

Su Linux puoi usare readlink -ein tandem con dirname.

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

i rendimenti

/etc/

E poi usi dirnamela sorella, basenamesolo per ottenere il nome del file

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

i rendimenti

passwd

Metterli tutti insieme..

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

i rendimenti

/etc/passwd

Sei sicuro se stai prendendo di mira una directory, basenamenon restituirà nulla e finirai con doppie barre nell'output finale.


Eccellente voce con dirname, readlinke basename. Ciò mi ha aiutato a ottenere il percorso assoluto di un collegamento simbolico, non il suo obiettivo.
kevinarpe,

Non funziona quando vuoi tornare al percorso dei collegamenti simbolici (cosa che mi è capitato di dover fare ...).
Tomáš Zato - Ripristina Monica il

Come hai mai potuto trovare il percorso assoluto per un percorso che non esiste?
synthesizerpatel

@synthesizerpatel Abbastanza facilmente, avrei pensato; se ci sto /home/GKFXe scrivo touch newfile, prima di premere Invio potresti capire che intendo "creare / home / GKFX / newfile", che è un percorso assoluto per un file che non esiste ancora.
GKFX,

25

Penso che questo sia il più portatile:

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

Fallirà se il percorso non esiste però.


3
Non è necessario ripetere il cd. Vedi stackoverflow.com/a/21188136/1504556 . La tua è la risposta migliore in questa pagina, IMHO. Per chi è interessato, il link fornisce una spiegazione del perché questa soluzione funzioni .
Pietro,

Questo non è molto portatile, dirnameè un'utilità di base GNU, non comune a tutti gli unixen che credo.
einpoklum,

@einpoklum dirnameè un'utilità POSIX standard, vedi qui: pubs.opengroup.org/onlinepubs/9699919799/utilities/dirname.html
Ernest A

Oh mio Dio, grazie. Sto provando a correggere la versione che utilizza ${1##*/}da un giorno ormai, e ora che ho sostituito quel cestino con basename "$1"esso sembra finalmente gestire correttamente i percorsi che finiscono in /.
l3l_aze,

NB, questo non fa la cosa giusta con un percorso che termina in../..
Alex Coventry il

16

realpath è probabilmente il migliore

Ma ...

Inizialmente la domanda iniziale era molto confusa, con un esempio scarsamente correlato alla domanda come affermato.

La risposta selezionata risponde effettivamente all'esempio fornito e per nulla alla domanda nel titolo. Il primo comando è quella risposta (è davvero? Ne dubito), e potrebbe fare altrettanto senza '/'. E non riesco a vedere cosa sta facendo il secondo comando.

Diverse questioni sono miste:

  • cambiando un nome di percorso relativo in uno assoluto, qualunque cosa denoti, forse nulla. ( In genere, se si emette un comando come touch foo/bar, il percorso foo/bardeve esistere per te ed eventualmente essere utilizzato nel calcolo, prima che il file venga effettivamente creato. )

  • potrebbero esserci diversi nomi di percorso assoluti che indicano lo stesso file (o potenziale file), in particolare a causa di collegamenti simbolici (collegamenti simbolici) sul percorso, ma probabilmente per altri motivi (un dispositivo può essere montato due volte in sola lettura). Si può o meno voler risolvere esplicitamente tali collegamenti simbolici.

  • arrivare alla fine di una catena di collegamenti simbolici a un file o nome non simbolico. Questo può o non può dare un nome di percorso assoluto, a seconda di come è fatto. E si può, o non si desidera, risolverlo in un percorso assoluto.

Il comando readlink foosenza opzione fornisce una risposta solo se il suo argomento fooè un collegamento simbolico e tale risposta è il valore di quel collegamento simbolico. Nessun altro collegamento è seguito. La risposta potrebbe essere un percorso relativo: qualunque fosse il valore dell'argomento symlink.

Tuttavia, readlinkha opzioni (-f -e o -m) che funzioneranno per tutti i file e assegneranno un percorso assoluto (quello senza collegamenti simbolici) al file effettivamente indicato dall'argomento.

Questo funziona bene per tutto ciò che non è un collegamento simbolico, anche se si potrebbe desiderare di utilizzare un percorso assoluto senza risolvere i collegamenti simbolici intermedi sul percorso. Questo viene fatto dal comandorealpath -s foo

Nel caso di un argomento symlink, readlinkcon le sue opzioni risolverà di nuovo tutti i symlink sul percorso assoluto dell'argomento, ma includerà anche tutti i link simbolici che possono essere incontrati seguendo il valore dell'argomento. Potresti non volerlo se desideri un percorso assoluto all'argomento symlink stesso, piuttosto che a qualunque cosa a cui possa collegarsi. Ancora una volta, se fooè un collegamento simbolico, realpath -s foootterrà un percorso assoluto senza risolvere i collegamenti simbolici, incluso quello indicato come argomento.

Senza l' -sopzione, realpathfa praticamente lo stesso readlink, tranne per la semplice lettura del valore di un collegamento, così come molte altre cose. Non mi è chiaro perché readlinkabbia le sue opzioni, creando apparentemente una ridondanza indesiderata con realpath.

L'esplorazione del Web non dice molto di più, tranne per il fatto che potrebbero esserci alcune variazioni tra i sistemi.

Conclusione: realpathè il comando migliore da usare, con la massima flessibilità, almeno per l'uso richiesto qui.


10

La mia soluzione preferita era quella di @EugenKonkov perché non implicava la presenza di altre utility (il pacchetto coreutils).

Ma non è riuscito per i relativi percorsi "." e "..", quindi ecco una versione leggermente migliorata che gestisce questi casi speciali.

cdTuttavia continua a fallire se l'utente non dispone dell'autorizzazione per accedere alla directory principale del percorso relativo.

#! /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 risposta di Eugen non ha funzionato abbastanza per me, ma questo ha fatto:

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

Nota a margine, l'attuale directory di lavoro non è interessata.


Avviso: questo è script. dove $ 1 è il primo argomento per questo
Eugen Konkov,

5

La soluzione migliore, imho, è quella pubblicata qui: https://stackoverflow.com/a/3373298/9724628 .

Richiede Python per funzionare, ma sembra coprire tutti o la maggior parte dei casi limite ed essere una soluzione molto portatile.

  1. Con risoluzione dei collegamenti simbolici:
python -c "import os,sys; print(os.path.realpath(sys.argv[1]))" path/to/file
  1. o senza di essa:
python -c "import os,sys; print(os.path.abspath(sys.argv[1]))" path/to/file

3

In findtal caso , è probabilmente più semplice fornire il percorso assoluto per la ricerca, ad esempio:

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

3

Se si utilizza bash su Mac OS X, che nessuno dei due ha realpath esistita, né il suo readlink può stampare il percorso assoluto, potrebbe essere scelta, ma per il codice la propria versione per stamparlo. Ecco la mia implementazione:

(puro 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[*]}"
)
}

(usa 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
}

(puro 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)
}

esempio:

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

1

Quello che hanno detto, tranne find $PWDo (in bash) find ~+è un po 'più conveniente.


1

Simile alla risposta di @ ernest-a, ma senza influenzare $OLDPWDo definire una nuova funzione potresti lanciare una subshell(cd <path>; pwd)

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

1

Se il percorso relativo è un percorso di directory, quindi prova il mio, dovrebbe essere il migliore:

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

echo $absPath

Questa soluzione funziona solo per bash, vedi anche 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
Grazie per questo frammento di codice, che potrebbe fornire un aiuto limitato a breve termine. Una spiegazione adeguata migliorerebbe notevolmente il suo valore a lungo termine mostrando perché questa è una buona soluzione al problema e la renderebbe più utile ai futuri lettori con altre domande simili. Si prega di modificare la risposta di aggiungere qualche spiegazione, tra le ipotesi che hai fatto.
Toby Speight,

1

Un miglioramento alla versione piuttosto bella di @ ernest-a:

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

Questo si occupa correttamente del caso in cui si trova l'ultimo elemento del percorso .., nel qual caso la "$(pwd)/$(basename "$1")"risposta di in @ ernest-a verrà visualizzata come accurate_sub_path/spurious_subdirectory/...


0

Se vuoi trasformare una variabile contenente un percorso relativo in un percorso assoluto, questo funziona:

   dir=`cd "$dir"`

"cd" fa eco senza cambiare la directory di lavoro, perché eseguito qui in una sotto-shell.


2
Su bash-4.3-p46, questo non funziona: la shell stampa una riga vuota quando corrodir=`cd ".."` && echo $dir
Michael,

0

Questa è una soluzione concatenata di tutte le altre, ad esempio quando realpathfallisce, o perché non è installata o perché esce con un codice di errore, quindi la soluzione successiva viene tentata fino a quando non trova il percorso giusto.

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

È possibile utilizzare la sostituzione della stringa bash per qualsiasi percorso relativo $ line:

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

La sostituzione di base della stringa iniziale segue la formula
$ {stringa / # sottostringa / sostituzione}
che è discussa bene qui: https://www.tldp.org/LDP/abs/html/string-manipulation.html

Il \personaggio nega il /momento in cui vogliamo che faccia parte della stringa che troviamo / sostituiamo.


0

Non sono riuscito a trovare una soluzione che fosse perfettamente portatile tra Mac OS Catalina, Ubuntu 16 e Centos 7, quindi ho deciso di farlo con Python in linea e ha funzionato bene per i miei script bash.

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

to_abs_path "/some_path/../secrets"
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.