Jak rozwiązać dowiązania symboliczne w skrypcie powłoki

220

Biorąc pod uwagę ścieżkę bezwzględną lub względną (w systemie uniksopodobnym), chciałbym ustalić pełną ścieżkę celu po rozwiązaniu jakichkolwiek pośrednich dowiązań symbolicznych. Punkty bonusowe za równoczesne rozwiązanie ~ notacji nazwy użytkownika.

Jeśli celem jest katalog, może być możliwe chdir () do katalogu, a następnie wywołanie getcwd (), ale naprawdę chcę to zrobić ze skryptu powłoki zamiast pisać pomocnika C. Niestety, powłoki mają tendencję do ukrywania przed użytkownikami istnienia dowiązań symbolicznych (jest to bash na OS X):

$ ls -ld foo bar
drwxr-xr-x   2 greg  greg  68 Aug 11 22:36 bar
lrwxr-xr-x   1 greg  greg   3 Aug 11 22:36 foo -> bar
$ cd foo
$ pwd
/Users/greg/tmp/foo
$

To, czego chcę, to funkcja resolver () taka, że ​​po uruchomieniu z katalogu tmp w powyższym przykładzie resolve („foo”) == "/ Users / greg / tmp / bar”.

Greg Hewgill
źródło

Odpowiedzi:

92

Zgodnie ze standardami pwd -Ppowinien zwrócić ścieżkę z rozwiązanymi linkami symbolicznymi.

Funkcja C char *getcwd(char *buf, size_t size)z unistd.hpowinna mieć takie samo zachowanie.

getcwd pwd

kauppi
źródło
22
To działa tylko dla (bieżącego) katalogu? Jeśli celem jest plik, nic nie da ...
daleko
4
Nie działa, jeśli link jest zepsuty, ponieważ nie można ustawić go jako bieżącej ścieżki
Tom Howard,
6
Podsumowując, korzystając z perspektywy czasu: Ta odpowiedź działa tylko w bardzo ograniczonych okolicznościach, a mianowicie, jeśli dowiązanie symboliczne jest do katalogu, który faktycznie istnieje ; plus, musisz najpierw cdto zrobić, zanim zadzwonisz pwd -P. Innymi słowy: nie pozwoli ci to na usunięcie (zobacz cel) dowiązań symbolicznych do plików lub zepsutych dowiązań symbolicznych, a dla rozwiązania dowiązań symbolicznych istniejących katalogów musisz wykonać dodatkową pracę (przywrócić poprzedni działający katalog lub zlokalizować wywołania cdi pwd -Pw podpowłoce).
mklement0
na złe szukam sposobu na rozwiązanie pliku, a nie katalogu.
Martin
Jak zauważyli inni, to tak naprawdę nie odpowiada na pytanie. Odpowiedź @ pixelbeat poniżej.
erstaples
401
readlink -f "$path"

Uwaga edytora: Powyższe działa z GNU readlink i FreeBSD / PC-BSD / OpenBSD readlink , ale nie w OS X od 10.11.
GNU readlink oferuje dodatkowe, powiązane opcje, takie jak -mrozstrzyganie dowiązania symbolicznego, czy istnieje ostateczny cel.

Uwaga: od czasu wydania GNU coreutils 8.15 (2012-01-06) dostępny jest program realpath , który jest mniej tępy i bardziej elastyczny niż powyższe. Jest także kompatybilny z FreeBSD o tej samej nazwie. Zawiera także funkcję generowania ścieżki względnej między dwoma plikami.

realpath $path

[Dodatek administratora poniżej z komentarza halloleo - danorton]

W systemie Mac OS X (przez co najmniej 10.11.x) używaj readlinkbez -fopcji:

readlink $path

Uwaga edytora: To nie rozwiąże rekurencyjnie dowiązań symbolicznych, a zatem nie zgłosi ostatecznego celu; np. biorąc pod uwagę dowiązanie symboliczne, aktóre wskazuje b, co z kolei wskazuje c, będzie to tylko raportować b(i nie zapewni, że zostanie wyprowadzone jako ścieżka bezwzględna ).
Użyj następującego perlpolecenia w systemie OS X, aby wypełnić lukę brakującej readlink -ffunkcji:
perl -MCwd -le 'print Cwd::abs_path(shift)' "$path"

