Não tenho conhecimento de tal utilidade. No entanto, sua abordagem é falha (e eu concordo que é muito difícil acertar).
O modificador :a calcula um caminho absoluto sem nenhum acesso ao sistema de arquivos. Em particular, ele altera a/b/.. para a , independentemente de o a/b ser um link simbólico ou não.
Se você tiver um link simbólico /a/b -> ../foo , isso será /foo somente se /a não for um link simbólico em si. Mesmo que você canonicize o $(dirname $1) , isso ainda não funcionaria para links simbólicos como /a/b -> x/../../foo if x em si é um symlink.
No seu caso:
/tmp/example/A/B/C/D/symlink-0 -> ../../symlink-1/b/c/d/target
resolve /tmp/example/A/B/symlink-1/b/c/d/target apenas porque nem /tmp/example/A/B/C/D nem /tmp/example/A/B/C são links simbólicos.
Em outras palavras, para obter um caminho absoluto para o destino de um link simbólico livre de .. componentes, talvez seja necessário resolver mais de um link simbólico e não é possível fazê-lo sozinho combinando o caminho do arquivo e o alvo do link simbólico.
Se você quiser esse caminho, a maneira mais fácil (e eu diria apenas razoável e / ou útil) é pegar o caminho canônico daquele destino do link simbólico, que é onde todos os componentes do caminho não são .. , . nem links simbólicos, exceto possivelmente para o último. Para isso, você poderia fazer:
zmodload -F zsh/stat b:zstat
canonicalize1() {
if [ -L "$1" ]; then
local link
zstat -A link +link -- "$1" || return
case $link in
(/*) ;;
(*)
case $1:h in
(*/) link=$1:h$link;;
(*) link=$1:h/$link;;
esac;;
esac
printf '%s\n' $link:h:A/$link:t
else
printf '%s\n' $1:A
fi
}
Em seus links simbólicos /tmp/example/A/B/C/D/symlink-0 , isso ainda dá /tmp/example/a/b/c/d/target , já que o alvo do link simbólico não é um link simbólico, mas se não é isso que você quer, então eu pergunto: qual resultado você quer ao fazer:
canonicalize1 /tmp/x/y
Onde /tmp/x/y é um link simbólico para ../foo e /tmp/x um link simbólico para /a/b/c e /a , /a/b , /a/b/c também são eles próprios links simbólicos?