Nota: acredito que seja uma solução sólida, portátil e pronta para uso, que é invariavelmente demorada por esse motivo.
Abaixo está um script / função totalmente compatível com POSIX que, portanto, é multiplataforma (funciona também no macOS, que readlink
ainda não suporta a -f
partir da versão 10.12 (Sierra)) - ele usa apenas recursos da linguagem shell POSIX e apenas chamadas de utilitário compatíveis com POSIX .
É uma implementação portátil do GNUreadlink -e
(a versão mais rígida do readlink -f
).
Você pode executar o script comsh
ou fonte a função em bash
, ksh
ezsh
:
Por exemplo, dentro de um script, você pode usá-lo da seguinte maneira para obter o diretório de origem verdadeiro do script em execução, com os links simbólicos resolvidos:
trueScriptDir=$(dirname -- "$(rreadlink "$0")")
rreadlink
definição de script / função:
O código foi adaptado com gratidão a partir desta resposta .
Eu também criei uma bash
versão de utilitário independente baseada em aqui , com a qual você pode instalar
npm install rreadlink -g
, se o Node.js. estiver instalado.
#!/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 "$@"
Uma tangente à segurança:
jarno , em referência à função que garante que o builtin command
não seja sombreado por uma função de apelido ou shell com o mesmo nome, pergunta em um comentário:
E se unalias
ou unset
e [
são definidos como aliases ou funções de shell?
A motivação por trás de rreadlink
garantir quecommand
tem seu significado original é usá-lo para ignorar aliases de conveniência (benignos) e funções frequentemente usadas para sombrear comandos padrão em shells interativos, como redefinir ls
para incluir opções favoritas.
Eu acho que é seguro dizer que, a menos que você esteja lidando com um ambiente malicioso não confiável, se preocupe com unalias
ou unset
- ou, para essa matéria, while
, do
, ... - sendo redefinido não é uma preocupação.
Tem algo que a função deve confiar para ter seu significado e comportamento originais - não há como contornar isso.
O fato de os shells do tipo POSIX permitirem a redefinição de componentes internos e até palavras-chave de idioma é inerentemente um risco à segurança (e escrever código paranóico é difícil em geral).
Para resolver suas preocupações especificamente:
A função depende unalias
e unset
tem seu significado original. Redefini-las como funções shell de uma maneira que altera seu comportamento seria um problema; redefinição como um apelido não é necessariamente uma preocupação, porque citar (parte do) nome do comando (por exemplo, \unalias
) ignora os aliases.
No entanto, citando é não uma opção para a Shell palavras-chave ( while
, for
,if
, do
, ...) e enquanto palavras-chave shell fazer têm precedência sobre shell funções , em bash
e zsh
aliases têm a mais alta prioridade, de modo a proteger contra redefinições shell-palavra-chave que devem ser executados unalias
com seus nomes (embora em shells não interativos bash
(como scripts), os aliases não sejam expandidos por padrão - somente se shopt -s expand_aliases
for explicitamente chamado primeiro.
Para garantir que unalias
- como um componente interno - tenha seu significado original, você deve usá \unset
-lo primeiro, o que exige que ele unset
tenha seu significado original:
unset
é um shell integrado , portanto, para garantir que ele seja chamado como tal, você deve garantir que ele próprio não seja redefinido como uma função . Embora você possa ignorar um formulário de alias com citação, não pode ignorar um formulário de função de shell - captura 22.
Assim, a menos que você possa confiar unset
para ter seu significado original, pelo que posso dizer, não há maneira garantida de se defender contra todas as redefinições maliciosas.