Cách giải quyết các liên kết tượng trưng trong tập lệnh shell


220

Đưa ra một đường dẫn tuyệt đối hoặc tương đối (trong một hệ thống giống Unix), tôi muốn xác định đường dẫn đầy đủ của mục tiêu sau khi giải quyết bất kỳ liên kết trung gian nào. Điểm thưởng cho việc giải quyết ~ ký hiệu tên người dùng cùng một lúc.

Nếu mục tiêu là một thư mục, có thể chdir () vào thư mục và sau đó gọi getcwd (), nhưng tôi thực sự muốn làm điều này từ một tập lệnh shell thay vì viết một trình trợ giúp C. Thật không may, shell có xu hướng cố gắng che giấu sự tồn tại của symlink khỏi người dùng (đây là bash trên OS X):

$ ls -ld foo bar
drwxr-xr-x   2 greg  greg  68 Aug 11 22:36 bar
lrwxr-xr-x   1 greg  greg   3 Aug 11 22:36 foo -> bar
$ cd foo
$ pwd
/Users/greg/tmp/foo
$

Điều tôi muốn là một hàm giải quyết () sao cho khi được thực thi từ thư mục tmp trong ví dụ trên, giải quyết ("foo") == "/ Users / greg / tmp / bar".

Câu trả lời:


92

Theo các tiêu chuẩn, pwd -Pnên trả lại đường dẫn với các liên kết tượng trưng được giải quyết.

Hàm C char *getcwd(char *buf, size_t size)từ unistd.hnên có hành vi tương tự.

getcwd pwd


22
Điều này chỉ hoạt động cho thư mục (hiện tại)? Nếu mục tiêu là một tệp, nó sẽ không cung cấp bất cứ thứ gì ...
dala

4
Không hoạt động nếu liên kết bị hỏng vì bạn không thể đặt đường dẫn hiện tại của mình
Tom Howard

6
Để tóm tắt với lợi ích của nhận thức muộn: Câu trả lời này chỉ hoạt động trong những trường hợp rất hạn chế, cụ thể là nếu liên kết tượng trưng quan tâm đến một thư mục thực sự tồn tại ; Thêm vào đó, bạn phải cdcho nó trước, trước khi gọi pwd -P. Nói cách khác: nó sẽ không cho phép bạn quyết tâm (xem mục tiêu) liên kết tượng trưng đến tập tin hoặc phá vỡ liên kết tượng trưng, và để giải quyết các liên kết tượng trưng hiện thư mục bạn phải làm công việc bổ sung (khôi phục lại thư mục làm việc trước hoặc khoanh vùng cdpwd -Pcác cuộc gọi trong một subshell).
mkuity0

xấu tôi tìm cách giải quyết một tập tin chứ không phải một thư mục.
Martin

Như những người khác đã chỉ ra, điều này không thực sự trả lời câu hỏi. Câu trả lời của @ pixelbeat dưới đây.
erstaples

401
readlink -f "$path"

Lưu ý của biên tập viên: Phần trên hoạt động với GNU readlinkFreeBSD / PC-BSD / OpenBSD readlink , nhưng không phải trên OS X kể từ ngày 10.11.
GNU readlink cung cấp các tùy chọn bổ sung, có liên quan, chẳng hạn như -mđể giải quyết một liên kết tượng trưng cho dù mục tiêu cuối cùng có tồn tại hay không.

Lưu ý kể từ GNU coreutils 8.15 (2012-01-06), có một chương trình realpath có sẵn ít gây khó chịu và linh hoạt hơn so với ở trên. Nó cũng tương thích với ứng dụng FreeBSD cùng tên. Nó cũng bao gồm chức năng để tạo một đường dẫn tương đối giữa hai tệp.

realpath $path

[Quản trị viên bổ sung bên dưới từ nhận xét của halloleo - danorton]

