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