Як вирішити символічні посилання в сценарії оболонки


220

Враховуючи абсолютний або відносний шлях (у 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
$

Мені хочеться - це функція resol () така, що при виконанні з каталогу tmp у наведеному вище прикладі, разрешить ("foo") == "/ Users / greg / tmp / bar".

Відповіді:


92

Відповідно до стандартів, pwd -Pслід повернути шлях із вирішеними символьними посиланнями.

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

getcwd pwd


22
Це просто працює для (поточного) каталогу? Якщо ціль - файл, він нічого не дасть ...
дала

4
Не працює, якщо посилання розірвано, оскільки ви не зможете зробити його своїм поточним шляхом
Том Говард

6
Підводячи підсумок із користю заднього огляду: ця відповідь працює лише в дуже обмежених обставинах, а саме, якщо символьне посилання, що цікавить, стосується фактично існуючого каталогу ; Плюс до цього потрібно спочатку, перш ніж дзвонити . Іншими словами: це не дозволить вам вирішити (див. Ціль) символьних посилань на файли або розбиті символьні посилання, а для вирішення існуючих символьних посилань вам доведеться виконати додаткову роботу (відновити попередній робочий dir або локалізувати і виклики в нижній частині). cdpwd -Pcdpwd -P
mklement0

погано Я шукаю спосіб вирішити файл, а не каталог.
Мартін

Як зазначали інші, це насправді не відповідає на питання. @ pixelbeat відповідь нижче.
помилки

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


5
Це не працює в Mac OS X - см stackoverflow.com/questions/1055671 / ...
Bkkbrad

1
ганьба щодо несумісності з OS X, інакше приємно +1
jkp

11
На OS X ви можете встановити coreutils з домашньою мовою. Він встановлює його як "grealpath".
Кіеф

10
readlinkпрацює на OSX, але потребує в інший синтаксис: readlink $path без-f .
halloleo

2
readlink не вдається перенаправити декілька шарів символьної посилання, однак вона просто знеструмлює один шар за часом
Магнус,

26

"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

17

Один з моїх улюблених - це realpath foo

realpath - повернути канонізовану абсолютну назву шляху

realpath розширює всі символьні посилання та вирішує посилання на символи '/./', '/../' та додаткові '/' у нульовому завершеному рядку, названому шляхом та
       зберігає канонізовану абсолютну назву шляху у буфері розміру PATH_MAX, іменованим резолюцією_path. Отриманий шлях не матиме символічного зв’язку "/./" або
       '/../' компоненти.

У Debian (etch та пізніших версіях) ця команда доступна в пакеті realpath.
Філ Росс

2
realpath зараз (січень 2012) є частиною coreutils та назад, сумісних із варіантом debian та BSD
pixelbeat

1
У мене немає realpathна Centos 6 з GNU coreutils 8.4.31. Я натрапив на декілька інших в Unix & Linux, які мають ядро GNU без упаковки realpath. Тож, здається, це залежить від більш ніж просто версії.
токсалот

Я вважаю realpathза краще, readlinkтому що він пропонує прапорці опцій, такі як--relative-to
arr_sea

10
readlink -e [filepath]

здається, саме те, про що ви просите - він приймає довільний шлях, вирішує всі символьні посилання та повертає "реальний" шлях - і це "стандартний * nix", який, ймовірно, у всіх систем вже є


Щойно її покусав. Він не працює на Mac, і я шукаю заміну.
дефіле

5

Інший спосіб:

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

Мені потрібен CD в myreadlink (), оскільки це рекурсивна функція, переходячи до кожного каталогу, поки він не знайде посилання. Якщо він знайде посилання, повертає realpath і тоді sed замінить шлях.
Кеймон

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"

3

Ось як можна отримати фактичний шлях до файлу в 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

Ваш шлях - це каталог, чи це може бути файл? Якщо це каталог, це просто:

(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нього справжнє місце за допомогою простого трюку, який я вперше описав.


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"))/..

Це порушить (a) будь-який шлях, що містить пробіли білого простору або оболонки та (b) порушені символьні посилання (не проблема, якщо ви просто хочете, щоб батьківський шлях запущеного сценарію, якщо (а) не застосовується).
mklement0

Якщо вас турбує пробіл, використовуйте цитати.
Дейв

Будь ласка, - хоча це не буде адресою (b).
mklement0

Покажіть, будь ласка, приклад б) де це не вдається. За визначенням, ламане символьне посилання вказує на запис каталогу, який не існує. Сенс цього сценарію полягає у вирішенні символьних посилань в іншому напрямку. Якби симпосилання було порушено, сценарій не був би виконаний. Цей приклад призначений для демонстрації вирішення поточного сценарію виконання.
Дейв

"призначений для демонстрації вирішення поточного сценарію виконання" - справді, це звуження сфери питання, на яке ви обрали акцент; це абсолютно добре, доки ви так говорите. Оскільки ви цього не зробили, я це заявив у своєму коментарі. Виправте питання цитування, яке є проблемою незалежно від обсягу відповіді.
mklement0

2

Примітка. Я вважаю, що це міцне, портативне готове рішення, яке незмінно є тривалим саме з цієї причини.

Нижче представлений повністю сумісний з POSIX сценарій / функція, яка є кросплатформою (працює і на macOS, який readlinkдосі не підтримує -fстаном на 10.12 (Sierra)) - він використовує лише функції мови оболонки POSIX та лише виклики утиліти, сумісні з POSIX .

Це портативна реалізація GNUreadlink -e (більш сувора версія readlink -f).

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

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

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

rreadlink визначення сценарію / функції:

Код був адаптований з вдячністю з цієї відповіді .
Я також створив bash-А автономну версію утиліти тут , яку можна встановити з
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 /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це вбудована оболонка , тому для забезпечення її виклику як такої, вам слід переконатися, що вона сама не переосмислена як функція . Хоча ви можете обходити форму псевдоніму з цитуванням, ви не можете обійти форму функції оболонки - catch 22.

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


На мій досвід, цитування обходить псевдоніми, а не функції оболонки, на відміну від того, що ви спочатку скажете.
jarno

Я перевірив , що я можу визначити в [якості псевдоніма в dashі bashй як функція оболонки в bash.
jarno

1
Я висловив свою думку як окреме питання .
jarno

@jarno: Хороші бали; Я оновив свою відповідь; дайте мені знати, якщо ви думаєте, що проблема все ще існує.
mklement0

1
@jarno: Ви можете визначити whileяк функцію в bash, kshі zsh(але не dash), але тільки за допомогою function <name>синтаксису: function while { echo foo; }працює ( while() { echo foo; }не). Однак це не затінює while ключове слово , оскільки ключові слова мають перевагу над функціями (єдиний спосіб викликати цю функцію - це як \while). Псевдоніми в bashі zshпсевдонімах мають вищий пріоритет, ніж ключові слова, тому перейменування ключових слів псевдонімів затінює їх (але bashза замовчуванням лише в інтерактивних оболонках, якщо shopt -s expand_aliasesявно не називається).
mklement0

1

Звичайним сценаріям оболонок часто доводиться знаходити свій "домашній" каталог, навіть якщо вони викликаються як посилання. Таким чином, сценарій повинен знайти своє "реальне" положення лише від 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

Оскільки я протягом багатьох років натрапляв на це, і цього разу мені потрібна була чиста bash-портативна версія, яку я міг би використовувати в OSX та Linux, я пішов наперед і написав:

Тут живе живе версія:

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

але ради ТАК, ось поточна версія (я відчуваю, що це добре перевірено .. але я відкритий для зворотного зв'язку!)

Можливо, це не складе труднощів для того, щоб це працювало для звичайної оболонки 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/mvn@ -> ../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 

Моя попередня відповідь робить точно те саме, що займає приблизно 1/4 місця :) Це досить базовий сценарій оболонки і не вартий git repo.
Дейв

1

Щоб подолати сумісність з Mac, я придумав

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

Не чудово, але перехресна ОС


3
Python 2.6+ доступний у більшості систем кінцевих користувачів, ніж php, тому python -c "from os import path; print(path.realpath('${SYMLINK_PATH}'));", мабуть, має більше сенсу. Тим не менш, до моменту, коли вам потрібно використовувати Python із сценарію оболонки, ви, ймовірно, просто повинні використовувати Python і врятувати себе від головного болю від сценаріїв оболонки крос-платформи.
Джонатан Болдуін

Вам не потрібно більше, ніж sh-вбудовані readlink, ім’я dirname та базове ім'я.
Дейв

@ Dave: dirname, basenameі readlinkзовнішні утиліти , а НЕ обстрілювати вбудовані модулі; dirnameі basenameє частиною POSIX, readlinkні.
mklement0

@ mklement0 - Ви абсолютно праві. Вони надаються CoreUtils або аналогічні. Я не повинен відвідувати SO після 1 ранку. Суть мого коментаря полягає в тому, що ні PHP, ні будь-який інший перекладач мови, крім встановленого в базовій системі, не потрібні. Я використовував сценарій, який я надав на цій сторінці, для кожного варіанту Linux з 1997 року та MacOS X з 2006 року без помилок. ОП не вимагало рішення POSIX. Їх конкретним середовищем був Mac OS X.
Дейв

@ Dave: Так, це можливо зробити з фондовими комунальними послугами, але це також важко зробити ( про що свідчать недоліки вашого скрипта). Якщо OS X справді було в центрі уваги, то ця відповідь є цілком чудовою - і набагато простішою - враховуючи, що phpйдеться про OS X. Однак, хоча тіло питання згадує OS X, воно не позначене як таке, і це стає зрозумілим. що люди на різних платформах приходять сюди для отримання відповідей, тому варто вказати, що саме для платформи / не для POSIX.
mklement0

1

Це розв'язувач символьних посилань на 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
    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інше відбувається у передпласті.


Насправді, {} навколо () є непотрібними, оскільки () вважається "складною командою", як і {}. Але вам все одно потрібно () після назви функції.
Кріс Когдон

@ChrisCogdon {}навколо ()НЕ не потрібен , якщо їсти не ()за ім'ям функції. Bash приймає декларації функцій без, ()тому що в оболонці немає списків параметрів у деклараціях і не робить дзвінків, ()тому декларації функцій ()не мають великого сенсу.
твердий сніг

0

Тут я представляю те, що, на мою думку, є кросплатформенним (принаймні 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

Моя відповідь тут Bash: як отримати реальний шлях симпосилання?

але коротше, дуже зручно в сценаріях:

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

Вони є частиною GNU coreutils, придатною для використання в системах Linux.

Щоб протестувати все, ми ставимо symlink в / 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

У випадку, коли pwd не можна використовувати (наприклад, виклик сценаріїв з іншого місця), використовуйте realpath (з dirname або без нього):

$(dirname $(realpath $PATH_TO_BE_RESOLVED))

Працює як при виклику через (кілька) символьних посилань, так і при безпосередньому виклику сценарію - з будь-якого місця.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.