Đối với Mac OS X (đến ít nhất 10.11.x), hãy sử dụng readlinkmà không có -ftùy chọn:

readlink $path

Lưu ý của biên tập viên: Điều này sẽ không giải quyết đệ quy liên kết theo cách đệ quy và do đó sẽ không báo cáo mục tiêu cuối cùng ; ví dụ: liên kết tượng trưng ađã chỉ ra b, lần lượt trỏ tới c, điều này sẽ chỉ báo cáo b(và sẽ không đảm bảo rằng đó là đầu ra dưới dạng một đường dẫn tuyệt đối ).
Sử dụng perllệnh sau trên OS X để lấp đầy khoảng trống của readlink -fchức năng bị thiếu :
perl -MCwd -le 'print Cwd::abs_path(shift)' "$path"


5
Điều này không hoạt động trong Mac OS X - xem stackoverflow.com/questions/1055671/ cấp
Bkkbrad

1
xấu hổ về khả năng không tương thích của OS X, nếu không thì +1
jkp

11
Trên OS X, bạn có thể cài đặt coreutils với homebrew. Nó cài đặt nó là "grealpath".
Kief

10
readlinkhoạt động trên OSX, nhưng cần cú pháp khác: readlink $path không có sự -f.
halloleo

2
readlink không thể tham chiếu nhiều lớp symlink mặc dù vậy, nó chỉ hủy đăng ký một lớp tại một thời điểm
Magnus

26

"Pwd -P" dường như hoạt động nếu bạn chỉ muốn thư mục, nhưng nếu vì lý do nào đó bạn muốn tên của thực thi thực tế, tôi không nghĩ rằng nó giúp. Đây là giải pháp của tôi:

#!/bin/bash

# get the absolute path of the executable
SELF_PATH=$(cd -P -- "$(dirname -- "$0")" && pwd -P) && SELF_PATH=$SELF_PATH/$(basename -- "$0")

# resolve symlinks
while [[ -h $SELF_PATH ]]; do
    # 1) cd to directory of the symlink
    # 2) cd to the directory of where the symlink points
    # 3) get the pwd
    # 4) append the basename
    DIR=$(dirname -- "$SELF_PATH")
    SYM=$(readlink "$SELF_PATH")
    SELF_PATH=$(cd "$DIR" && cd "$(dirname -- "$SYM")" && pwd)/$(basename -- "$SYM")
done

17

Một trong những sở thích của tôi là realpath foo

realpath - trả lại tên đường dẫn tuyệt đối được chuẩn hóa

realpath mở rộng tất cả các liên kết tượng trưng và giải quyết các tham chiếu đến các ký tự '/./', '/../' và thêm '/' trong chuỗi kết thúc null được đặt tên theo đường dẫn và
       lưu trữ tên đường dẫn tuyệt đối được chuẩn hóa trong bộ đệm có kích thước PATH_MAX được đặt tên bởi dec_path. Đường dẫn kết quả sẽ không có liên kết tượng trưng, ​​'/./' hoặc
       '/../' các thành phần.

Trên Debian (etch và sau này) lệnh này có sẵn trong gói realpath.
Phil Ross

2
realpath bây giờ (tháng 1 năm 2012) là một phần của coreutils và tương thích ngược với biến thể debian và BSD
pixelbeat

1
Tôi không có realpathtrên Centos 6 với GNU coreutils 8.4.31. Tôi đã bắt gặp một số người khác trên Unix & Linux có gói lõi GNU mà không có realpath . Vì vậy, nó dường như phụ thuộc vào nhiều hơn chỉ là phiên bản.
toxalot

Tôi thích realpathhơn readlinkvì nó cung cấp các cờ tùy chọn, chẳng hạn như--relative-to
Array_sea

10
readlink -e [filepath]

dường như chính xác là những gì bạn yêu cầu - nó chấp nhận một đường dẫn trọng yếu, giải quyết tất cả các liên kết tượng trưng và trả về đường dẫn "thực" - và đó là "tiêu chuẩn * nix" có khả năng tất cả các hệ thống đã có


