2008-08-11 12 views
175

Учитывая абсолютный или относительный путь (в Unix-подобной системе), я хотел бы определить полный путь к цели после разрешения любых промежуточных символических ссылок. Бонусные баллы за одновременное разрешение ~ имени пользователя.Как разрешить символические ссылки в сценарии оболочки

Если цель является каталогом, возможно, в chdir() можно будет ввести каталог, а затем вызвать getcwd(), но я действительно хочу сделать это из сценария оболочки вместо написания помощника C. К сожалению, снаряды имеют тенденцию пытаться скрыть существование симлинок от пользователя (это баш на 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 
$ 

То, что я хочу, это функция решительность() таким образом, чтобы при выполнении из каталога TMP в приведенном выше примере, разрешите ("foo") == "/ Users/greg/tmp/bar".

ответ

79

В соответствии со стандартами, pwd -P должен возвращать путь с разрешенными символическими ссылками.

Функция C char *getcwd(char *buf, size_t size) от unistd.h должна иметь такое же поведение.

getcwd pwd

+13

Это просто работает для (текущего) каталога? Если целью является файл, он ничего не даст ... – dala 2010-04-28 09:18:31

+2

Не работает, если ссылка сломана, поскольку вы не можете сделать ее текущим путем. – 2011-11-16 20:17:47

+3

Подводя итог с учетом ретроспективного анализа: этот ответ работает только в очень ограниченных обстоятельствах , а именно, если символическая ссылка представляет собой _directory_, которая фактически существует _; плюс, вы должны сначала «cd», прежде чем вызывать `pwd -P`. Другими словами: он не позволит вам разрешать (видеть цель) символические ссылки _to files_ или символические ссылки _broken_, а для устранения символических ссылок существующего каталога вам нужно выполнить дополнительную работу (восстановить предыдущий рабочий каталог или локализовать ` cd` и `pwd -P` вызывает в подоболочке). – mklement0 2015-10-22 20:56:55

1

скриптов Common оболочки часто должны найти свой «домашний» каталог, даже если они вызываются как линк. Таким образом, сценарий должен найти свою «реальную» позицию всего за $ 0.

cat `mvn` 
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` 
334
readlink -f "$path" 

Примечание редактора: Вышеуказанные работы с GNUreadlink и FreeBSD/PC-BSD/OpenBSDreadlink, но не на OS X от 10.11.
GNUreadlink предлагает дополнительные, связанные с ними варианты, такие как -m для разрешения символической ссылки, существует ли конечная цель.

Примечание так ГНУ Coreutils 8,15 (2012-01-06), есть Realpath программа доступна, что является менее тупым и более гибким, чем выше. Он также совместим с тем же именем, что и FreeBSD. Он также включает функции для создания относительного пути между двумя файлами.

realpath $path 

[Администратор дополнение ниже от комментариев по halloleo - danorton]

Для Mac OS X (по крайней мере, 10.11.x), используйте readlink без -f опции:

readlink $path 

Примечание редактора: Это не решит символические ссылки рекурсивно и, таким образом, не сообщит цель конечной цели; например, учитывая symlink a, который указывает на b, что, в свою очередь, указывает на c, это сообщит только b (и не будет гарантировать, что он будет выводиться как абсолютный путь ).
Используйте следующую perl команда на OS X, чтобы заполнить пробел отсутствующего readlink -f функциональность:
perl -MCwd -le 'print Cwd::abs_path(shift)' "$path"

+0

потрясающий, именно то, что мне нужно! – rampion 2009-07-17 18:44:03

+5

Это не работает в Mac OS X - см. Http://stackoverflow.com/questions/1055671/how-can-i-get-the-behavior-of-gnus-readlink-f-on-a-mac – Bkkbrad 2009-08-23 20:19:14

+1

стыд о несовместимости OS X, в противном случае хороший +1 – jkp 2011-11-29 08:16:32

11

Один из моих любимых является realpath foo

 
realpath - return the canonicalized absolute pathname 

realpath expands all symbolic links and resolves references to '/./', '/../' and extra '/' characters in the null terminated string named by path and 
     stores the canonicalized absolute pathname in the buffer of size PATH_MAX named by resolved_path. The resulting path will have no symbolic link, '/./' or 
     '/../' components. 
19

"PWD -P", кажется, работает, если вам просто нужен каталог, но если по какой-то причине вам нужно имя фактического исполняемого файла, я не думаю, что это помогает. Вот мое решение:

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

Другой способ:

# 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|"; } 
1
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"))/.. 
1

Чтобы обойти Mac несовместимости, я придумал

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

Не большой, но кросс OS

1

Попробуйте следующее:

cd $(dirname $([ -L $0 ] && readlink -f $0 || echo $0)) 
1

Поскольку я столкнулся с этим много раз на протяжении многих лет, и в этот раз мне нужен чистый Баш портативную версию, которую я мог бы использовать на OSX и Linux, я пошел вперед и написал одно:

Жилая версия живет здесь:

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

но ради SO, вот текущая версия (я чувствую, что это хорошо tested..but Я открыт для обратной связи!)

Не может быть трудно заставить его работать для простой оболочки bourne (sh), но я не пробовал ... Мне очень нравится $ 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 
} 

вот классический пример, благодаря заварить:

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

использовать эту функцию, и она будет возвращать -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 
9
readlink -e [filepath] 

, кажется, точно то, что вы просите - он принимает путь арбирарии, разрешает все символические ссылки и возвращает «реальный» путь - и это «стандарт * Никс», что, вероятно, все системы уже

2

Примечания: Я считаю, что это быть твердым, портативным, готовым решением, которое неизменно длительного по той же причине.

Ниже полностью POSIX-совместимый скрипт/функция что поэтому кросс-платформенный (работает на MacOS тоже, чьи readlink еще не поддерживает -f по состоянию на 10.12 (Sierra)) - он использует только POSIX shell language features и только вызовы утилиты, совместимые с POSIX.

Это переносимая реализация readlink -e (более строгой версии readlink -f) ГНУ.

Вы можете запустить сценарий с sh или источника, функции в bash, ksh и zsh:

Например, внутри сценария вы можете использовать его как следует, чтобы получить сценарий запуска подлинного каталога происхождения, с символическими ссылками:

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

rreadlink сценарий определение/функция:

код был адаптирован с благодарностью от this answer.
Я также создал автономную служебную версию bashhere, которую вы можете установить с помощью
npm install rreadlink -g, если у вас установлен 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 /[email protected] -> /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 "[email protected]" 

Касательная на безопасность:

jarno, по отношению к функции, обеспечивающей, что встроенные command не затененное псевдоним или оболочки функции с тем же именем, спрашивает в комментарий:

Что делать, если unalias или unset и [ заданы как псевдонимы или функции оболочки?

Мотивация rreadlink обеспечения того, чтобы command имеет свой первоначальный смысл использовать его для обхода (доброкачественного) удобства псевдонимов и функция часто используются для теневыми стандартные команды в интерактивных оболочках, такие как переопределение ls включить любимые варианты ,

Я думаю, что можно с уверенностью сказать, что если вы имеете дело с ненадежной, злонамеренной средой, заботясь о unalias или unset - или, если на то пошли, while, do ... - пересматриваются не является проблемой.

Существует что-то что функция должна полагаться на свое первоначальное значение и поведение - нет никакого способа обойти это.
Эта оболочка, подобная POSIX, позволяет переопределять встроенные и даже языковые ключевые слова: по существу риск для безопасности (и писать параноидальный код в целом сложно).

Для решения ваших проблем именно:

Функция основана на unalias и unset, имеющие свое первоначальное значение. Если их переопределить как функции оболочки таким образом, что они изменят их поведение, это будет проблемой; переопределение как псевдоним - не обязательно вызывает беспокойство, поскольку , цитируя (часть), имя команды (например, \unalias) обходит псевдонимы.

Однако, цитируя это не вариант для оболочки ключевых слов (while, for, if, do, ...) и в то время как ключевые слова оболочки действительно имеют приоритет над оболочкой функции, в bash и zsh псевдонимами есть самый высокий приоритет, поэтому для защиты от переопределений оболочки-ключевого слова вы должны запустить unalias с их именами (хотя в неинтерактивнымиbash алиасы (например, скрипты): не расширено по умолчанию - только если shopt -s expand_aliases явно вызывается первым).

Для того, чтобы unalias - как встроенный - имеет свое первоначальное значение, вы должны использовать \unset на него первым, который требует, чтобы unset иметь свое первоначальное значение:

unset является оболочка встроенной, с тем чтобы обеспечить что он вызывается как таковой, вам нужно убедиться, что он сам не переопределяется как функция . Хотя вы можете обойти форму псевдонима с цитированием, вы не можете обойти форму-формулу уловки 22.

Таким образом, если вы не можете положиться на unset, чтобы иметь свое первоначальное значение, из того, что я могу сказать, нет гарантированного способ защиты от всех вредоносных переопределений.

5

Объединяя некоторые из данных решений, зная, что readlink доступен для большинства систем, но для этого нужны разные аргументы, это хорошо работает для меня на OSX и Debian. Я не уверен в BSD-системах. Возможно, условие должно быть [[ $OSTYPE != darwin* ]], чтобы исключить -f только из OSX.

#!/bin/bash 
MY_DIR=$(cd $(dirname $(readlink `[[ $OSTYPE == linux* ]] && echo "-f"` $0)) ; pwd -P) 
echo "$MY_DIR" 
0

Я считаю, что это правда, и окончательное «способ решить симлинк», является ли он каталогом или не-каталог, с помощью Bash:

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 "$link" 
    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##*/}" 
)} 

Обратите внимание, что все cd и set материал происходит в подоболочке.

0

Вот как можно получить реальный путь к файлу в MacOS/Unix, используя встроенный Perl скрипт:

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

Аналогично, чтобы получить каталог с символическими ссылками файла:

DIR=$(perl -e "use Cwd qw(abs_path); use File::Basename; print dirname(abs_path('$0'))") 

 Смежные вопросы

  • Нет связанных вопросов^_^