Как разрешить символические ссылки в сценарии оболочки

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

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

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

11.08.2008 10:40:41
19 ОТВЕТОВ
РЕШЕНИЕ

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

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

getcwd pwd

91
21.10.2015 18:41:11
Это просто работает для (текущего) каталога? Если целью является файл, он ничего не даст ...
dala 28.04.2010 09:18:31
Не работает, если ссылка не работает, поскольку вы не можете сделать ее своим текущим путем
Tom Howard 16.11.2011 20:17:47
Подведем итоги с точки зрения ретроспективности: этот ответ работает только в очень ограниченных обстоятельствах, а именно, если символическая ссылка представляет интерес для каталога, который действительно существует ; Кроме того, вы должны cdсначала до звонка pwd -P. Другими словами: он не позволит вам разрешить (см. Цель) символические ссылки на файлы или поврежденные символические ссылки, а для разрешения символических ссылок существующего каталога вам придется проделать дополнительную работу (восстановить предыдущий рабочий каталог или локализовать вызовы cdand pwd -Pв скорлупе).
mklement0 22.10.2015 20:56:55
плохо я ищу способ разрешить файл, а не каталог.
Martin 11.01.2019 09:37:28
Как уже отмечали другие, это на самом деле не отвечает на вопрос. @ pixelbeat ответ ниже делает.
erstaples 19.02.2019 20:53:24

