Rozwiń możliwą ścieżkę względną w bash

82

Jako argumenty do mojego skryptu są ścieżki do plików. Mogą one oczywiście być względne (lub zawierać ~). Ale dla funkcji, które napisałem, potrzebuję ścieżek, które są bezwzględne, ale nie mają rozwiązanych ich dowiązań symbolicznych.

Czy jest do tego jakaś funkcja?

laar
źródło
1
Wypróbuj funkcję readlink - andy.wordpress.com/2008/05/09/bash-equivalent-for-php-realpath
arunkumar
7
readlinkrozwiązuje dowiązania symboliczne; OP tego nie chce.
Keith Thompson,
1
echo $(cd some_directory && pwd), nie rozwiązuj dowiązania symbolicznego, nie powiedzie się, jeśli jakiś_katalog nie istnieje. katalog roboczy nie ma wpływu. lub przypisz do zmiennej MY_PATH=$(cd some_directory && pwd).
qeatzy

Odpowiedzi:

79

MY_PATH=$(readlink -f $YOUR_ARG)rozwiąże ścieżki względne, takie jak "./"i"../"

Rozważ to również ( źródło ):

#!/bin/bash
dir_resolve()
{
cd "$1" 2>/dev/null || return $?  # cd to desired directory; if fail, quell any error messages but return exit status
echo "`pwd -P`" # output full, link-resolved path
}

# sample usage
if abs_path="`dir_resolve \"$1\"`"
then
echo "$1 resolves to $abs_path"
echo pwd: `pwd` # function forks subshell, so working directory outside function is not affected
else
echo "Could not reach $1"
fi
Amir Afghani
źródło
to z pewnością fajne, ale problem polega na tym, że linki simlink są rozwiązane, co doprowadziłoby do zamieszania wśród użytkowników.
laar
man pwd: "-P Wyświetl fizyczny bieżący katalog roboczy (wszystkie dowiązania symboliczne rozwiązane)", po prostu usuń-P
andsens,
7
Aby uzyskać readlink -ffunkcjonalność w systemie OS X, możesz użyć Homebrew, a następnie uruchomić: $ brew install coreutils Możesz następnie użyć greadlink -f.
jgpawletko
46

http://www.linuxquestions.org/questions/programming-9/bash-script-return-full-path-and-filename-680368/page3.html zawiera następujące elementy

function abspath {
    if [[ -d "$1" ]]
    then
        pushd "$1" >/dev/null
        pwd
        popd >/dev/null
    elif [[ -e "$1" ]]
    then
        pushd "$(dirname "$1")" >/dev/null
        echo "$(pwd)/$(basename "$1")"
        popd >/dev/null
    else
        echo "$1" does not exist! >&2
        return 127
    fi
}

który używa pushd/, popdaby wejść w stan, w którym pwdjest przydatny.

Mike Samuel
źródło
2
Niestety, jest to naprawdę najlepsza odpowiedź tutaj i nie została wybrana do miejsca, w którym należy. +1 za dokładną odpowiedź na pytanie PO. Chce, aby linki symboliczne nie były rozwiązane, co jest tak naprawdę kwestią lokalnego basha DIRSTACKi nic więcej. Niezłe rozwiązanie!
cptstubing06
5
Dążąc do standardowej shkompatybilności, pushd; cd <dir>; <cmd>; popdmożna go zastąpić (cd <dir>; <cmd>).
Abbafei
16

Prosta jedna linijka:

function abs_path {
  (cd "$(dirname '$1')" &>/dev/null && printf "%s/%s" "$PWD" "${1##*/}")
}

Stosowanie:

function do_something {
    local file=$(abs_path $1)
    printf "Absolute path to %s: %s\n" "$1" "$file"
}
do_something $HOME/path/to/some\ where

Nadal próbuję dowiedzieć się, jak mogę to zrobić, aby całkowicie nie zdawać sobie sprawy z tego, czy ścieżka istnieje, czy nie (więc można jej używać również podczas tworzenia plików).

