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


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.