Jak pobrać ścieżkę bezwzględną podaną względną


159

Czy istnieje polecenie pobrania ścieżki bezwzględnej podanej ścieżką względną?

Na przykład chcę, aby $ line zawierało bezwzględną ścieżkę każdego pliku w katalogu ./etc/

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


1
nie jest to zwykły duplikat, ale podobny
mpapis


O wiele lepszym rozwiązaniem niż którekolwiek z wymienionych do tej pory jest tutaj, jak przekonwertować-względną-ścieżkę-na-absolutną-ścieżkę-w-unix
Daniel Genin

Odpowiedzi:


66

posługiwać się:

find "$(pwd)"/ -type f

aby pobrać wszystkie pliki lub

echo "$(pwd)/$line"

wyświetlić pełną ścieżkę (jeśli ważna jest ścieżka względna)


2
co jeśli określę ścieżkę względną w znalezieniu?
nubme

17
następnie zobacz linki w komentarzach do swojego pytania, prawdopodobnie najlepszym sposobem jest „readlink -f $ line”
mpapis

3
Dlaczego warto używać $(pwd)zamiast $PWD? (tak, wiem, że pwdjest wbudowany)
Adam Katz

178

Spróbuj realpath.

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

Aby uniknąć rozwijania linków symbolicznych, użyj realpath -s.

Odpowiedź pochodzi z polecenia „ bash / fish polecenie wypisania bezwzględnej ścieżki do pliku ”.