pixelbeat
źródło
5
To nie działa w Mac OS X - patrz stackoverflow.com/questions/1055671/…
Bkkbrad
1
wstyd z powodu niezgodności systemu OS X, w przeciwnym razie fajne +1
jkp,
11
W OS X można zainstalować Coreutils z Homebrew. Instaluje go jako „grealpath”.
Kief
10
readlinkdziała na OSX, ale potrzebuje innej składni: readlink $path bez-f .
halloleo
2
readlink nie odwołuje się jednak do odwoływania się do wielu warstw dowiązania symbolicznego, po prostu usuwa jedną warstwę na raz
Magnus
26

Wydaje się, że „pwd -P” działa, jeśli chcesz tylko katalog, ale jeśli z jakiegoś powodu chcesz nazwę rzeczywistego pliku wykonywalnego, nie sądzę, żeby to pomogło. Oto moje rozwiązanie:

#!/bin/bash

# get the absolute path of the executable
SELF_PATH=$(cd -P -- "$(dirname -- "$0")" && pwd -P) && SELF_PATH=$SELF_PATH/$(basename -- "$0")

# resolve symlinks
while [[ -h $SELF_PATH ]]; do
    # 1) cd to directory of the symlink
    # 2) cd to the directory of where the symlink points
    # 3) get the pwd
    # 4) append the basename
    DIR=$(dirname -- "$SELF_PATH")
    SYM=$(readlink "$SELF_PATH")
    SELF_PATH=$(cd "$DIR" && cd "$(dirname -- "$SYM")" && pwd)/$(basename -- "$SYM")
done
tlrobinson
źródło
17

Jednym z moich ulubionych jest realpath foo

realpath - zwraca kanoniczną bezwzględną nazwę ścieżki

realpath rozwija wszystkie dowiązania symboliczne i rozwiązuje odwołania do znaków „/./”, „/../” i dodatkowych znaków „/” w łańcuchu zakończonym znakiem null o nazwie path i
       przechowuje kanonizowaną bezwzględną nazwę ścieżki w buforze o rozmiarze PATH_MAX o nazwie resolved_path. Powstała ścieżka nie będzie miała dowiązania symbolicznego, „/./” lub
       '/../' składniki.
Gregory
źródło
W Debianie (etch i później) to polecenie jest dostępne w pakiecie realpath.
Phil Ross
2
realpath jest teraz (styczeń 2012) częścią coreutils i jest kompatybilny wstecz z wersją debian i BSD
pixelbeat
1
Nie mam realpathCentos 6 z GNU coreutils 8.4.31. Natknąłem kilku innych na Unix i Linux , które mają Coreutils GNU pakowane bez realpath . Wydaje się więc, że zależy to nie tylko od wersji.
toxalot
Wolę realpathwięcej, readlinkponieważ oferuje flagi opcji, takie jak--relative-to
arr_sea
10
readlink -e [filepath]

wydaje się być dokładnie tym, o co prosisz - akceptuje ścieżkę arbitrażową, rozwiązuje wszystkie dowiązania symboliczne i zwraca „prawdziwą” ścieżkę - i jest to „standardowy * nix”, który prawdopodobnie wszystkie systemy już mają

Chuck Kollars
źródło
Właśnie mnie ugryzł. To nie działa na Macu i szukam zamiennika.
zjazd
5

Inny sposób:

# Gets the real path of a link, following all links
myreadlink() { [ ! -h "$1" ] && echo "$1" || (local link="$(expr "$(command ls -ld -- "$1")" : '.*-> \(.*\)$')"; cd $(dirname $1); myreadlink "$link" | sed "s|^\([^/].*\)\$|$(dirname $1)/\1|"); }

# Returns the absolute path to a command, maybe in $PATH (which) or not. If not found, returns the same
whereis() { echo $1 | sed "s|^\([^/].*/.*\)|$(pwd)/\1|;s|^\([^/]*\)$|$(which -- $1)|;s|^$|$1|"; } 

# Returns the realpath of a called command.
whereis_realpath() { local SCRIPT_PATH=$(whereis $1); myreadlink ${SCRIPT_PATH} | sed "s|^\([^/].*\)\$|$(dirname ${SCRIPT_PATH})/\1|"; } 
Keymon
źródło
Potrzebuję cd w myreadlink (), ponieważ jest to funkcja rekurencyjna, przechodząca do każdego katalogu, aż znajdzie link. Jeśli znajdzie link, zwraca realpath, a następnie sed zastąpi ścieżkę.
Keymon,
5

