หมายเหตุ: ฉันเชื่อว่านี่เป็นโซลูชันที่มั่นคงพกพาและพร้อมใช้งานซึ่งมีความยาวอย่างสม่ำเสมอด้วยเหตุผลอย่างนั้น
ด้านล่างนี้เป็นสคริปต์ / ฟังก์ชั่นที่รองรับ POSIX อย่างสมบูรณ์ซึ่งเป็นแพลตฟอร์มข้ามแพลตฟอร์ม (ทำงานบน macOS ด้วยซึ่งreadlink
ยังไม่รองรับ-f
ตั้งแต่ 10.12 (Sierra)) - มันใช้คุณสมบัติภาษา POSIX เชลล์เท่านั้นเท่านั้นและเรียกใช้ยูทิลิตี้ POSIX ได้เท่านั้น .
เป็นการใช้งานแบบพกพาของ GNUreadlink -e
(รุ่นที่เข้มงวดกว่าreadlink -f
)
คุณสามารถเรียกใช้สคริปต์ด้วยsh
หรือแหล่งฟังก์ชั่นในbash
, ksh
และzsh
:
ตัวอย่างเช่นภายในสคริปต์คุณสามารถใช้ดังต่อไปนี้เพื่อรับไดเรกทอรีต้นกำเนิดจริงของสคริปต์ที่มีการเรียกใช้พร้อมกับ symlink ที่ได้รับการแก้ไข:
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 , ในการอ้างอิงถึงฟังก์ชั่นเพื่อให้แน่ใจว่า builtincommand
ไม่ได้ถูกเงาโดยนามแฝงหรือฟังก์ชั่นเชลล์ที่มีชื่อเดียวกัน, ถามในความคิดเห็น:
เกิดอะไรขึ้นถ้าunalias
หรือunset
และ[
ถูกตั้งเป็นชื่อแทนหรือฟังก์ชั่นเปลือก?
แรงจูงใจเบื้องหลังการrreadlink
สร้างความมั่นใจว่าcommand
มีความหมายดั้งเดิมคือการใช้เพื่อหลีกเลี่ยงความสะดวกสบายนามแฝงและมักใช้ในการทำเงาคำสั่งมาตรฐานในเชลล์แบบโต้ตอบเช่นการกำหนดใหม่ls
เพื่อรวมตัวเลือกที่โปรดปราน
ฉันคิดว่ามันปลอดภัยที่จะพูดว่าเว้นแต่คุณจะจัดการกับสภาพแวดล้อมที่ไม่น่าเชื่อถือและเป็นอันตรายไม่ต้องกังวลเกี่ยวกับunalias
หรือunset
- หรือสำหรับเรื่องwhile
นั้นdo
,, ... - การถูกนิยามใหม่ไม่ใช่ข้อกังวล
มีบางสิ่งที่ฟังก์ชั่นนั้นต้องพึ่งพาที่จะมีความหมายและพฤติกรรมดั้งเดิม - ไม่มีทางแก้ไข
เชลล์ที่คล้าย POSIX นั้นอนุญาตให้สร้างนิยามใหม่ของบิวอินและแม้แต่คำหลักภาษาก็คือเนื้อแท้เสี่ยงต่อความปลอดภัย (และเขียนรหัสหวาดระแวงยากทั่วไป)
ในการจัดการข้อกังวลของคุณโดยเฉพาะ:
ฟังก์ชั่นอาศัยunalias
และunset
มีความหมายดั้งเดิม ให้พวกเขานิยามใหม่เป็นฟังก์ชันเชลล์ในลักษณะที่เปลี่ยนแปลงพฤติกรรมของพวกเขาจะเป็นปัญหา การนิยามใหม่ในฐานะนามแฝงไม่จำเป็นต้องเป็นข้อกังวลเนื่องจากการอ้างชื่อคำสั่ง (เช่นส่วนหนึ่งของ) (เช่น\unalias
) จะข้ามนามแฝง
แต่อ้างเป็นไม่ได้เป็นตัวเลือกสำหรับเปลือกคำหลัก ( while
, for
, if
, do
, ... ) และในขณะที่คำหลักเปลือกทำจะมีความสำคัญมากกว่าเปลือกฟังก์ชั่นในbash
และzsh
นามแฝงมีความสำคัญสูงสุดดังนั้นเพื่อป้องกัน redefinitions เปลือกคำหลักที่คุณต้องทำงานunalias
ด้วย ชื่อของพวกเขา (แม้ว่าในนามแฝงที่ไม่ใช่การโต้ตอบ bash
(เช่นสคริปต์) นามแฝงจะไม่ได้รับการขยายโดยค่าเริ่มต้น - เฉพาะถ้าshopt -s expand_aliases
เรียกอย่างชัดเจนก่อน)
เพื่อให้มั่นใจว่าunalias
- ในฐานะที่เป็น builtin - มีความหมายดั้งเดิมคุณต้องใช้\unset
มันก่อนซึ่งต้องการให้unset
มีความหมายดั้งเดิม:
unset
เป็นเชลล์ในตัวดังนั้นเพื่อให้แน่ใจว่ามีการเรียกใช้เช่นนี้คุณต้องตรวจสอบให้แน่ใจว่ามันไม่ได้ถูกนิยามใหม่เป็นฟังก์ชั่นฟังก์ชั่นในขณะที่คุณสามารถข้ามฟอร์มนามแฝงด้วยการอ้างอิงคุณไม่สามารถข้ามแบบฟอร์มฟังก์ชันเชลล์ - catch 22
ดังนั้นหากคุณไม่สามารถพึ่งพาunset
ความหมายดั้งเดิมได้จากสิ่งที่ฉันสามารถบอกได้ไม่มีวิธีใดที่รับประกันได้ว่าจะป้องกันการกำหนดนิยามใหม่ที่เป็นอันตรายทั้งหมด