andsens
źródło
1
Czy to nie zmieni bieżącego katalogu roboczego? Czy powinienem cd -później go zresetować?
devios1
4
Nie ma potrzeby. Zwróć uwagę na pareny, one powodują, że polecenia wewnątrz są uruchamiane w podpowłoce. Stan (np. Katalog roboczy) podpowłoki nie wycieka do jej powłoki nadrzędnej. Przeczytaj więcej tutaj: tldp.org/LDP/abs/html/subshells.html
andsens
1
Ładny jednolinijkowy. Jest jednak problem: jeśli rozwinięta wartość $ 1 nie ma żadnego ukośnika (tj. Jest to nazwa pliku w bieżącym katalogu), to $ {1% / *} rozwija się do samej nazwy pliku i polecenie cd kończy się niepowodzeniem. Możesz zamiast tego użyć $ (dirname $ 1), które rozwinie się do '.' w tym wypadku.
Paulo
Masz rację, zaktualizowany. Mam pewne problemy z wymianą cytując ${1##*/}ze basenamechociaż, bez narażania go w osobnej zmiennej, ścieżki ze spacjami nie działa prawidłowo.
andsens
1
@BryanP Skończyło się na tym, że całkowicie przesadziłem z synchronizatorem dotfile, który utrzymuję. Ma funkcję clean_path (), która usuwa niepotrzebne części ścieżki, jeśli umieścisz ścieżkę względną na końcu $PWDi przeprowadzisz ją przez nią, powinna działać całkiem dobrze: github.com/andsens/homeshick/blob/ ...
andsens
8

To załatwia sprawę na OS X: $(cd SOME_DIRECTORY 2> /dev/null && pwd -P)

Powinien działać wszędzie. Inne rozwiązania wydawały się zbyt skomplikowane.

Mikey
źródło
4

na OS X, którego możesz użyć

stat -f "%N" YOUR_PATH

na Linuksie możesz mieć realpathplik wykonywalny. jeśli nie, poniższe mogą działać (nie tylko w przypadku linków):

readlink -c YOUR_PATH
Witalij Kushner
źródło
20
Odpowiedź OS X nie działa. stat -f "%N" PATHdaje mi dokładnie tę samą ścieżkę, którą mu podałem.
Lily Ballard
3

Użyj readlink -f <relative-path>np

export FULLPATH=`readlink -f ./`
isapir
źródło
2

Może jest to bardziej czytelne i nie używa podpowłoki i nie zmienia bieżącego katalogu:

dir_resolve() {
  local dir=`dirname "$1"`
  local file=`basename "$1"`
  pushd "$dir" &>/dev/null || return $? # On error, return error code
  echo "`pwd -P`/$file" # output full, link-resolved path with filename
  popd &> /dev/null
}
sja
źródło
1

Jest inna metoda. Możesz użyć osadzania Pythona w skrypcie bash, aby rozwiązać ścieżkę względną.

abs_path=$(python3 - <<END
from pathlib import Path
path = str(Path("$1").expanduser().resolve())
print(path)
END
)
cy8g3n
źródło
0

samodzielna edycja, właśnie zauważyłem, że OP powiedział, że nie szuka rozwiązanych linków symbolicznych:

„Ale dla funkcji, które napisałem, potrzebuję ścieżek, które są bezwzględne, ale nie mają rozwiązanych dowiązań symbolicznych”.

Więc myślę, że to jednak nie jest tak odpowiednie do jego pytania. :)

Ponieważ przez lata napotykałem na to wiele razy, a tym razem potrzebowałem czystej przenośnej wersji bash, której mógłbym używać na OSX i Linuksie, napisałem jedną:

Żywa wersja mieszka tutaj:

https://github.com/keen99/shell-functions/tree/master/resolve_path

ale ze względu na SO, oto aktualna wersja (wydaje mi się, że jest dobrze przetestowana, ale jestem otwarty na opinie!)

Może nie być trudne, aby działał na zwykłej skorupie burne (sh), ale nie próbowałem ... Za bardzo lubię $ FUNCNAME. :)

#!/bin/bash