Chỉ cần bị nó cắn. Nó không hoạt động trên Mac và tôi đang tìm kiếm sự thay thế.
dhill

5

Cách khác:

# Gets the real path of a link, following all links
myreadlink() { [ ! -h "$1" ] && echo "$1" || (local link="$(expr "$(command ls -ld -- "$1")" : '.*-> \(.*\)$')"; cd $(dirname $1); myreadlink "$link" | sed "s|^\([^/].*\)\$|$(dirname $1)/\1|"); }

# Returns the absolute path to a command, maybe in $PATH (which) or not. If not found, returns the same
whereis() { echo $1 | sed "s|^\([^/].*/.*\)|$(pwd)/\1|;s|^\([^/]*\)$|$(which -- $1)|;s|^$|$1|"; } 

# Returns the realpath of a called command.
whereis_realpath() { local SCRIPT_PATH=$(whereis $1); myreadlink ${SCRIPT_PATH} | sed "s|^\([^/].*\)\$|$(dirname ${SCRIPT_PATH})/\1|"; } 

Tôi cần cd trong myreadlink (), vì đây là hàm đệ quy, đi đến từng thư mục cho đến khi tìm thấy một liên kết. Nếu nó tìm thấy một liên kết, trả về realpath, và sau đó sed sẽ thay thế đường dẫn.
Keymon

5

Đặt một số giải pháp nhất định lại với nhau, biết rằng readlink có sẵn trên hầu hết các hệ thống, nhưng cần các đối số khác nhau, điều này hoạt động tốt với tôi trên OSX và Debian. Tôi không chắc chắn về các hệ thống BSD. Có lẽ điều kiện cần phải được [[ $OSTYPE != darwin* ]]loại trừ -fkhỏi OSX.

#!/bin/bash
MY_DIR=$( cd $(dirname $(readlink `[[ $OSTYPE == linux* ]] && echo "-f"` $0)) ; pwd -P)
echo "$MY_DIR"

3

Đây là cách người ta có thể nhận đường dẫn thực tế đến tệp trong MacOS / Unix bằng cách sử dụng tập lệnh Perl nội tuyến:

FILE=$(perl -e "use Cwd qw(abs_path); print abs_path('$0')")

Tương tự, để có được thư mục của một tệp symlinked:

DIR=$(perl -e "use Cwd qw(abs_path); use File::Basename; print dirname(abs_path('$0'))")

3

Đường dẫn của bạn là một thư mục, hoặc nó có thể là một tập tin? Nếu đó là một thư mục, nó đơn giản:

(cd "$DIR"; pwd -P)

Tuy nhiên, nếu đó có thể là một tệp, thì điều này sẽ không hoạt động:

DIR=$(cd $(dirname "$FILE"); pwd -P); echo "${DIR}/$(readlink "$FILE")"

bởi vì liên kết tượng trưng có thể phân giải thành một đường dẫn tương đối hoặc đầy đủ.

Trên các tập lệnh tôi cần tìm đường dẫn thực, để tôi có thể tham chiếu cấu hình hoặc các tập lệnh khác được cài đặt cùng với nó, tôi sử dụng:

