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