resolve_path() {
    #I'm bash only, please!
    # usage:  resolve_path <a file or directory> 
    # follows symlinks and relative paths, returns a full real path
    #
    local owd="$PWD"
    #echo "$FUNCNAME for $1" >&2
    local opath="$1"
    local npath=""
    local obase=$(basename "$opath")
    local odir=$(dirname "$opath")
    if [[ -L "$opath" ]]
    then
    #it's a link.
    #file or directory, we want to cd into it's dir
        cd $odir
    #then extract where the link points.
        npath=$(readlink "$obase")
        #have to -L BEFORE we -f, because -f includes -L :(
        if [[ -L $npath ]]
         then
        #the link points to another symlink, so go follow that.
            resolve_path "$npath"
            #and finish out early, we're done.
            return $?
            #done
        elif [[ -f $npath ]]
        #the link points to a file.
         then
            #get the dir for the new file
            nbase=$(basename $npath)
            npath=$(dirname $npath)
            cd "$npath"
            ndir=$(pwd -P)
            retval=0
            #done
        elif [[ -d $npath ]]
         then
        #the link points to a directory.
            cd "$npath"
            ndir=$(pwd -P)
            retval=0
            #done
        else
            echo "$FUNCNAME: ERROR: unknown condition inside link!!" >&2
            echo "opath [[ $opath ]]" >&2
            echo "npath [[ $npath ]]" >&2
            return 1
        fi
    else
        if ! [[ -e "$opath" ]]
         then
            echo "$FUNCNAME: $opath: No such file or directory" >&2
            return 1
            #and break early
        elif [[ -d "$opath" ]]
         then 
            cd "$opath"
            ndir=$(pwd -P)
            retval=0
            #done
        elif [[ -f "$opath" ]]
         then
            cd $odir
            ndir=$(pwd -P)
            nbase=$(basename "$opath")
            retval=0
            #done
        else
            echo "$FUNCNAME: ERROR: unknown condition outside link!!" >&2
            echo "opath [[ $opath ]]" >&2
            return 1
        fi
    fi
    #now assemble our output
    echo -n "$ndir"
    if [[ "x${nbase:=}" != "x" ]]
     then
        echo "/$nbase"
    else 
        echo
    fi
    #now return to where we were
    cd "$owd"
    return $retval
}

oto klasyczny przykład, dzięki naparowi:

%% ls -l `which mvn`
lrwxr-xr-x  1 draistrick  502  29 Dec 17 10:50 /usr/local/bin/mvn@ -> ../Cellar/maven/3.2.3/bin/mvn

użyj tej funkcji, a zwróci ona -real- ścieżkę:

%% cat test.sh
#!/bin/bash
. resolve_path.inc
echo
echo "relative symlinked path:"
which mvn
echo
echo "and the real path:"
resolve_path `which mvn`


%% test.sh

relative symlinked path:
/usr/local/bin/mvn

and the real path:
/usr/local/Cellar/maven/3.2.3/libexec/bin/mvn
zapalony
źródło
0

Jeśli Twój system operacyjny to obsługuje, użyj:

realpath "./some/dir"

I używając go w zmiennej:

some_path="$(realpath "./some/dir")"

Który poszerzy twoją ścieżkę. Testowany na Ubuntu i CentOS, może nie być dostępny na twoim. Niektórzy zalecają readlink, ale dokumentacja dla readlink mówi:

Uwaga realpath (1) jest poleceniem preferowanym do wykorzystania w funkcji kanonizacji.

Na wypadek, gdyby ludzie zastanawiali się, dlaczego cytuję moje zmienne, chodzi o zachowanie spacji na ścieżkach. Jak robi realpath some pathdaje dwa różne wyniki ścieżce. Ale realpath "some path"zwróci jeden. Podane parametry ftw :)

Nalot
źródło
-1

Czy musisz używać wyłącznie basha? Musiałem to zrobić i miałem dość różnic między Linuksem i OS X. Więc użyłem PHP jako szybkiego i brudnego rozwiązania.

#!/usr/bin/php <-- or wherever
<?php
{
   if($argc!=2)
      exit();
   $fname=$argv[1];
   if(!file_exists($fname))
      exit();
   echo realpath($fname)."\n";
}
?>

Wiem, że to niezbyt eleganckie rozwiązanie, ale działa.

David G.
źródło
To nic nie robi, ale zwraca ścieżkę do pliku, jeśli znasz już ścieżkę do pliku lub plik znajduje się w bieżącym katalogu ... nie znajduje pliku w ustawieniach PATH
G-Man,