Catatan: Saya percaya ini menjadi solusi yang solid, portabel, siap pakai, yang selalu panjang untuk alasan itu.
Di bawah ini adalah script / fungsi yang sepenuhnya kompatibel dengan POSIX yang karena itu lintas platform (berfungsi pada macOS juga, yang readlink
masih belum mendukung -f
pada 10.12 (Sierra)) - ini hanya menggunakan fitur bahasa shell POSIX dan hanya panggilan utilitas yang sesuai dengan POSIX .
Ini adalah implementasi portabel dari GNUreadlink -e
(versi yang lebih ketat readlink -f
).
Anda dapat menjalankan skrip dengansh
atau sumber yang fungsi dalam bash
, ksh
danzsh
:
Misalnya, di dalam skrip Anda dapat menggunakannya sebagai berikut untuk mendapatkan direktori asli skrip yang menjalankan asalnya, dengan symlink yang diselesaikan:
trueScriptDir=$(dirname -- "$(rreadlink "$0")")
rreadlink
definisi skrip / fungsi:
Kode diadaptasi dengan rasa terima kasih dari jawaban ini .
Saya juga membuat bash
versi utilitas yang berdiri sendiri di sini , yang dapat Anda instal
npm install rreadlink -g
, jika Node.js telah diinstal.
#!/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 "$@"
Garis singgung tentang keamanan:
jarno , mengacu pada fungsi yang memastikan bahwa builtin command
tidak dibayangi oleh fungsi alias atau shell dengan nama yang sama, bertanya dalam komentar:
Bagaimana jika unalias
atau unset
dan [
ditetapkan sebagai alias atau fungsi shell?
Motivasi di balik rreadlink
memastikan bahwa command
memiliki makna aslinya adalah menggunakannya untuk mem-bypass (jinak) alias kenyamanan dan fungsi yang sering digunakan untuk membayangi perintah standar dalam shell interaktif, seperti mendefinisikan ulangls
untuk menyertakan opsi favorit.
Saya pikir aman untuk mengatakan bahwa kecuali Anda sedang berurusan dengan, lingkungan berbahaya tidak terpercaya, mengkhawatirkan unalias
atau unset
- atau, dalam hal ini, while
, do
, ... - yang didefinisikan ulang tidak perhatian.
Ada sesuatu yang fungsi harus andalkan untuk memiliki makna dan perilaku aslinya - tidak ada jalan lain untuk itu.
Kerang seperti POSIX memungkinkan pendefinisian ulang builtin dan bahkan kata kunci bahasa secara inheren risiko keamanan (dan menulis kode paranoid sulit pada umumnya).
Untuk mengatasi masalah Anda secara khusus:
Fungsi ini bergantung pada unalias
dan unset
memiliki makna aslinya. Setelah mereka didefinisikan ulang sebagai fungsi shell dengan cara yang mengubah perilaku mereka akan menjadi masalah; redefinisi sebagai alias tidak selalu menjadi perhatian, karena mengutip (bagian dari) nama perintah (misalnya, \unalias
) mem-bypass alias.
Namun, mengutip adalah bukan pilihan bagi shell kata kunci ( while
, for
, if
, do
, ...) dan sementara kata kunci shell melakukan take didahulukan atas shell fungsi , di bash
dan zsh
alias memiliki hak tertinggi, sehingga untuk menjaga terhadap redefinitions shell-kata kunci Anda harus menjalankan unalias
dengan nama mereka (meskipun dalam shell non-interaktif bash
(seperti skrip) alias tidak diperluas secara default - hanya jika shopt -s expand_aliases
secara eksplisit disebut pertama).
Untuk memastikan bahwa unalias
- sebagai builtin - memiliki makna aslinya, Anda harus menggunakannya \unset
terlebih dahulu, yang mengharuskan yang unset
memiliki makna aslinya:
unset
adalah shell builtin , jadi untuk memastikan bahwa itu dipanggil seperti itu, Anda harus memastikan bahwa itu sendiri tidak didefinisikan ulang sebagai suatu fungsi . Meskipun Anda bisa mem-bypass formulir alias dengan mengutip, Anda tidak bisa mem-bypass formulir fungsi shell - catch 22.
Jadi, kecuali Anda dapat mengandalkan unset
untuk memiliki makna aslinya, dari apa yang dapat saya katakan, tidak ada cara yang pasti untuk mempertahankan diri dari semua definisi-ulang berbahaya.