11
realpathnie wydaje się być dostępny w systemie Mac (OS X 10.11 „El Capitan”). :-(
Laryx Decidua

realpathnie wydaje się być również dostępny w CentOS 6
user5359531

6
na osx, brew install coreutilsprzyniesierealpath
Kevin Chen

Na moim Ubuntu 18.04 realpathjest już obecny. Nie musiałem go instalować osobno.
Acumenus

Zaskakujące realpathjest , że jest dostępny na Git for Windows (przynajmniej dla mnie).
Andrew Keeton,

104

Jeśli masz zainstalowany pakiet coreutils, możesz ogólnie użyć go readlink -f relative_file_namew celu odzyskania absolutnego (z rozwiązanymi wszystkimi dowiązaniami symbolicznymi)


3
Zachowanie w tym przypadku różni się nieco od tego, o co prosi użytkownik, będzie również śledzić i rozwiązywać rekurencyjne dowiązania symboliczne w dowolnym miejscu ścieżki. W niektórych przypadkach możesz tego nie chcieć.
ffledgling

1
@BradPeabody Działa to na komputerze Mac, jeśli zainstalujesz coreutils z homebrew brew install coreutils. Jednak plik wykonywalny jest poprzedzony przez ag:greadlink -f relative_file_name
Miguel Isla

2
Zauważ, że strona podręcznika readlink (1) ma jako pierwsze zdanie opisu: „Uwaga realpath (1) jest poleceniem preferowanym do wykorzystania przy tworzeniu funkcji kanonicznych”.
josch

1
możesz użyć -e zamiast -f, aby sprawdzić, czy plik / katalog istnieje, czy nie
Iceman

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

UPD Kilka wyjaśnień

  1. Ten skrypt pobiera ścieżkę względną jako argument "$1"
  2. Wtedy otrzymujemy dirname część tej ścieżki (możesz przekazać katalog lub plik do tego skryptu):dirname "$1"
  3. Następnie cd "$(dirname "$1")przechodzimy do tego względnego katalogu i uzyskujemy do niego ścieżkę bezwzględną, uruchamiając pwdpolecenie powłoki
  4. Następnie dodajemy basename do ścieżki bezwzględnej:$(basename "$1")
  5. Jako ostatni krok zrobiliśmy echoto

8
Ta odpowiedź używa najlepszego idiomu Basha
Rondo,

2
readlink jest prostym rozwiązaniem dla Linuksa, ale to rozwiązanie działa również na OSX, więc +1
thetoolman

1
Ten skrypt nie jest odpowiednikiem tego, co realpathlub readlink -fzrobić. Na przykład nie działa na ścieżkach, w których ostatni składnik jest dowiązaniem symbolicznym.
josch

2
@josch: Pytanie nie dotyczy rozwiązywania linków symbolicznych. Ale jeśli chcesz to zrobić, możesz podać -Popcję pwdpolecenia:echo "$(cd "$(dirname "$1")"; pwd -P)/$(basename "$1")"
Eugen Konkov

4
Podoba mi się odpowiedź, ale działa tylko wtedy, gdy użytkownik może przejść do katalogu. Nie zawsze jest to możliwe.
Matthias B

34

Głosowałem za wybraną odpowiedzią, ale chciałem podzielić się rozwiązaniem. Wadą jest to, że jest to tylko Linux - spędziłem około 5 minut próbując znaleźć odpowiednik OSX, zanim doszedłem do przepełnienia stosu. Jestem pewien, że tam jest.

W systemie Linux można używać readlink -ew połączeniu z dirname.

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

plony

/etc/

A potem używasz dirnamesiostry ', basenameaby po prostu uzyskać nazwę pliku

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

plony

passwd

Poskładać wszystko do kupy..

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

plony

/etc/passwd

Jesteś bezpieczny, jeśli celujesz w katalog, basenamenic nie zwróci, a skończysz z podwójnymi ukośnikami w ostatecznym wyniku.


Doskonała pozycja z dirname, readlink, i basename. To pomogło mi uzyskać absolutną ścieżkę dowiązania symbolicznego, a nie jego cel.
kevinarpe

Nie działa, gdy chcesz zwrócić ścieżkę do dowiązań symbolicznych (co akurat muszę zrobić ...).
Tomáš Zato - Przywróć Monikę

Jak mogłeś kiedykolwiek znaleźć absolutną ścieżkę do ścieżki, która nie istnieje?
synthesizerpatel

@synthesizerpatel Całkiem łatwo, pomyślałbym; jeśli wchodzę /home/GKFXi piszę touch newfile, to zanim naciśnę klawisz Enter, możesz dojść do wniosku, że mam na myśli „create / home / GKFX / newfile”, co jest bezwzględną ścieżką do pliku, który jeszcze nie istnieje.
GKFX

25

Myślę, że jest to najbardziej przenośny:

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

Nie powiedzie się, jeśli ścieżka nie istnieje.


3
Nie ma potrzeby ponownego odtwarzania płyty CD. Zobacz stackoverflow.com/a/21188136/1504556 . Twoja jest najlepszą odpowiedzią na tej stronie, IMHO. Dla zainteresowanych link daje wyjaśnienie, dlaczego to rozwiązanie działa.
peterh

To nie jest zbyt przenośne, dirnamejest podstawowym narzędziem GNU, nie jest wspólne dla wszystkich unixenów, jak sądzę.
einpoklum

@einpoklum dirnameto standardowe narzędzie POSIX, patrz tutaj: pubs.opengroup.org/onlinepubs/9699919799/utilities/dirname.html
Ernest A

O mój Boże, dziękuję. Próbuję naprawić wersję używaną ${1##*/}od jednego dnia, a teraz, kiedy zastąpiłem ten kosz basename "$1", wydaje się, że w końcu poprawnie obsługują ścieżki kończące się na /.
l3l_aze

NB, to nie działa dobrze ze ścieżką kończącą się na../..
Alex Coventry,

16

realpath jest prawdopodobnie najlepszy

Ale ...

Początkowe pytanie było bardzo zagmatwane na początku, z przykładem słabo powiązanym z zadanym pytaniem.

Wybrana odpowiedź w rzeczywistości odpowiada na podany przykład, a nie na pytanie w tytule. Pierwsza komenda to odpowiedź (czy naprawdę? Wątpię) i równie dobrze mogłaby działać bez znaku „/”. I nie widzę, co robi drugie polecenie.

Kilka problemów jest mieszanych:

  • zmiana względnej ścieżki na absolutną, cokolwiek by to oznaczało, prawdopodobnie nic. ( Zazwyczaj, jeśli wydasz takie polecenie, jak touch foo/barnazwa ścieżki, foo/barmusi istnieć dla Ciebie i prawdopodobnie zostać użyta w obliczeniach, zanim plik zostanie faktycznie utworzony ).

  • może istnieć kilka bezwzględnych nazw ścieżek, które oznaczają ten sam plik (lub potencjalny plik), w szczególności z powodu dowiązań symbolicznych (dowiązań symbolicznych) na ścieżce, ale prawdopodobnie z innych powodów (urządzenie może być zamontowane dwa razy jako tylko do odczytu). Można lub nie chcieć rozwiązywać jawności takich dowiązań symbolicznych.

  • dotarcie do końca łańcucha dowiązań symbolicznych do pliku lub nazwy niebędącej dowiązaniem symbolicznym. To może, ale nie musi, dać absolutną nazwę ścieżki, w zależności od tego, jak jest to zrobione. I można, ale nie trzeba, przekształcić to w absolutną ścieżkę.

Polecenie readlink foobez opcji daje odpowiedź tylko wtedy, gdy jego argumentfoo jest dowiązanie symboliczne, a ta odpowiedź jest wartością tego dowiązania symbolicznego. Żaden inny link nie jest używany. Odpowiedzią może być ścieżka względna: cokolwiek było wartością argumentu dowiązania symbolicznego.

Jednak, readlink ma opcje (-f -e lub -m), które będą działać dla wszystkich plików i podadzą jedną bezwzględną ścieżkę dostępu (tę bez dowiązań symbolicznych) do pliku faktycznie oznaczonego przez argument.

Działa to dobrze w przypadku wszystkiego, co nie jest dowiązaniem symbolicznym, chociaż można chcieć użyć bezwzględnej nazwy ścieżki bez rozwiązywania pośrednich dowiązań symbolicznych na ścieżce. Odbywa się to za pomocą poleceniarealpath -s foo

W przypadku argumentu dowiązania symbolicznego, readlinkz jego opcjami ponownie rozwiążemy wszystkie dowiązania symboliczne na bezwzględnej ścieżce do argumentu, ale będzie to również obejmować wszystkie dowiązania symboliczne, które można napotkać, podążając za wartością argumentu. Możesz tego nie chcieć, jeśli chcesz mieć bezwzględną ścieżkę do samego dowodu symbolicznego argumentu, a nie do tego, do czego może ono prowadzić. Ponownie, jeśli foojest dowiązaniem symbolicznym, realpath -s foootrzyma ścieżkę bezwzględną bez rozpoznawania dowiązań symbolicznych, w tym podanego jako argument.

Bez tej -sopcji realpathrobi to samo, co readlinkpoza zwykłym odczytem wartości linku, a także kilkoma innymi rzeczami. Po prostu nie jest dla mnie jasne, dlaczego readlinkma swoje opcje, stwarzając pozornie niepożądaną nadmiarowość realpath .

Eksploracja sieci nie mówi nic więcej, z wyjątkiem tego, że mogą występować pewne różnice między systemami.

Wniosek: realpathto najlepsza komenda do użycia, z największą elastycznością, przynajmniej do użycia tutaj wymaganego.


10

Moim ulubionym rozwiązaniem było to przez @EugenKonkov ponieważ nie sugerować obecność innych mediów (pakiet coreutils).

Ale zawiodło dla ścieżek względnych "." i „..”, więc tutaj jest nieco ulepszona wersja obsługująca te specjalne przypadki.

cdJednak nadal kończy się niepowodzeniem, jeśli użytkownik nie ma uprawnień do dostępu do katalogu nadrzędnego ścieżki względnej.

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

Odpowiedź Eugena nie do końca mi odpowiadała, ale tak się stało:

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

Uwaga dodatkowa, twój bieżący katalog roboczy pozostaje niezmieniony.


Uwaga: to jest skrypt. gdzie 1 $ jest pierwszym argumentem za tym
Eugen Konkov

5

Najlepsze rozwiązanie, imho, to to zamieszczone tutaj: https://stackoverflow.com/a/3373298/9724628 .

Wymaga Pythona do działania, ale wydaje się, że obejmuje wszystkie lub większość przypadków skrajnych i jest bardzo przenośnym rozwiązaniem.

  1. Z rozwiązywaniem linków symbolicznych:
python -c "import os,sys; print(os.path.realpath(sys.argv[1]))" path/to/file
  1. czy bez:
python -c "import os,sys; print(os.path.abspath(sys.argv[1]))" path/to/file

3

W przypadku findprawdopodobnie najłatwiej jest po prostu podać bezwzględną ścieżkę wyszukiwania, np .:

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

3

Jeśli używasz bash w systemie Mac OS X, który ani nie ma realpath, ani jego readlink nie może wydrukować bezwzględnej ścieżki, możesz mieć wybór i musisz zakodować własną wersję, aby ją wydrukować. Oto moja realizacja:

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

(użyj 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
}

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

przykład:

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

1

To, co powiedzieli, z wyjątkiem find $PWDlub (w bash), find ~+jest nieco wygodniejsze.


1

Podobnie jak odpowiedź @ ernest-a, ale bez wpływu $OLDPWDlub definiowania nowej funkcji można uruchomić podpowłokę(cd <path>; pwd)

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

1

Jeśli ścieżka względna jest ścieżką do katalogu, spróbuj mojej, powinna być najlepsza:

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

echo $absPath

To rozwiązanie działa tylko w przypadku bash, zobacz także 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
Dziękujemy za ten fragment kodu, który może zapewnić ograniczoną krótkoterminową pomoc. Właściwe wyjaśnienie znacznie poprawiłoby jego długoterminową wartość, pokazując, dlaczego jest to dobre rozwiązanie problemu, i uczyniłoby go bardziej użytecznym dla przyszłych czytelników z innymi, podobnymi pytaniami. Proszę edytować swoją odpowiedź dodać kilka wyjaśnień, w tym założeń już wykonanych.
Toby Speight

1

Ulepszenie do raczej ładnej wersji @ ernest-a:

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

Rozwiązanie to poprawnie rozwiązuje przypadek, w którym znajduje się ostatni element ścieżki .., w którym to przypadku odpowiedź "$(pwd)/$(basename "$1")"in @ ernest-a pojawi się jako accurate_sub_path/spurious_subdirectory/...


0

Jeśli chcesz przekształcić zmienną zawierającą ścieżkę względną w ścieżkę bezwzględną, działa to:

   dir=`cd "$dir"`

„cd” powtarza echo bez zmiany katalogu roboczego, ponieważ jest wykonywany tutaj w powłoce podrzędnej.


2
Na bash-4.3-p46 to nie działa: powłoka drukuje pustą linię, kiedy uruchamiamdir=`cd ".."` && echo $dir
Michael

0

Jest to rozwiązanie łańcuchowe od wszystkich innych, na przykład w przypadku realpathniepowodzenia, ponieważ nie jest zainstalowane lub kończy się z kodem błędu, wówczas podejmowane są próby rozwiązania następnego, dopóki nie uzyska właściwej ścieżki.

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

Możesz użyć podstawienia ciągu bash dla dowolnej ścieżki względnej $ line:

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

Podstawowe podstawianie początku ciągu jest zgodne ze wzorem
$ {string / # substring / replace},
który jest dobrze omówiony tutaj: https://www.tldp.org/LDP/abs/html/string-manipulation.html

\Charakter neguje /, gdy chcemy, aby być częścią łańcucha, które Znajdź / Zamień.


0

Nie mogłem znaleźć rozwiązania, które byłoby zgrabnie przenośne między Mac OS Catalina, Ubuntu 16 i Centos 7, więc zdecydowałem się to zrobić z wbudowanym Pythonem i działało dobrze dla moich skryptów bash.

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

to_abs_path "/some_path/../secrets"
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.