Lưu ý: Tôi tin rằng đây là một giải pháp chắc chắn, di động, sẵn sàng, luôn luôn kéo dài vì lý do đó.
Bên dưới là tập lệnh / chức năng tuân thủ POSIX hoàn toàn do đó là nền tảng chéo (cũng hoạt động trên macOS, readlink
vẫn không hỗ trợ -f
kể từ 10.12 (Sierra)) - nó chỉ sử dụng các tính năng ngôn ngữ shell POSIX và chỉ các cuộc gọi tiện ích tuân thủ POSIX .
Đây là một triển khai di động của GNUreadlink -e
(phiên bản chặt chẽ hơn readlink -f
).
Bạn có thể chạy các script vớish
hoặc nguồn các chức năng trong bash
, ksh
vàzsh
:
Chẳng hạn, bên trong một tập lệnh, bạn có thể sử dụng tập lệnh như sau để có được thư mục gốc của tập lệnh đang chạy, với các liên kết tượng trưng được giải quyết:
trueScriptDir=$(dirname -- "$(rreadlink "$0")")
rreadlink
định nghĩa tập lệnh / hàm:
Các mã đã được điều chỉnh với lòng biết ơn từ câu trả lời này .
Tôi cũng đã tạo ra một bash
độc lập phiên bản tiện ích dựa trên đây , mà bạn có thể cài đặt với
npm install rreadlink -g
, nếu bạn có Node.js cài đặt.
#!/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 "$@"
Một tiếp tuyến về bảo mật:
jarno , liên quan đến chức năng đảm bảo rằng nội trang command
không bị che khuất bởi một bí danh hoặc hàm shell cùng tên, hỏi trong một nhận xét:
Điều gì nếu unalias
hay unset
và [
được thiết lập như là bí danh hoặc chức năng vỏ?
Động lực đằng sau rreadlink
đảm bảo command
có ý nghĩa ban đầu của nó là sử dụng nó để bỏ qua các bí danh và chức năng tiện lợi (lành tính) thường được sử dụng để che giấu các lệnh tiêu chuẩn trong hệ vỏ tương tác, như xác định lại ls
để bao gồm các tùy chọn yêu thích.
Tôi nghĩ đó là an toàn để nói rằng trừ khi bạn đang làm việc với một môi trường tin cậy, độc hại, lo lắng về unalias
hoặc unset
- hoặc, cho rằng vấn đề, while
, do
, ... - được xác định lại là không phải là một mối quan tâm.
Có một cái gì đó mà chức năng phải dựa vào để có ý nghĩa và hành vi ban đầu của nó - không có cách nào xung quanh đó.
Các shell giống như POSIX cho phép xác định lại các nội dung và thậm chí các từ khóa ngôn ngữ vốn là một rủi ro bảo mật (và nói chung việc viết mã hoang tưởng là khó khăn).
Để giải quyết các mối quan tâm của bạn cụ thể:
Các chức năng dựa trên unalias
và unset
có ý nghĩa ban đầu của họ. Việc chúng được định nghĩa lại là các hàm shell theo cách thay đổi hành vi của chúng sẽ là một vấn đề; xác định lại như một bí danh không nhất thiết phải là một mối quan tâm, bởi vì trích dẫn (một phần) tên lệnh (ví dụ \unalias
:) bỏ qua các bí danh.
Tuy nhiên, trích dẫn là không một lựa chọn cho vỏ từ khóa ( while
, for
, if
, do
, ...) và trong khi từ khóa vỏ làm mất ưu tiên so với vỏ chức năng , trong bash
và zsh
bí danh có ưu tiên cao nhất, vì vậy để bảo vệ chống lại định nghĩa lại vỏ từ khóa mà bạn phải chạy unalias
với tên của chúng (mặc dù trong các bash
vỏ không tương tác (như tập lệnh), các mặc định không được mở rộng theo mặc định - chỉ khi shopt -s expand_aliases
được gọi rõ ràng trước).
Để đảm bảo rằng unalias
- dưới dạng dựng sẵn - có nghĩa gốc, \unset
trước tiên bạn phải sử dụng nó, đòi hỏi phải unset
có nghĩa gốc:
unset
là một phần mềm dựng sẵn , vì vậy để đảm bảo rằng nó được gọi như vậy, bạn phải đảm bảo rằng chính nó không được định nghĩa lại như là một hàm . Mặc dù bạn có thể bỏ qua biểu mẫu bí danh bằng trích dẫn, bạn không thể bỏ qua biểu mẫu hàm shell - bắt 22.
Do đó, trừ khi bạn có thể dựa vào unset
ý nghĩa ban đầu của nó, từ những gì tôi có thể nói, không có cách nào bảo đảm để chống lại tất cả các định nghĩa độc hại.