Обычные сценарии оболочки часто должны находить свой «домашний» каталог, даже если они вызываются как символическая ссылка. Таким образом, скрипт должен найти свою «реальную» позицию всего за $ 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`
1
11.08.2008 11:17:23
readlink -f "$path"

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

Обратите внимание, что начиная с GNU coreutils 8.15 (2012-01-06), существует программа realpath, которая менее тупая и более гибкая, чем описанная выше. Он также совместим с утилитой FreeBSD с тем же именем. Он также включает в себя функциональность для генерации относительного пути между двумя файлами.

realpath $path

[Добавление администратора ниже из комментария от halloleo - danorton]

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

readlink $path

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

392
22.10.2015 22:46:51
Это не работает в Mac OS X - см stackoverflow.com/questions/1055671/...
Bkkbrad 23.08.2009 20:19:14
позор о несовместимости OS X, иначе приятно +1
jkp 29.11.2011 08:16:32
На OS X вы можете установить coreutils с homebrew. Он устанавливает его как "grealpath".
Kief 18.10.2012 14:39:01
readlinkработает на OSX, но нуждается в другой синтаксис: readlink $path без-f .
halloleo 28.01.2013 21:00:04
readlink не может разыменовать несколько слоев символической ссылки, но просто разыменовывает один слой за раз
Magnus 26.06.2013 15:14:24

Один из моих любимых realpath foo

realpath - возвращает канонизированный абсолютный путь

realpath раскрывает все символические ссылки и разрешает ссылки на символы «/./», «/../» и дополнительные символы «/» в строке с нулевым символом в конце, названной path и
       сохраняет канонизированный абсолютный путь в буфере размером PATH_MAX, названном resolved_path. Полученный путь не будет иметь символической ссылки, «/ ./» или
       '/../' составные части.
17
4.12.2008 23:39:05
В Debian (etch и позже) эта команда доступна в пакете realpath.
Phil Ross 25.06.2009 11:39:00
realpath теперь (январь 2012 г.) входит в состав coreutils и обратно совместим с вариантом Debian и BSD
pixelbeat 16.04.2012 07:43:41
У меня нет realpathна Centos 6 с GNU coreutils 8.4.31. Я сталкивался с несколькими другими в Unix и Linux , у которых GNU coreutils упакован без realpath . Так что, похоже, это зависит не только от версии.
toxalot 18.03.2014 22:43:03
Я предпочитаю realpathболее , readlinkпоскольку он предлагает опции , такие как флаги--relative-to
arr_sea 12.12.2018 19:05:44

«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
26
18.08.2016 13:24:01

Другой путь:

# 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|"; } 
5
13.09.2011 10:52:44
Мне нужен cd в myreadlink (), потому что это рекурсивная функция, идущая в каждый каталог, пока не найдет ссылку. Если он найдет ссылку, вернет реальный путь, а затем sed заменит путь.
Keymon 19.10.2017 21:03:42
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
10.04.2014 14:52:57
Это приведет к разрыву с (a) любым путем, содержащим метасимволы пробелов или оболочки и (b) прерванными символическими ссылками (это не проблема, если вы просто хотите, чтобы родительский путь выполняемого сценария предполагал (a) не применяется).
mklement0 22.10.2015 23:33:30
Если вас интересуют пробелы, используйте кавычки.
Dave 24.10.2015 03:40:41
Пожалуйста, сделайте - хотя это не будет адреса (б).
mklement0 24.10.2015 03:43:43
Пожалуйста, покажите мне пример б), где это не удается. По определению, неработающая символическая ссылка указывает на несуществующую запись каталога. Смысл этого скрипта - разрешить символические ссылки в другом направлении. Если бы символическая ссылка была сломана, вы бы не выполняли скрипт. Этот пример предназначен для демонстрации разрешения выполняемого в данный момент скрипта.
Dave 24.10.2015 03:46:59
«предназначенный для демонстрации разрешения выполняемого в данный момент сценария» - действительно, это сужение области вопроса, на которой вы решили сосредоточиться; это прекрасно, если вы так говорите. Поскольку вы этого не сделали, я изложил это в своем комментарии. Пожалуйста, исправьте проблему цитирования, которая является проблемой независимо от объема ответа.
mklement0 24.10.2015 03:54:04

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

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

Не отлично, но кросс-ОС

1
21.10.2015 18:43:19
Python 2.6+ доступен в большем количестве систем для конечных пользователей, чем php, так python -c "from os import path; print(path.realpath('${SYMLINK_PATH}'));"что, вероятно, имеет больше смысла. Тем не менее, к тому времени, когда вам нужно использовать Python из сценария оболочки, вам, вероятно, следует просто использовать Python и избавить себя от головной боли при межплатформенных сценариях оболочки.
Jonathan Baldwin 27.03.2015 21:31:05
Вам не нужно ничего, кроме встроенной ссылки sh readlink, dirname и basename.
Dave 30.09.2015 00:53:54
@ Dave: dirname, basenameи readlinkвнешние утилиты , а не обстреливать встроенные модули; dirnameи basenameявляются частью POSIX, readlinkнет.
mklement0 21.10.2015 18:55:16
@ mklement0 - Вы совершенно правы. Они предоставляются CoreUtils или эквивалентными. Я не должен посещать ТАК после 1 часа ночи. Суть моего комментария в том, что не требуется ни PHP, ни какой-либо другой интерпретатор языка, кроме того, который установлен в базовой системе. Я использовал скрипт, представленный на этой странице, во всех вариантах Linux с 1997 года и MacOS X с 2006 года без ошибок. ОП не попросил решения POSIX. Их специфической средой была Mac OS X.
Dave 24.10.2015 03:39:11
@ Dave: Да, это возможно сделать с фондовыми коммунальными услугами, но это также трудно сделать ( о чем свидетельствуют недостатки вашего скрипта). Если OS X действительно были в центре внимания, то этот ответ является совершенно нормально - и гораздо проще - учитывая то , что phpприходит с OS X. Однако, несмотря на то, тело вопроса упоминает OS X, не помечены как таковые, и это стало ясно , что люди на разных платформах приходят сюда за ответами, поэтому стоит указать, что конкретно для платформы / не POSIX.
mklement0 24.10.2015 04:04:31

Попробуй это:

cd $(dirname $([ -L $0 ] && readlink -f $0 || echo $0))
1
29.08.2014 03:05:19

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

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

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

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

Может быть нетрудно заставить его работать на простой оболочке 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
}

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

%% 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

используйте эту функцию, и она вернет -real- path:

%% 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 
1
24.12.2014 21:06:54
Мой предыдущий ответ делает то же самое примерно в 1/4 пространства :) Это довольно простой сценарий оболочки и не достоин git-репо.
Dave 30.09.2015 00:51:21
readlink -e [filepath]

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

10
9.07.2015 14:34:38
Просто укушен этим. Это не работает на Mac, и я ищу замену.
dhill 11.04.2018 13:18:06

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

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

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

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

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

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

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

Код был адаптирован с благодарностью от этого ответа .
Я также создал здесьbash версию автономной утилиты на основе , которую вы можете установить , если у вас установлен Node.js.
npm install rreadlink -g

#!/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 "$@"

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

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чтобы иметь его первоначальное значение, насколько я могу судить, нет гарантированного способа защиты от всех злонамеренных переопределений.

2
23.05.2017 12:03:05
По моему опыту, кавычки обходят псевдонимы, а не функции оболочки, в отличие от того, что вы сначала сказали.
jarno 10.03.2016 15:11:32
Я проверил, что я могу определить [как псевдоним в dashи bashи как функцию оболочки в bash.
jarno 10.03.2016 15:19:31
Я высказал свою мысль как отдельный вопрос .
jarno 10.03.2016 15:23:41
@jarno: хорошие моменты; Я обновил свой ответ; дайте мне знать, если вы думаете, что есть проблема.
mklement0 10.03.2016 15:25:37
@jarno: Вы можете определить whileкак функцию в bash, kshи zsh(но не dash), но только с помощью function <name>синтаксиса: function while { echo foo; }работает ( while() { echo foo; }не). Однако это не затеняет while ключевое слово , потому что ключевые слова имеют более высокий приоритет, чем функции (единственный способ вызвать эту функцию - as \while). В bashи zshпсевдонимы имеют более высокий приоритет, чем ключевые слова, поэтому переопределения псевдонимов ключевых слов затеняют их (но bashпо умолчанию только в интерактивных оболочках, если shopt -s expand_aliasesявно не вызывается).
mklement0 11.03.2016 03:38:24

Собирая вместе некоторые из приведенных решений, зная, что 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"
5
14.11.2016 07:47:17

Я считаю, что это верный и окончательный «способ разрешения символической ссылки», будь то каталог или не каталог, используя 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
23.08.2017 00:49:39
На самом деле, {} вокруг () не нужны, поскольку () считается «составной командой», как и {}. Но вам все еще нужно () после имени функции.
Chris Cogdon 22.11.2017 22:39:26

Вот как можно получить фактический путь к файлу в 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'))")
3
30.01.2018 03:57:48

Ваш путь - это каталог, или это может быть файл? Если это каталог, все просто:

(cd "$DIR"; pwd -P)

Однако, если это может быть файл, то это не будет работать:

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

потому что символическая ссылка может преобразоваться в относительный или полный путь.

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

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

Вы можете установить SOURCEлюбой путь к файлу. По сути, пока путь является символической ссылкой, он разрешает эту символическую ссылку. Хитрость в последней строке цикла. Если разрешенная символическая ссылка является абсолютной, она будет использовать это как SOURCE. Однако, если он является относительным, он будет предшествовать DIRдля него, который был преобразован в реальное местоположение с помощью простого трюка, который я впервые описал.

3
28.06.2018 18:00:47

Здесь я представляю то, что я считаю кроссплатформенным (по крайней мере, Linux и macOS) решением для ответа, который в настоящее время хорошо работает для меня.

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"
}

Вот решение для macOS (только?). Возможно, лучше подходит для первоначального вопроса.

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
}
0
14.01.2019 10:32:08

Мой ответ здесь Bash: как получить реальный путь символической ссылки?

но вкратце очень удобно в скриптах:

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

Они являются частью GNU coreutils и подходят для использования в системах Linux.

Чтобы проверить все, мы помещаем символическую ссылку в / home / test2 /, исправляем некоторые дополнительные вещи и запускаем / вызываем ее из корневого каталога:

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

где

Original script is: /home/test/realscript.sh
Called script is: /home/test2/symlink
0
3.09.2019 14:01:43

В случае, когда pwd не может использоваться (например, вызывать скрипты из другого места), используйте realpath (с или без dirname):

$(dirname $(realpath $PATH_TO_BE_RESOLVED))

Работает как при вызове через (несколько) символьных ссылок, так и при непосредственном вызове скрипта - из любого места.

0
25.02.2020 13:20:39