SOURCE="${BASH_SOURCE[0]}"
while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink
  DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
  SOURCE="$(readlink "$SOURCE")"
  [[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE" # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located
done

Bạn có thể đặt SOURCEthành bất kỳ đường dẫn tập tin. Về cơ bản, miễn là đường dẫn là một liên kết tượng trưng, ​​nó sẽ giải quyết liên kết tượng trưng đó. Bí quyết là trong dòng cuối cùng của vòng lặp. Nếu symlink được giải quyết là tuyệt đối, nó sẽ sử dụng như là SOURCE. Tuy nhiên, nếu nó là tương đối, nó sẽ trả trước DIRcho nó, nó đã được giải quyết thành một vị trí thực sự bằng thủ thuật đơn giản mà tôi mô tả lần đầu tiên.


2
function realpath {
    local r=$1; local t=$(readlink $r)
    while [ $t ]; do
        r=$(cd $(dirname $r) && cd $(dirname $t) && pwd -P)/$(basename $t)
        t=$(readlink $r)
    done
    echo $r
}

#example usage
SCRIPT_PARENT_DIR=$(dirname $(realpath "$0"))/..

Điều này sẽ phá vỡ với (a) bất kỳ đường dẫn nào chứa các siêu ký tự khoảng trắng hoặc vỏ và (b) các liên kết tượng trưng bị hỏng (không phải là vấn đề nếu bạn chỉ muốn đường dẫn cha của tập lệnh đang chạy, giả sử (a) không áp dụng).
mkuity0

Nếu bạn quan tâm đến khoảng trắng sử dụng dấu ngoặc kép.
Dave

Vui lòng làm - mặc dù đó không phải là địa chỉ (b).
mkuity0

Vui lòng cho tôi xem một ví dụ về b) khi điều này thất bại. Theo định nghĩa, một liên kết tượng trưng bị hỏng trỏ đến một mục nhập thư mục không tồn tại. Điểm của kịch bản này là giải quyết các liên kết tượng trưng theo hướng khác. Nếu symlink bị hỏng, bạn sẽ không thực thi tập lệnh. Ví dụ này nhằm thể hiện việc giải quyết tập lệnh hiện đang thực thi.
Dave

"Dự định thể hiện việc giải quyết tập lệnh hiện đang thực thi" - thực sự, đó là sự thu hẹp phạm vi câu hỏi mà bạn đã chọn để tập trung vào; điều đó là hoàn toàn tốt, miễn là bạn nói như vậy. Vì bạn đã không làm, tôi đã nêu nó trong bình luận của tôi. Vui lòng sửa vấn đề trích dẫn, đây là một vấn đề không phân biệt phạm vi câu trả lời.
mkuity0

2

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, readlinkvẫn không hỗ trợ -fkể 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, kshzsh :

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 commandkhô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 unaliashay unset[đượ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 commandcó ý 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ề unaliashoặ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.

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 unaliasunsetcó ý 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 bashzshbí 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 unaliasvớ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, \unsettrước tiên bạn phải sử dụng nó, đòi hỏi phải unsetcó nghĩa gốc:

unsetlà 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.


Theo kinh nghiệm của tôi, trích dẫn bỏ qua các bí danh, không phải các hàm shell, không giống như bạn nói trước.
jarno

Tôi đã thử nghiệm mà tôi có thể định nghĩa [như một bí danh trong dashbashvà như là một chức năng vỏ trong bash.
jarno

1
Tôi đã đưa ra quan điểm của mình như một câu hỏi riêng biệt .
jarno

@jarno: Điểm tốt; Tôi đã cập nhật câu trả lời của mình; Hãy cho tôi biết nếu bạn nghĩ vẫn còn một vấn đề.
mkuity0

1
@jarno: Bạn có thể định nghĩa whilelà một hàm trong bash, kshzsh(nhưng không dash), nhưng chỉ với function <name>cú pháp: function while { echo foo; }works ( while() { echo foo; }không). Tuy nhiên, điều này sẽ không làm mờ while từ khóa , bởi vì các từ khóa có độ ưu tiên cao hơn các hàm (cách duy nhất để gọi hàm này là as \while). Trong bashzsh, bí danh có độ ưu tiên cao hơn các từ khóa, vì vậy bí danh định nghĩa lại từ khóa làm bóng họ (nhưng trong bashtheo mặc định chỉ trong tương tác vỏ, trừ khi shopt -s expand_aliasesđược gọi một cách rõ ràng).
mkuity0

1

Các tập lệnh shell thông thường thường phải tìm thư mục "home" của chúng ngay cả khi chúng được gọi dưới dạng symlink. Do đó, tập lệnh phải tìm vị trí "thực" của họ chỉ từ $ 0.

cat `mvn`

trên hệ thống của tôi in một tập lệnh chứa nội dung sau, đây sẽ là một gợi ý tốt về những gì bạn cần.

if [ -z "$M2_HOME" ] ; then
  ## resolve links - $0 may be a link to maven's home
  PRG="$0"

  # need this for relative symlinks
  while [ -h "$PRG" ] ; do
    ls=`ls -ld "$PRG"`
    link=`expr "$ls" : '.*-> \(.*\)$'`
    if expr "$link" : '/.*' > /dev/null; then
      PRG="$link"
    else
      PRG="`dirname "$PRG"`/$link"
    fi
  done

  saveddir=`pwd`

  M2_HOME=`dirname "$PRG"`/..

  # make it fully qualified
  M2_HOME=`cd "$M2_HOME" && pwd`

1

Thử cái này:

cd $(dirname $([ -L $0 ] && readlink -f $0 || echo $0))

1

Vì tôi đã gặp phải điều này nhiều lần trong nhiều năm và lần này tôi cần một phiên bản bash di động thuần túy mà tôi có thể sử dụng trên OSX và linux, tôi đã tiếp tục và viết một:

Phiên bản sống ở đây:

https://github.com/keen99/shell-fifts/tree/master/resolve_path

nhưng vì lợi ích của SO, đây là phiên bản hiện tại (tôi cảm thấy nó đã được thử nghiệm tốt..nhưng tôi sẵn sàng phản hồi!)

Có thể không khó để làm cho nó hoạt động cho vỏ bourne đơn giản (sh), nhưng tôi đã không thử ... Tôi thích $ FUNCNAME quá nhiều. :)

#!/bin/bash

resolve_path() {
    #I'm bash only, please!
    # usage:  resolve_path <a file or directory> 
    # follows symlinks and relative paths, returns a full real path
    #
    local owd="$PWD"
    #echo "$FUNCNAME for $1" >&2
    local opath="$1"
    local npath=""
    local obase=$(basename "$opath")
    local odir=$(dirname "$opath")
    if [[ -L "$opath" ]]
    then
    #it's a link.
    #file or directory, we want to cd into it's dir
        cd $odir
    #then extract where the link points.
        npath=$(readlink "$obase")
        #have to -L BEFORE we -f, because -f includes -L :(
        if [[ -L $npath ]]
         then
        #the link points to another symlink, so go follow that.
            resolve_path "$npath"
            #and finish out early, we're done.
            return $?
            #done
        elif [[ -f $npath ]]
        #the link points to a file.
         then
            #get the dir for the new file
            nbase=$(basename $npath)
            npath=$(dirname $npath)
            cd "$npath"
            ndir=$(pwd -P)
            retval=0
            #done
        elif [[ -d $npath ]]
         then
        #the link points to a directory.
            cd "$npath"
            ndir=$(pwd -P)
            retval=0
            #done
        else
            echo "$FUNCNAME: ERROR: unknown condition inside link!!" >&2
            echo "opath [[ $opath ]]" >&2
            echo "npath [[ $npath ]]" >&2
            return 1
        fi
    else
        if ! [[ -e "$opath" ]]
         then
            echo "$FUNCNAME: $opath: No such file or directory" >&2
            return 1
            #and break early
        elif [[ -d "$opath" ]]
         then 
            cd "$opath"
            ndir=$(pwd -P)
            retval=0
            #done
        elif [[ -f "$opath" ]]
         then
            cd $odir
            ndir=$(pwd -P)
            nbase=$(basename "$opath")
            retval=0
            #done
        else
            echo "$FUNCNAME: ERROR: unknown condition outside link!!" >&2
            echo "opath [[ $opath ]]" >&2
            return 1
        fi
    fi
    #now assemble our output
    echo -n "$ndir"
    if [[ "x${nbase:=}" != "x" ]]
     then
        echo "/$nbase"
    else 
        echo
    fi
    #now return to where we were
    cd "$owd"
    return $retval
}

đây là một ví dụ kinh điển, nhờ brew:

%% ls -l `which mvn`
lrwxr-xr-x  1 draistrick  502  29 Dec 17 10:50 /usr/local/bin/mvn@ -> ../Cellar/maven/3.2.3/bin/mvn

sử dụng chức năng này và nó sẽ trả về đường dẫn -real-:

%% cat test.sh
#!/bin/bash
. resolve_path.inc
echo
echo "relative symlinked path:"
which mvn
echo
echo "and the real path:"
resolve_path `which mvn`


%% test.sh

relative symlinked path:
/usr/local/bin/mvn

and the real path:
/usr/local/Cellar/maven/3.2.3/libexec/bin/mvn 

Câu trả lời trước đó của tôi thực hiện chính xác điều tương tự trong khoảng 1/4 khoảng trống :) Đó là kịch bản shell khá cơ bản và không xứng đáng với một repo git.
Dave

1

Để khắc phục sự không tương thích của Mac, tôi đã nghĩ ra

echo `php -r "echo realpath('foo');"`

Không tuyệt vời nhưng hệ điều hành chéo


3
Python 2.6+ có sẵn trên nhiều hệ thống người dùng cuối hơn php, vì vậy python -c "from os import path; print(path.realpath('${SYMLINK_PATH}'));"có lẽ sẽ có ý nghĩa hơn. Tuy nhiên, vào thời điểm bạn cần sử dụng Python từ tập lệnh shell, có lẽ bạn chỉ nên sử dụng Python và tiết kiệm cho mình sự đau đầu của kịch bản shell đa nền tảng.
Jonathan Baldwin

Bạn không cần nhiều hơn các readlink, dirname và tên cơ sở.
Dave

@Dave : dirname, basenamereadlinklà các tiện ích bên ngoài , không được tích hợp sẵn; dirnamebasenamelà một phần của POSIX, readlinkthì không.
mkuity0

@ mkuity0 - Bạn hoàn toàn đúng. Chúng được cung cấp bởi CoreUtils hoặc tương đương. Tôi không nên đến SO sau 1 giờ sáng. Bản chất của nhận xét của tôi là không phải PHP và bất kỳ trình thông dịch ngôn ngữ nào khác ngoài cài đặt trong một hệ thống cơ sở. Tôi đã sử dụng tập lệnh tôi cung cấp trên trang này trên mọi biến thể linux kể từ năm 1997 và MacOS X kể từ năm 2006 mà không gặp lỗi. OP đã không yêu cầu một giải pháp POSIX. Môi trường cụ thể của họ là Mac OS X.
Dave

@ Dave: Vâng, nó có thể làm điều đó với tiện ích chứng khoán, nhưng nó cũng là khó khăn để làm như vậy (được minh chứng bởi những thiếu sót của kịch bản của bạn). Nếu OS X thực sự là trọng tâm, thì câu trả lời này hoàn toàn tốt - và đơn giản hơn nhiều - được đưa phpra với OS X. Tuy nhiên, mặc dù phần thân của câu hỏi có đề cập đến OS X, nhưng nó không được gắn thẻ như vậy và nó trở nên rõ ràng rằng mọi người trên các nền tảng khác nhau đến đây để tìm câu trả lời, vì vậy, đáng để chỉ ra những gì cụ thể về nền tảng / không phải POSIX.
mkuity0

1

Đây là trình phân giải symlink trong Bash hoạt động cho dù liên kết là thư mục hay không phải thư mục:

function readlinks {(
  set -o errexit -o nounset
  declare n=0 limit=1024 link="$1"

  # If it's a directory, just skip all this.
  if cd "$link" 2>/dev/null
  then
    pwd -P
    return 0
  fi

  # Resolve until we are out of links (or recurse too deep).
  while [[ -L $link ]] && [[ $n -lt $limit ]]
  do
    cd "$(dirname -- "$link")"
    n=$((n + 1))
    link="$(readlink -- "${link##*/}")"
  done
  cd "$(dirname -- "$link")"

  if [[ $n -ge $limit ]]
  then
    echo "Recursion limit ($limit) exceeded." >&2
    return 2
  fi

  printf '%s/%s\n' "$(pwd -P)" "${link##*/}"
)}

Lưu ý rằng tất cả cdvà các setcông cụ diễn ra trong một subshell.


Trên thực tế, {} xung quanh () là không cần thiết, vì () được tính là "lệnh ghép", giống như {}. Nhưng bạn vẫn cần () sau tên hàm.
Chris Cogdon

@ChrisCogdon {}Xung quanh ()không cần thiết nếu không có ()tên hàm phía sau. Bash chấp nhận khai báo hàm mà không cần ()vì shell không có danh sách tham số trong khai báo và không thực hiện các cuộc gọi với ()khai báo hàm ()nên không có nhiều ý nghĩa.
solidsnack

0

Ở đây tôi trình bày những gì tôi tin là một giải pháp đa nền tảng (ít nhất là Linux và macOS) cho câu trả lời hiện đang hoạt động tốt cho tôi.

crosspath()
{
    local ref="$1"
    if [ -x "$(which realpath)" ]; then
        path="$(realpath "$ref")"
    else
        path="$(readlink -f "$ref" 2> /dev/null)"
        if [ $? -gt 0 ]; then
            if [ -x "$(which readlink)" ]; then
                if [ ! -z "$(readlink "$ref")" ]; then
                    ref="$(readlink "$ref")"
                fi
            else
                echo "realpath and readlink not available. The following may not be the final path." 1>&2
            fi
            if [ -d "$ref" ]; then
                path="$(cd "$ref"; pwd -P)"
            else
                path="$(cd $(dirname "$ref"); pwd -P)/$(basename "$ref")"
            fi
        fi
    fi
    echo "$path"
}

Đây là một giải pháp macOS (chỉ?). Có thể phù hợp hơn với câu hỏi ban đầu.

mac_realpath()
{
    local ref="$1"
    if [[ ! -z "$(readlink "$ref")" ]]; then
        ref="$(readlink "$1")"
    fi
    if [[ -d "$ref" ]]; then
        echo "$(cd "$ref"; pwd -P)"
    else
        echo "$(cd $(dirname "$ref"); pwd -P)/$(basename "$ref")"
    fi
}

0

Câu trả lời của tôi ở đây Bash: làm thế nào để có được đường dẫn thực sự của một liên kết tượng trưng?

nhưng trong ngắn hạn rất tiện dụng trong các kịch bản:

script_home=$( dirname $(realpath "$0") )
echo Original script home: $script_home

Đây là một phần của lõi GNU, thích hợp để sử dụng trong các hệ thống Linux.

Để kiểm tra mọi thứ, chúng tôi đặt symlink vào / home / test2 /, sửa đổi một số thứ bổ sung và chạy / gọi nó từ thư mục gốc:

/$ /home/test2/symlink
/home/test
Original script home: /home/test

Ở đâu

Original script is: /home/test/realscript.sh
Called script is: /home/test2/symlink

0

Trong trường hợp không thể sử dụng pwd (ví dụ: gọi tập lệnh từ một vị trí khác), hãy sử dụng realpath (có hoặc không có dirname):

$(dirname $(realpath $PATH_TO_BE_RESOLVED))

Hoạt động cả khi gọi qua (nhiều) symlink (s) hoặc khi gọi trực tiếp tập lệnh - từ bất kỳ vị trí nào.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.