Łącząc niektóre z wymienionych rozwiązań, wiedząc, że readlink jest dostępny w większości systemów, ale potrzebuje różnych argumentów, działa to dobrze dla mnie na OSX i Debianie. Nie jestem pewien co do systemów BSD. Być może warunkiem musi być [[ $OSTYPE != darwin* ]]wykluczenie -ftylko z OSX.

#!/bin/bash
MY_DIR=$( cd $(dirname $(readlink `[[ $OSTYPE == linux* ]] && echo "-f"` $0)) ; pwd -P)
echo "$MY_DIR"
hpvw
źródło
3

Oto, w jaki sposób można uzyskać rzeczywistą ścieżkę do pliku w systemie MacOS / Unix za pomocą wbudowanego skryptu Perl:

FILE=$(perl -e "use Cwd qw(abs_path); print abs_path('$0')")

Podobnie, aby uzyskać katalog pliku dowiązania symbolicznego:

DIR=$(perl -e "use Cwd qw(abs_path); use File::Basename; print dirname(abs_path('$0'))")
Igor Afanasyjew
źródło
3

Czy ścieżka jest katalogiem, czy może plikiem? Jeśli jest to katalog, to proste:

(cd "$DIR"; pwd -P)

Jeśli jednak może to być plik, to nie zadziała:

DIR=$(cd $(dirname "$FILE"); pwd -P); echo "${DIR}/$(readlink "$FILE")"

ponieważ dowiązanie symboliczne może przekształcić się w ścieżkę względną lub pełną.

W skryptach muszę znaleźć prawdziwą ścieżkę, aby móc odwoływać się do konfiguracji lub innych skryptów zainstalowanych razem z nią, używam tego:

SOURCE="${BASH_SOURCE[0]}"
while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink
  DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
  SOURCE="$(readlink "$SOURCE")"
  [[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE" # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located
done

Możesz ustawić SOURCEdowolną ścieżkę pliku. Zasadniczo, dopóki ścieżka jest dowiązaniem symbolicznym, rozwiązuje to dowiązanie symboliczne. Sztuczka znajduje się w ostatnim wierszu pętli. Jeśli rozstrzygnięte dowiązanie symboliczne jest bezwzględne, użyje go jako SOURCE. Jeśli jednak jest względny, przygotuje DIRgo, który został rozwiązany w prawdziwej lokalizacji za pomocą prostej sztuczki, którą opisałem po raz pierwszy.

Daniel C. Sobral
źródło
2
function realpath {
    local r=$1; local t=$(readlink $r)
    while [ $t ]; do
        r=$(cd $(dirname $r) && cd $(dirname $t) && pwd -P)/$(basename $t)
        t=$(readlink $r)
    done
    echo $r
}

#example usage
SCRIPT_PARENT_DIR=$(dirname $(realpath "$0"))/..
Dave
źródło
Spowoduje to zerwanie z (a) dowolną ścieżką zawierającą białe znaki lub metaznaki powłoki oraz (b) zepsute dowiązania symboliczne (nie stanowi to problemu, jeśli chcesz tylko ścieżki rodzica uruchomionego skryptu, zakładając, że (a) nie ma zastosowania).
mklement0
Jeśli martwisz się spacjami, użyj cudzysłowów.
Dave
Proszę - chociaż to nie dotyczy (b).
mklement0
Pokaż mi przykład b) w przypadku niepowodzenia. Z definicji zepsute dowiązanie symboliczne wskazuje na wpis katalogu, który nie istnieje. Celem tego skryptu jest rozwiązywanie dowiązań symbolicznych w innym kierunku. Gdyby dowiązanie symboliczne zostało zerwane, skrypt nie byłby wykonywany. Ten przykład ma na celu zademonstrowanie rozwiązania aktualnie wykonywanego skryptu.
Dave
„ma na celu zademonstrowanie rozwiązania aktualnie wykonywanego skryptu” - w istocie jest to zawężenie zakresu pytania, na którym postanowiłeś się skoncentrować; to jest w porządku, o ile tak mówisz. Ponieważ tego nie zrobiłeś, powiedziałem to w moim komentarzu. Napraw problem z cytowaniem, który jest problemem niezależnie od zakresu odpowiedzi.
mklement0
2

Uwaga: Uważam, że jest to solidne, przenośne, gotowe rozwiązanie, które z tego powodu jest niezmiennie długie .

Poniżej znajduje się w pełni zgodny ze standardem POSIX skrypt / funkcja, która jest zatem wieloplatformowa (działa również na macOS, którego readlinkwciąż nie obsługuje -fod 10.12 (Sierra)) - używa tylko funkcji języka powłoki POSIX i tylko wywołań narzędzi zgodnych z POSIX .

Jest to przenośna implementacja GNUreadlink -e (bardziej rygorystyczna wersja readlink -f).

Można uruchomić skrypt zsh lub pozyskać funkcję w bash, kshizsh :

Na przykład w skrypcie możesz użyć go w następujący sposób, aby uzyskać prawdziwy katalog pochodzenia skryptu uruchomionego, z rozwiązanymi dowiązaniami symbolicznymi:

trueScriptDir=$(dirname -- "$(rreadlink "$0")")

rreadlink definicja skryptu / funkcji:

Kod został przyjęty z wdzięcznością z tej odpowiedzi .
Ja również stworzył bashopartych samodzielną wersję użytkową tutaj , które można zainstalować z
npm install rreadlink -g, jeśli masz zainstalowany node.js.

#!/bin/sh

# SYNOPSIS
#   rreadlink <fileOrDirPath>
# DESCRIPTION
#   Resolves <fileOrDirPath> to its ultimate target, if it is a symlink, and
#   prints its canonical path. If it is not a symlink, its own canonical path
#   is printed.
#   A broken symlink causes an error that reports the non-existent target.
# LIMITATIONS
#   - Won't work with filenames with embedded newlines or filenames containing 
#     the string ' -> '.
# COMPATIBILITY
#   This is a fully POSIX-compliant implementation of what GNU readlink's
#    -e option does.
# EXAMPLE
#   In a shell script, use the following to get that script's true directory of origin:
#     trueScriptDir=$(dirname -- "$(rreadlink "$0")")
rreadlink() ( # Execute the function in a *subshell* to localize variables and the effect of `cd`.

  target=$1 fname= targetDir= CDPATH=

  # Try to make the execution environment as predictable as possible:
  # All commands below are invoked via `command`, so we must make sure that
  # `command` itself is not redefined as an alias or shell function.
  # (Note that command is too inconsistent across shells, so we don't use it.)
  # `command` is a *builtin* in bash, dash, ksh, zsh, and some platforms do not 
  # even have an external utility version of it (e.g, Ubuntu).
  # `command` bypasses aliases and shell functions and also finds builtins 
  # in bash, dash, and ksh. In zsh, option POSIX_BUILTINS must be turned on for
  # that to happen.
  { \unalias command; \unset -f command; } >/dev/null 2>&1
  [ -n "$ZSH_VERSION" ] && options[POSIX_BUILTINS]=on # make zsh find *builtins* with `command` too.

  while :; do # Resolve potential symlinks until the ultimate target is found.
      [ -L "$target" ] || [ -e "$target" ] || { command printf '%s\n' "ERROR: '$target' does not exist." >&2; return 1; }
      command cd "$(command dirname -- "$target")" # Change to target dir; necessary for correct resolution of target path.
      fname=$(command basename -- "$target") # Extract filename.
      [ "$fname" = '/' ] && fname='' # !! curiously, `basename /` returns '/'
      if [ -L "$fname" ]; then
        # Extract [next] target path, which may be defined
        # *relative* to the symlink's own directory.
        # Note: We parse `ls -l` output to find the symlink target
        #       which is the only POSIX-compliant, albeit somewhat fragile, way.
        target=$(command ls -l "$fname")
        target=${target#* -> }
        continue # Resolve [next] symlink target.
      fi
      break # Ultimate target reached.
  done
  targetDir=$(command pwd -P) # Get canonical dir. path
  # Output the ultimate target's canonical path.
  # Note that we manually resolve paths ending in /. and /.. to make sure we have a normalized path.
  if [ "$fname" = '.' ]; then
    command printf '%s\n' "${targetDir%/}"
  elif  [ "$fname" = '..' ]; then
    # Caveat: something like /var/.. will resolve to /private (assuming /var@ -> /private/var), i.e. the '..' is applied
    # AFTER canonicalization.
    command printf '%s\n' "$(command dirname -- "${targetDir}")"
  else
    command printf '%s\n' "${targetDir%/}/$fname"
  fi
)

rreadlink "$@"

Styczna w zakresie bezpieczeństwa:

jarno , w odniesieniu do funkcji zapewniającej, że wbudowana funkcja commandnie jest zaciemniona przez alias lub funkcję powłoki o tej samej nazwie, pyta w komentarzu:

Co jeśli unaliaslub unseti [są ustawione jako aliasy lub funkcji powłoki?

Motywacją do rreadlinkzapewnienia, że commandma to swoje oryginalne znaczenie, jest obejście (łagodnych) wygodnych aliasów i funkcji często używanych do cieniowania standardowych poleceń w interaktywnych powłokach, takich jak redefiniowanie w lscelu włączenia ulubionych opcji.

Myślę, że to na pewno powiedzieć, że jeśli masz do czynienia z niezaufanych, szkodliwego środowiska, martwić unaliasalbo unset- albo, jeśli o to chodzi, while, do, ... - jest nowo nie jest problemem.

Jest coś , na czym funkcja musi polegać, aby mieć swoje pierwotne znaczenie i zachowanie - nie ma na to sposobu.
To, że powłoki podobne do POSIX pozwalają na ponowne zdefiniowanie wbudowanych narzędzi, a nawet słowa kluczowe w języku, jest z natury zagrożeniem bezpieczeństwa (a pisanie kodu paranoicznego jest ogólnie trudne).

Aby w szczególności odpowiedzieć na twoje wątpliwości:

Funkcja polega unaliasi unsetma swoje oryginalne znaczenie. Ponowne zdefiniowanie ich jako funkcji powłoki w sposób, który zmienia ich zachowanie, stanowiłoby problem; redefinicja jako alias niekoniecznie stanowi problem, ponieważ cytowanie (części) nazwy polecenia (np. \unalias) pomija aliasy.

Jednak cytowanie jest nie opcja dla powłoki słów kluczowych ( while, for, if, do, ...) i gdy powłoka słowa kluczowe biorą górę nad powłoki funkcji , w bashi zshaliasy mają najwyższy priorytet, tak aby zabezpieczyć się przed tulejką kluczowe redefiniowaniem należy uruchomić unaliasz ich nazwy (chociaż w nieinteraktywnych bash powłokach (takich jak skrypty) aliasy nie są domyślnie rozwijane - tylko jeśli shopt -s expand_aliaseszostaną jawnie wywołane jako pierwsze).

Aby mieć pewność, że unalias- jako wbudowany - ma swoje oryginalne znaczenie, musisz \unsetgo najpierw użyć , co wymaga, aby unsetmiało to oryginalne znaczenie:

unsetjest wbudowaną powłoką , więc aby upewnić się, że jest wywoływana jako taka, musisz upewnić się, że sama nie zostanie ponownie zdefiniowana jako funkcja . Chociaż możesz ominąć formularz aliasu z cytowaniem, nie możesz ominąć formularza funkcji powłoki - catch 22.

Tak więc, o ile nie mogę polegać na unsetswoim pierwotnym znaczeniu, z tego co mogę powiedzieć, nie ma gwarantowanego sposobu obrony przed wszystkimi złośliwymi przedefiniowaniami.

mklement0
źródło
Z mojego doświadczenia wynika, że ​​cytowanie pomija aliasy, a nie funkcje powłoki, w przeciwieństwie do tego, co wcześniej mówisz.
jarno
Przetestowane, które można określić [jako aliasu dasha bashoraz jako powłoki w funkcji bash.
jarno
1
Swoją uwagę przedstawiłem jako osobne pytanie .
jarno
@jarno: Dobre punkty; Zaktualizowałem swoją odpowiedź; daj mi znać, jeśli uważasz, że nadal występuje problem.
mklement0
1
@jarno: Użytkownik może zdefiniować whilejako funkcję w bash, kshi zsh(nie dash), ale tylko ze function <name>składnią: function while { echo foo; }prace ( while() { echo foo; }nie). Nie będzie to jednak przesłaniać while słowa kluczowego , ponieważ słowa kluczowe mają wyższy priorytet niż funkcje (jedynym sposobem na wywołanie tej funkcji jest as \while). W bashi zshaliasy mają wyższy priorytet niż słowa kluczowe, więc alias redefinicji słów kluczowych je cienia (ale bashdomyślnie tylko w interaktywnych powłokach, chyba że shopt -s expand_aliasesjest to jawnie wywołane).
mklement0
1

Typowe skrypty powłoki często muszą znajdować katalog „domowy”, nawet jeśli są wywoływane jako dowiązanie symboliczne. Skrypt musi więc znaleźć swoją „prawdziwą” pozycję już od 0 USD.

cat `mvn`

w moim systemie wypisuje skrypt zawierający następujące elementy, które powinny być dobrą wskazówką, czego potrzebujesz.

if [ -z "$M2_HOME" ] ; then
  ## resolve links - $0 may be a link to maven's home
  PRG="$0"

  # need this for relative symlinks
  while [ -h "$PRG" ] ; do
    ls=`ls -ld "$PRG"`
    link=`expr "$ls" : '.*-> \(.*\)$'`
    if expr "$link" : '/.*' > /dev/null; then
      PRG="$link"
    else
      PRG="`dirname "$PRG"`/$link"
    fi
  done

  saveddir=`pwd`

  M2_HOME=`dirname "$PRG"`/..

  # make it fully qualified
  M2_HOME=`cd "$M2_HOME" && pwd`
Hugo
źródło
1

Spróbuj tego:

cd $(dirname $([ -L $0 ] && readlink -f $0 || echo $0))
diyizm
źródło
1

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

Żywa wersja mieszka tutaj:

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

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

Może nie będzie trudno sprawić, że będzie działać dla zwykłej powłoki bourne (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 ścieżkę -real-:

%% 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
Moja wcześniejsza odpowiedź robi dokładnie to samo na około 1/4 miejsca :) Jest to dość podstawowe skrypty powłoki i nie jest warte git repo.
Dave
1

Aby obejść niezgodność komputera Mac, wpadłem na pomysł

echo `php -r "echo realpath('foo');"`

Nie świetne, ale w różnych systemach operacyjnych

Clemens Tolboom
źródło
3
Python 2.6+ jest dostępny na większej liczbie systemów użytkowników niż php, więc python -c "from os import path; print(path.realpath('${SYMLINK_PATH}'));"prawdopodobnie miałby więcej sensu. Jednak do czasu, kiedy będziesz musiał używać Pythona ze skryptu powłoki, prawdopodobnie powinieneś po prostu użyć Pythona i zaoszczędzić sobie bólu głowy podczas wieloplatformowego skryptowania powłoki.
Jonathan Baldwin
Nie potrzebujesz nic więcej niż wbudowane sh readlink, dirname i basename.
Dave
@Dave: dirname, basename, i readlinksą zewnętrzne narzędzia , nie zapłacić Zabudowy; dirnamei basenamesą częścią POSIX,readlink nie jest.
mklement0
@ mklement0 - Masz całkowitą rację. Są dostarczane przez CoreUtils lub równoważne. Nie powinienem odwiedzać SO po 1 w nocy. Istotą mojego komentarza jest to, że ani PHP, ani żaden inny tłumacz językowy poza tym zainstalowanym w systemie podstawowym nie są wymagane. Użyłem skryptu, który podałem na tej stronie, w każdym wariancie linuksa od 1997 roku i MacOS X od 2006 roku bez błędów. OP nie poprosił o rozwiązanie POSIX. Ich specyficznym środowiskiem był Mac OS X.
Dave
@Dave: Tak, to jest możliwe, aby to zrobić z narzędzi akcji, ale jest to również ciężko to zrobić (jak wynika z niedostatków skryptu). Jeśli OS X naprawdę był w centrum uwagi, to odpowiedź jest całkowicie dobra - i znacznie prostsza - biorąc pod uwagę, że phppochodzi z OS X. Jednak nawet jeśli w treści pytania wspomniano OS X, nie jest on oznaczony jako taki i staje się jasny że ludzie na różnych platformach przychodzą tutaj, aby uzyskać odpowiedzi, dlatego warto wskazać, co jest specyficzne dla platformy / inne niż POSIX.
mklement0
1

To jest resolver symlink w Bash, który działa niezależnie od tego, czy dowiązanie jest katalogiem czy innym katalogiem:

function readlinks {(
  set -o errexit -o nounset
  declare n=0 limit=1024 link="$1"

  # If it's a directory, just skip all this.
  if cd "$link" 2>/dev/null
  then
    pwd -P
    return 0
  fi

  # Resolve until we are out of links (or recurse too deep).
  while [[ -L $link ]] && [[ $n -lt $limit ]]
  do
    cd "$(dirname -- "$link")"
    n=$((n + 1))
    link="$(readlink -- "${link##*/}")"
  done
  cd "$(dirname -- "$link")"

  if [[ $n -ge $limit ]]
  then
    echo "Recursion limit ($limit) exceeded." >&2
    return 2
  fi

  printf '%s/%s\n' "$(pwd -P)" "${link##*/}"
)}

Zauważ, że wszystkie cdi setrzeczy odbywa się w podpowłoce.

solidsnack
źródło
W rzeczywistości {} wokół () są niepotrzebne, ponieważ () liczy się jako „polecenie złożone”, podobnie jak {}. Ale nadal potrzebujesz () po nazwie funkcji.
Chris Cogdon,
@ChrisCogdon {}Wokół ()nie są niepotrzebne, jeśli nie ma ()nazwy za nazwą funkcji. Bash akceptuje deklaracje funkcji bez, ()ponieważ powłoka nie ma list parametrów w deklaracjach i nie wykonuje wywołań, ()więc deklaracje funkcji ()nie mają większego sensu.
solidsnack
0

Przedstawiam tutaj rozwiązanie, które moim zdaniem jest rozwiązaniem wieloplatformowym (przynajmniej Linux i macOS), które obecnie działa dla mnie dobrze.

crosspath()
{
    local ref="$1"
    if [ -x "$(which realpath)" ]; then
        path="$(realpath "$ref")"
    else
        path="$(readlink -f "$ref" 2> /dev/null)"
        if [ $? -gt 0 ]; then
            if [ -x "$(which readlink)" ]; then
                if [ ! -z "$(readlink "$ref")" ]; then
                    ref="$(readlink "$ref")"
                fi
            else
                echo "realpath and readlink not available. The following may not be the final path." 1>&2
            fi
            if [ -d "$ref" ]; then
                path="$(cd "$ref"; pwd -P)"
            else
                path="$(cd $(dirname "$ref"); pwd -P)/$(basename "$ref")"
            fi
        fi
    fi
    echo "$path"
}

Oto rozwiązanie macOS (tylko?). Być może lepiej pasuje do pierwotnego pytania.

mac_realpath()
{
    local ref="$1"
    if [[ ! -z "$(readlink "$ref")" ]]; then
        ref="$(readlink "$1")"
    fi
    if [[ -d "$ref" ]]; then
        echo "$(cd "$ref"; pwd -P)"
    else
        echo "$(cd $(dirname "$ref"); pwd -P)/$(basename "$ref")"
    fi
}
RuneImp
źródło
0

Moja odpowiedź tutaj Bash: jak uzyskać prawdziwą ścieżkę dowiązania symbolicznego?

ale w skrócie bardzo przydatny w skryptach:

script_home=$( dirname $(realpath "$0") )
echo Original script home: $script_home

Są one częścią jąder GNU, odpowiednich do użycia w systemach Linux.

Aby wszystko przetestować, umieszczamy dowiązanie symboliczne w / home / test2 /, poprawiamy kilka dodatkowych rzeczy i uruchamiamy / wywołujemy go z katalogu głównego:

/$ /home/test2/symlink
/home/test
Original script home: /home/test

Gdzie

Original script is: /home/test/realscript.sh
Called script is: /home/test2/symlink
Arunas Bartisius
źródło
0

W przypadku, gdy nie można użyć pwd (np. Wywoływania skryptów z innej lokalizacji), użyj realpath (z lub bez nazwy katalogu):

$(dirname $(realpath $PATH_TO_BE_RESOLVED))

Działa zarówno podczas wywoływania przez (wiele) dowiązań symbolicznych, jak i podczas bezpośredniego wywoływania skryptu - z dowolnej lokalizacji.

nakwa
źródło