Làm cách nào tôi có thể có được hành vi của readlink -f của GNU trên máy Mac?


377

Trên Linux, readlinktiện ích chấp nhận một tùy chọn -ftheo các liên kết bổ sung. Điều này dường như không hoạt động trên Mac và có thể các hệ thống dựa trên BSD. Tương đương sẽ là gì?

Dưới đây là một số thông tin gỡ lỗi:

$ which readlink; readlink -f
/usr/bin/readlink
readlink: illegal option -f
usage: readlink [-n] [file ...]

1
Một chút muộn, nhưng câu hỏi của bạn thiếu bất kỳ đề cập đến vỏ bạn sử dụng. Điều này có liên quan, bởi vì readlinkcó thể là một lệnh dựng sẵn hoặc lệnh bên ngoài.
0xC0000022L

1
Điều đó sẽ giải thích sự khác biệt có lẽ. Tôi khá chắc chắn rằng tôi đã sử dụng bash trong cả hai lần.
troelskn

1
Tại sao tùy chọn này là bất hợp pháp trên máy Mac?
CommaToast

3
Tôi thực sự mong muốn Apple sẽ làm cho OS X hỗ trợ các đường dẫn linux mặc định hơn và giải quyết những thứ như thế này. Họ có thể làm điều đó mà không phá vỡ bất cứ điều gì, phải không?
CommaToast

1
@CommaToast Chà, họ giao hàng với Perl nên ++ :-) ... mở Terminal.app và gõ: touch myfile ; ln -s myfile otherfile ; perl -MCwd=abs_path -le 'print abs_path readlink(shift);' otherfile... trong trường hợp của tôi, tôi thấy: / Users / cito / myfile`. Đã thêm nó vào phản hồi của tôi dưới đây. Chúc mừng.
G. Cito

Câu trả lời:


181

readlink -f thực hiện hai điều:

  1. Nó lặp đi lặp lại dọc theo một chuỗi các liên kết tượng trưng cho đến khi tìm thấy một tập tin thực sự.
  2. Nó trả về tên hợp quy của tập tin đó tức là tên đường dẫn tuyệt đối của nó.

Nếu bạn muốn, bạn chỉ có thể xây dựng một tập lệnh shell sử dụng hành vi liên kết đọc vanilla để đạt được điều tương tự. Đây là một ví dụ. Rõ ràng bạn có thể chèn đoạn này vào tập lệnh của riêng bạn, nơi bạn muốn gọireadlink -f

#!/bin/sh

TARGET_FILE=$1

cd `dirname $TARGET_FILE`
TARGET_FILE=`basename $TARGET_FILE`

# Iterate down a (possible) chain of symlinks
while [ -L "$TARGET_FILE" ]
do
    TARGET_FILE=`readlink $TARGET_FILE`
    cd `dirname $TARGET_FILE`
    TARGET_FILE=`basename $TARGET_FILE`
done

# Compute the canonicalized name by finding the physical path 
# for the directory we're in and appending the target file.
PHYS_DIR=`pwd -P`
RESULT=$PHYS_DIR/$TARGET_FILE
echo $RESULT

Lưu ý rằng điều này không bao gồm bất kỳ xử lý lỗi. Đặc biệt quan trọng, nó không phát hiện các chu kỳ symlink. Một cách đơn giản để làm điều này là đếm số lần bạn đi vòng vòng và thất bại nếu bạn đạt được một số lượng lớn không thể, chẳng hạn như 1.000.

EDITED để sử dụng pwd -Pthay vì $PWD.

Lưu ý rằng tập lệnh này dự kiến ​​sẽ được gọi như ./script_name filename, không -f, thay đổi $1thành $2nếu bạn muốn có thể sử dụng với -f filenamenhư đường dẫn đọc GNU.


1
Theo như tôi có thể nói, điều đó sẽ không hoạt động nếu một đường dẫn cha mẹ của đường dẫn là một liên kết tượng trưng. Ví dụ. nếu foo -> /var/cux, sau đó foo/barsẽ không được giải quyết bar, mặc dù không phải là một liên kết foo.
troelskn

1
Ah. Đúng. Điều đó không đơn giản nhưng bạn có thể cập nhật tập lệnh trên để đối phó với điều đó. Tôi sẽ chỉnh sửa (viết lại, thực sự) câu trả lời tương ứng.
Keith Smith

Vâng, một liên kết có thể là bất cứ nơi nào trong đường dẫn. Tôi đoán kịch bản có thể lặp lại qua từng phần của đường dẫn, nhưng sau đó nó trở nên hơi phức tạp.
troelskn

1
Cảm ơn. Vấn đề là $ PWD đang cung cấp cho chúng tôi thư mục làm việc hợp lý, dựa trên các giá trị trong các liên kết tượng trưng mà chúng tôi đã theo dõi. Chúng ta có thể có được thư mục vật lý thực sự với 'pwd -P' Nó sẽ tính toán nó bằng cách theo đuổi ".." đến tận gốc của hệ thống tệp. Tôi sẽ cập nhật kịch bản trong câu trả lời của tôi cho phù hợp.
Keith Smith

12
Giải pháp tuyệt vời, nhưng nó không xử lý đúng các đường dẫn cần thoát , chẳng hạn như tên tệp hoặc thư mục có dấu cách được nhúng . Để khắc phục điều đó, sử dụng cd "$(dirname "$TARGET_FILE")"TARGET_FILE=$(readlink "$TARGET_FILE")TARGET_FILE=$(basename "$TARGET_FILE")ở các vị trí thích hợp trong đoạn code trên.
mkuity0

438

MacPorts và Homebrew cung cấp gói coreutils chứa greadlink(GNU readlink). Tín dụng cho bài viết của Michael Kallweitt trong mackb.com.

brew install coreutils

greadlink -f file.txt

Nếu bạn đang gọi readlink từ những nơi khác ngoài bash, một giải pháp khác sẽ là remove / usr / bin / readlink sau đó tạo một liên kết đến / usr / local / bin / greadlink.
chinglun

8
Xin đừng, trừ khi bạn a) viết phần mềm cho riêng bạn b) muốn gây rối với người khác. Viết nó theo cách mà nó "chỉ hoạt động" cho bất cứ ai.
kgadek

14
@ching Làm điều này thay vào đó trong của bạn .bashrc:export PATH="/usr/local/opt/coreutils/libexec/gnubin:$PATH"
bfontaine

1
Câu hỏi yêu cầu tương đương trên hệ thống OS X hoặc BSD, điều này không trả lời điều đó. Bạn cũng sẽ phải cài đặt Homebrew. Bạn cũng sẽ cài đặt nhiều thứ ngoài GNU Readlink. Nếu bạn đang viết một tập lệnh cho OS X tiêu chuẩn sẽ cài đặt câu trả lời này sẽ không có ích. Bạn có thể sử dụng kết hợp pwd -Preadlinktrên OS X mà không cần cài đặt bất cứ thứ gì
Jason S

4
Tôi muốn GNU và các tiện ích khác trên máy Mac của tôi mà tôi tin là làm tốt như vậy, không có tiền tố và có thể dễ dàng truy cập từ người dùng của tôi PATH. Đã làm ở trên bắt đầu với brew install <package> --default-names. Nhiều lần một câu hỏi trên trang web này đã được trả lời một chút, hoặc nhiều hơn, ngoài các yêu cầu kỹ thuật của người hỏi, nhưng trong tình huống tương tự, và vẫn rất hữu ích. toporms.net/blog/2013/04/14/NH
Pysis

43

Bạn có thể quan tâm realpath(3)hoặc Python os.path.realpath. Cả hai không hoàn toàn giống nhau; lệnh gọi thư viện C yêu cầu các thành phần đường dẫn trung gian tồn tại, trong khi phiên bản Python thì không.

$ pwd
/tmp/foo
$ ls -l
total 16
-rw-r--r--  1 miles    wheel  0 Jul 11 21:08 a
lrwxr-xr-x  1 miles    wheel  1 Jul 11 20:49 b -> a
lrwxr-xr-x  1 miles    wheel  1 Jul 11 20:49 c -> b
$ python -c 'import os,sys;print(os.path.realpath(sys.argv[1]))' c
/private/tmp/foo/a

Tôi biết bạn nói rằng bạn thích thứ gì đó nhẹ hơn ngôn ngữ kịch bản lệnh khác, nhưng chỉ trong trường hợp biên dịch nhị phân không thể truy cập được, bạn có thể sử dụng Python và ctypes (có sẵn trên Mac OS X 10.5) để thực hiện cuộc gọi thư viện:

#!/usr/bin/python

import ctypes, sys

libc = ctypes.CDLL('libc.dylib')
libc.realpath.restype = ctypes.c_char_p
libc.__error.restype = ctypes.POINTER(ctypes.c_int)
libc.strerror.restype = ctypes.c_char_p

def realpath(path):
    buffer = ctypes.create_string_buffer(1024) # PATH_MAX
    if libc.realpath(path, buffer):
        return buffer.value
    else:
        errno = libc.__error().contents.value
        raise OSError(errno, "%s: %s" % (libc.strerror(errno), buffer.value))

if __name__ == '__main__':
    print realpath(sys.argv[1])

Trớ trêu thay, phiên bản C của kịch bản này phải ngắn hơn. :)


Vâng, realpaththực sự là những gì tôi muốn. Nhưng có vẻ khá lúng túng khi tôi phải biên dịch nhị phân để có được hàm này từ tập lệnh shell.
troelskn

4
Tại sao không sử dụng Python one-liner trong shell script? (Không khác lắm so với cuộc gọi một đường đến readlinkchính nó, phải không?)
Telemachus

3
python -c "import os,sys; print(os.path.realpath(os.path.expanduser(sys.argv[1])))" "${1}"hoạt động với các đường dẫn như'~/.symlink'
Wes Turner

@troelskin Tôi không nhận ra Perl, Python, v.v. đã được "cho phép" !! ... trong trường hợp đó tôi sẽ thêm perl -MCwd=abs_path -le 'print abs_path readlink(shift);'vào câu trả lời của mình :-)
G. Cito

27

Tôi ghét phải thực hiện một triển khai khác, nhưng tôi cần a) triển khai vỏ di động, thuần túy và b) phạm vi kiểm tra đơn vị , vì số lượng các trường hợp cạnh cho một cái gì đó như thế này là không tầm thường .

Xem dự án của tôi trên Github để kiểm tra và mã đầy đủ. Dưới đây là tóm tắt của việc thực hiện:

Như Keith Smith khéo léo chỉ ra, readlink -fthực hiện hai điều: 1) giải quyết đệ quy liên kết theo cách đệ quy và 2) chuẩn hóa kết quả, do đó:

realpath() {
    canonicalize_path "$(resolve_symlinks "$1")"
}

Đầu tiên, việc thực hiện trình giải quyết symlink:

resolve_symlinks() {
    local dir_context path
    path=$(readlink -- "$1")
    if [ $? -eq 0 ]; then
        dir_context=$(dirname -- "$1")
        resolve_symlinks "$(_prepend_path_if_relative "$dir_context" "$path")"
    else
        printf '%s\n' "$1"
    fi
}

_prepend_path_if_relative() {
    case "$2" in
        /* ) printf '%s\n' "$2" ;;
         * ) printf '%s\n' "$1/$2" ;;
    esac 
}

Lưu ý rằng đây là một phiên bản đơn giản hóa của việc thực hiện đầy đủ . Việc thực hiện đầy đủ thêm một kiểm tra nhỏ cho các chu kỳ liên kết tượng trưng , cũng như mát xa đầu ra một chút.

Cuối cùng, chức năng chuẩn hóa một đường dẫn:

canonicalize_path() {
    if [ -d "$1" ]; then
        _canonicalize_dir_path "$1"
    else
        _canonicalize_file_path "$1"
    fi
}   

_canonicalize_dir_path() {
    (cd "$1" 2>/dev/null && pwd -P) 
}           

_canonicalize_file_path() {
    local dir file
    dir=$(dirname -- "$1")
    file=$(basename -- "$1")
    (cd "$dir" 2>/dev/null && printf '%s/%s\n' "$(pwd -P)" "$file")
}

Đó là nó, ít nhiều. Đủ đơn giản để dán vào tập lệnh của bạn, nhưng đủ khó để bạn có thể điên khi dựa vào bất kỳ mã nào không có bài kiểm tra đơn vị cho các trường hợp sử dụng của bạn.


3
Đây không phải là POSIX - nó sử dụng localtừ khóa không phải POSIX
go2null

Điều này không tương đương với readlink -f, cũng không realpath. Giả sử máy Mac có công thức coreutils được cài đặt (vì vậy có greadlinksẵn), hãy thử điều này với ln -s /tmp/linkeddir ~/Documents; greadlink -f /tmp/linkeddir/..in thư mục home của bạn (nó giải quyết được /tmp/linkeddirliên kết trước khi áp dụng các ..mẹ dir ref), nhưng mã của bạn sản xuất /private/tmp/như /tmp/linkeddir/..không phải là một liên kết tượng trưng, nhưng -d /tmp/linkeddir/..là đúng và do đó cd /tmp/linkeddir/..sẽ đưa bạn đến /tmp, mà con đường kinh điển là /private/tmp. Mã của bạn làm những gì realpath -L, thay vào đó.
Martijn Pieters

27

Một lớp lót đơn giản trong perl chắc chắn sẽ hoạt động ở hầu hết mọi nơi mà không có bất kỳ sự phụ thuộc bên ngoài nào:

perl -MCwd -e 'print Cwd::abs_path shift' ~/non-absolute/file

Sẽ liên kết biểu tượng.

Cách sử dụng trong một tập lệnh có thể như thế này:

readlinkf(){ perl -MCwd -e 'print Cwd::abs_path shift' "$1";}
ABSPATH="$(readlinkf ./non-absolute/file)"

4
để có được một dòng mới, hãy thêm -l, nhưperl -MCwd -le 'print Cwd::abs_path shift' ~/non-absolute/file
pjvandehaar

26
  1. Cài đặt homebrew
  2. Chạy "brew install coreutils"
  3. Chạy "đường dẫn greadlink -f"

greadlink là đường dẫn đọc gnu thực hiện -f. Bạn có thể sử dụng macports hoặc những người khác, tôi thích homebrew.


2
Tôi không thể thấy bất kỳ sự khác biệt nào với câu trả lời được đưa ra trong năm 2010 stackoverflow.com/a/4031502/695671
Jason S

Nếu bạn cần sử dụng các lệnh này với tên thông thường của chúng, bạn có thể thêm thư mục "gnubin" vào PATH của bạn từ bashrc của bạn như: PATH = "/ usr / local / opt / coreutils / libexec / gnubin: $ PATH"
Denis Trofimov

20

Tôi đã tạo một kịch bản gọi là realpath cá nhân trông giống như:

#!/usr/bin/env python
import os.sys
print os.path.realpath(sys.argv[1])

Bạn nên thay đổi điều đó thành sys.argv[1]nếu bạn muốn tập lệnh in giao diện thực của đối số đầu tiên. sys.argv[0]chỉ in đường dẫn thực sự của chính tập lệnh pyton, điều này không hữu ích lắm.
Jakob Egger

17
Đây là phiên bản bí danh, cho ~/.profile:alias realpath="python -c 'import os, sys; print os.path.realpath(sys.argv[1])'"
jwhitlock

Câu trả lời tuyệt vời và @jwhitlock tuyệt vời. Kể từ khi OP trích dẫn readlink -f, đây là phiên bản sửa đổi của bí danh của @jwhitlock để hỗ trợ -fcờ có thể : alias realpath="python -c 'import os, sys; print os.path.realpath(sys.argv[2] if sys.argv[1] == \"-f\" else sys.argv[1])'"
Codeniffer

11

Cái này thì sao?

function readlink() {
  DIR="${1%/*}"
  (cd "$DIR" && echo "$(pwd -P)")
}

Đây là giải pháp duy nhất phù hợp với tôi; Tôi cần phân tích các đường dẫn như ../../../->/
keflavich

điều này không thể hoạt động nếu bạn có một liên kết tượng trưng, /usr/bin/ví dụ như alternativeshệ thống muốn có.
akostadinov

Không hoạt động cho các tập tin cá nhân, chỉ thư mục. GNU readlink hoạt động với các thư mục, tập tin, liên kết tượng trưng, ​​v.v.
Codeniffer

7

Đây là một chức năng shell di động nên hoạt động trong BẤT K K shell vỏ tương đương. Nó sẽ giải quyết dấu chấm đường dẫn tương đối ".. hoặc." và liên kết tượng trưng cho biểu tượng.

Nếu vì lý do nào đó bạn không có lệnh realpath (1) hoặc readlink (1) thì điều này có thể được đặt bí danh.

which realpath || alias realpath='real_path'

Thưởng thức:

real_path () {
  OIFS=$IFS
  IFS='/'
  for I in $1
  do
    # Resolve relative path punctuation.
    if [ "$I" = "." ] || [ -z "$I" ]
      then continue
    elif [ "$I" = ".." ]
      then FOO="${FOO%%/${FOO##*/}}"
           continue
      else FOO="${FOO}/${I}"
    fi

    ## Resolve symbolic links
    if [ -h "$FOO" ]
    then
    IFS=$OIFS
    set `ls -l "$FOO"`
    while shift ;
    do
      if [ "$1" = "->" ]
        then FOO=$2
             shift $#
             break
      fi
    done
    IFS='/'
    fi
  done
  IFS=$OIFS
  echo "$FOO"
}

Ngoài ra, chỉ trong trường hợp bất kỳ ai quan tâm ở đây là cách triển khai tên cơ sở và dirname trong mã shell thuần 100%:

## http://www.opengroup.org/onlinepubs/000095399/functions/dirname.html
# the dir name excludes the least portion behind the last slash.
dir_name () {
  echo "${1%/*}"
}

## http://www.opengroup.org/onlinepubs/000095399/functions/basename.html
# the base name excludes the greatest portion in front of the last slash.
base_name () {
  echo "${1##*/}"
}

Bạn có thể tìm thấy phiên bản cập nhật của mã shell này tại trang web google của tôi: http://sites.google.com.vn/site/jdisnard/realpath

EDIT: Mã này được cấp phép theo các điều khoản của giấy phép 2 điều khoản (kiểu freeBSD). Một bản sao của giấy phép có thể được tìm thấy bằng cách theo siêu liên kết ở trên đến trang web của tôi.


Real_path trả về / tên tệp thay vì / path / to / filename trên Mac OS X Lion trong bash shell.

@Barry Điều tương tự cũng xảy ra trên OSX Snow Leopard. Tệ hơn, các cuộc gọi liên tiếp đến đầu ra nối tiếp real_path ()!
Dominique

@Dominique: không có gì đáng ngạc nhiên, vì bên trong hàm không chạy trong lớp con riêng của nó ... thực sự xấu xí và dễ vỡ.
0xC0000022L

7

FreeBSDOSX có phiên bản statbắt nguồn từ NetBSD.

Bạn có thể điều chỉnh đầu ra bằng các công tắc định dạng (xem các trang hướng dẫn tại các liên kết ở trên).

%  cd  /service
%  ls -tal 
drwxr-xr-x 22 root wheel 27 Aug 25 10:41 ..
drwx------  3 root wheel  8 Jun 30 13:59 .s6-svscan
drwxr-xr-x  3 root wheel  5 Jun 30 13:34 .
lrwxr-xr-x  1 root wheel 30 Dec 13 2013 clockspeed-adjust -> /var/service/clockspeed-adjust
lrwxr-xr-x  1 root wheel 29 Dec 13 2013 clockspeed-speed -> /var/service/clockspeed-speed
% stat -f%R  clockspeed-adjust
/var/service/clockspeed-adjust
% stat -f%Y  clockspeed-adjust
/var/service/clockspeed-adjust

Một số phiên bản OS X stat có thể thiếu -f%Rtùy chọn cho các định dạng. Trong trường hợp này -stat -f%Ycó thể đủ. Các -f%Ytùy chọn sẽ hiển thị các mục tiêu của một liên kết tượng trưng, trong khi-f%R các chương trình tên đường dẫn tuyệt đối tương ứng với tập tin.

BIÊN TẬP:

Nếu bạn có thể sử dụng Perl (Darwin / OS X được cài đặt với các phiên bản gần đây perl) thì:

perl -MCwd=abs_path -le 'print abs_path readlink(shift);' linkedfile.txt

sẽ làm việc.


2
Điều này đúng trên FreeBSD 10, nhưng không phải trên OS X 10.9.
Zanchey

Nắm bắt tốt. Các stat của nhãn hiệu trang trích dẫn là dành cho OS / X 10.9. Các Rtùy chọn để -f%là mất tích. Có thể stat -f%Ycho đầu ra mong muốn. Tôi sẽ điều chỉnh câu trả lời. Lưu ý rằng statcông cụ đã xuất hiện trong FreeBSD trong phiên bản 4.10 và NetBSD trong phiên bản 1.6.
G. Cito

Lệnh Perl không hoạt động chính xác trên 10.15. Cung cấp cho nó một tệp trong thư mục hiện tại và nó cung cấp cho thư mục hiện tại (không có tệp).
Codeniffer

7

Cách dễ nhất để giải quyết vấn đề này và kích hoạt chức năng của đường dẫn đọc trên Mac w / Homebrew được cài đặt hoặc FreeBSD là cài đặt gói 'coreutils'. Cũng có thể cần thiết trên một số bản phân phối Linux và HĐH POSIX khác.

Ví dụ: trong FreeBSD 11, tôi đã cài đặt bằng cách gọi:

# pkg install coreutils

Trên MacOS với Homebrew, lệnh sẽ là:

$ brew install coreutils

Không thực sự chắc chắn tại sao các câu trả lời khác rất phức tạp, đó là tất cả những gì có. Các tập tin không ở một nơi khác, chúng chưa được cài đặt.


7
brew install coreutils --with-default-namesđể cụ thể hơn. Hoặc thay vào đó, bạn sẽ kết thúc với "greadlink" ...
Henk

Tôi không thể thấy bất kỳ sự khác biệt nào đối với các câu trả lời từ 2010 stackoverflow.com/a/4031502/695671 và 2012 stackoverflow.com/a/9918368/695671 Câu hỏi thực sự là "... trên Mac và có thể là các hệ thống dựa trên BSD. tương đương được không? " Tôi không tin rằng đây là một câu trả lời rất hay cho câu hỏi đó. Nhiều lý do, nhưng bạn có thể nhắm mục tiêu các máy Mac OS mà không có khả năng cài đặt bất cứ thứ gì. pwd -Plà khá đơn giản nhưng nó biến mất trong số nhiều câu trả lời khác, bao gồm cả những câu trả lời lặp đi lặp lại, do đó downvote.
Jason S

3

Bắt đầu cập nhật

Đây là một vấn đề thường xuyên đến nỗi chúng tôi đã kết hợp một thư viện Bash 4 để sử dụng miễn phí (Giấy phép MIT) được gọi là realpath-lib . Điều này được thiết kế để mô phỏng readlink -f theo mặc định và bao gồm hai bộ kiểm tra để xác minh (1) rằng nó hoạt động cho một hệ thống unix nhất định và (2) chống lại readlink -f nếu được cài đặt (nhưng điều này không bắt buộc). Ngoài ra, nó có thể được sử dụng để điều tra, xác định và thư giãn sâu, các liên kết tượng trưng bị hỏng và các tham chiếu vòng tròn, vì vậy nó có thể là một công cụ hữu ích để chẩn đoán các vấn đề về thư mục và tệp vật lý hoặc biểu tượng được lồng sâu. Nó có thể được tìm thấy tại github.com hoặc bitbucket.org .

Kết thúc cập nhật

Một giải pháp rất nhỏ gọn và hiệu quả khác không dựa vào bất cứ thứ gì ngoài Bash là:

function get_realpath() {

    [[ ! -f "$1" ]] && return 1 # failure : file does not exist.
    [[ -n "$no_symlinks" ]] && local pwdp='pwd -P' || local pwdp='pwd' # do symlinks.
    echo "$( cd "$( echo "${1%/*}" )" 2>/dev/null; $pwdp )"/"${1##*/}" # echo result.
    return 0 # success

}

Điều này cũng bao gồm một cài đặt môi trường no_symlinkscung cấp khả năng giải quyết các liên kết tượng trưng đến hệ thống vật lý. Miễn là no_symlinksđược đặt thành một cái gì đó, tức là các no_symlinks='on'liên kết tượng trưng sẽ được giải quyết đến hệ thống vật lý. Nếu không, chúng sẽ được áp dụng (cài đặt mặc định).

Điều này sẽ hoạt động trên bất kỳ hệ thống nào cung cấp Bash và sẽ trả về mã thoát tương thích Bash cho mục đích thử nghiệm.


3

Đã có rất nhiều câu trả lời, nhưng không có câu trả lời nào cho tôi cả ... Vì vậy, đây là những gì tôi đang sử dụng.

readlink_f() {
  local target="$1"
  [ -f "$target" ] || return 1 #no nofile

  while [ -L "$target" ]; do
    target="$(readlink "$target")" 
  done
  echo "$(cd "$(dirname "$target")"; pwd -P)/$target"
}

1
Điều này không hoạt động nếu $targetlà một đường dẫn, nó chỉ hoạt động nếu đó là một tệp.
MaxGhost

3

Một cách lười biếng phù hợp với tôi,

$ brew install coreutils
$ ln -s /usr/local/bin/greadlink /usr/local/bin/readlink
$ which readlink
/usr/local/bin/readlink
/usr/bin/readlink

1

Muộn còn hơn không, tôi cho là vậy. Tôi đã có động lực để phát triển điều này một cách cụ thể vì các tập lệnh Fedora của tôi không hoạt động trên Mac. Vấn đề là phụ thuộc và Bash. Máy Mac không có chúng hoặc nếu có, chúng thường ở một nơi khác (một con đường khác). Thao tác đường dẫn phụ thuộc trong tập lệnh Bash đa nền tảng là vấn đề đau đầu nhất và rủi ro bảo mật ở mức tồi tệ nhất - vì vậy tốt nhất nên tránh sử dụng chúng, nếu có thể.

Hàm get_realpath () bên dưới là đơn giản, trung tâm Bash và không yêu cầu phụ thuộc. Tôi chỉ sử dụng các lệnh nội trú Bash vangcd . Nó cũng khá an toàn, vì mọi thứ đều được kiểm tra ở từng giai đoạn và nó trả về các điều kiện lỗi.

Nếu bạn không muốn theo liên kết tượng trưng, ​​hãy đặt set -P ở phía trước tập lệnh, nhưng nếu không thì cd sẽ giải quyết các liên kết tượng trưng theo mặc định. Nó đã được thử nghiệm với các đối số tệp là {perfect | người thân | liên kết | local} và nó trả về đường dẫn tuyệt đối đến tập tin. Cho đến nay chúng tôi không có bất kỳ vấn đề với nó.

function get_realpath() {

if [[ -f "$1" ]]
then
    # file *must* exist
    if cd "$(echo "${1%/*}")" &>/dev/null
    then
        # file *may* not be local
        # exception is ./file.ext
        # try 'cd .; cd -;' *works!*
        local tmppwd="$PWD"
        cd - &>/dev/null
    else
        # file *must* be local
        local tmppwd="$PWD"
    fi
else
    # file *cannot* exist
    return 1 # failure
fi

# reassemble realpath
echo "$tmppwd"/"${1##*/}"
return 0 # success

}

Bạn có thể kết hợp điều này với các hàm khác get_dirname, get_filename, get_stemname và validate_path. Chúng có thể được tìm thấy tại kho GitHub của chúng tôi dưới dạng realpath-lib (tiết lộ đầy đủ - đây là sản phẩm của chúng tôi nhưng chúng tôi cung cấp miễn phí cho cộng đồng mà không có bất kỳ hạn chế nào). Nó cũng có thể phục vụ như một công cụ hướng dẫn - nó được ghi chép lại.

Chúng tôi đã cố gắng hết sức để áp dụng cái gọi là thực tiễn 'Bash hiện đại', nhưng Bash là một chủ đề lớn và tôi chắc chắn sẽ luôn có chỗ để cải thiện. Nó yêu cầu Bash 4+ nhưng có thể được tạo ra để hoạt động với các phiên bản cũ hơn nếu chúng vẫn còn.


1

Vì công việc của tôi được sử dụng bởi những người không phải BSD Linux cũng như macOS, tôi đã chọn sử dụng các bí danh này trong các tập lệnh xây dựng của chúng tôi ( sedbao gồm vì nó có vấn đề tương tự):

##
# If you're running macOS, use homebrew to install greadlink/gsed first:
#   brew install coreutils
#
# Example use:
#   # Gets the directory of the currently running script
#   dotfilesDir=$(dirname "$(globalReadlink -fm "$0")")
#   alias al='pico ${dotfilesDir}/aliases.local'
##

function globalReadlink () {
  # Use greadlink if on macOS; otherwise use normal readlink
  if [[ $OSTYPE == darwin* ]]; then
    greadlink "$@"
  else
    readlink "$@"
  fi
}

function globalSed () {
  # Use gsed if on macOS; otherwise use normal sed
  if [[ $OSTYPE == darwin* ]]; then
    gsed "$@"
  else
    sed "$@"
  fi
}

Kiểm tra tùy chọn bạn có thể thêm để tự động cài đặt phụ thuộc homebrew + coreutils :

if [[ "$OSTYPE" == "darwin"* ]]; then
  # Install brew if needed
  if [ -z "$(which brew)" ]; then 
    /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"; 
  fi
  # Check for coreutils
  if [ -z "$(brew ls coreutils)" ]; then
    brew install coreutils
  fi
fi

Tôi cho rằng để thực sự "toàn cầu", nó cần kiểm tra những người khác ... nhưng điều đó có lẽ đã tiến gần đến mốc 80/20.


1

Giải trình

coreutils là brewgói cài đặt các tiện ích lõi GNU / Linux tương ứng với việc triển khai Mac OSX của chúng để bạn có thể sử dụng các tiện ích đó

Bạn có thể tìm thấy các chương trình hoặc tiện ích trên hệ thống mac osx của mình có vẻ giống với lõi Linux ("Tiện ích cốt lõi") nhưng chúng khác nhau theo một số cách (chẳng hạn như có các cờ khác nhau).

Điều này là do việc triển khai Mac OSX của các công cụ này là khác nhau. Để có được hành vi giống như GNU / Linux ban đầu, bạn có thể cài đặt coreutilsgói thông qua brewhệ thống quản lý gói.

Điều này sẽ cài đặt các tiện ích cốt lõi tương ứng, tiền tố bởi g. Ví dụ readlink, bạn sẽ tìm thấy một greadlinkchương trình tương ứng .

Để readlinkthực hiện như triển khai GNU readlink( greadlink), bạn có thể tạo một bí danh đơn giản sau khi bạn cài đặt coreutils.

Thực hiện

  1. Cài đặt bia

Thực hiện theo các hướng dẫn tại https://brew.sh/

  1. Cài đặt gói coreutils

brew install coreutils

  1. Tạo bí danh

Bạn có thể đặt bí danh của mình trong ~ / .bashrc, ~ / .bash_profile hoặc bất cứ nơi nào bạn được sử dụng để giữ bí danh bash của mình. Cá nhân tôi giữ của tôi trong ~ / .bashrc

alias readlink=greadlink

Bạn có thể tạo các bí danh tương tự cho các coreutils khác như gmv, gdu, gdf, v.v. Nhưng hãy cẩn thận rằng hành vi GNU trên máy mac có thể gây nhầm lẫn cho những người khác đã từng làm việc với coreutils gốc hoặc có thể hành xử theo những cách không mong muốn trên hệ thống mac của bạn.


0

Tôi đã viết một tiện ích realpath cho OS X có thể cung cấp kết quả tương tự như readlink -f.


Đây là một ví dụ:

(jalcazar@mac tmp)$ ls -l a
lrwxrwxrwx 1 jalcazar jalcazar 11  8 25 19:29 a -> /etc/passwd

(jalcazar@mac tmp)$ realpath a
/etc/passwd


Nếu bạn đang sử dụng MacPorts, bạn có thể cài đặt nó bằng lệnh sau : sudo port selfupdate && sudo port install realpath.


0

Người phụ thuộc nền tảng thực sự cũng sẽ là R-onliner này

readlink(){ RScript -e "cat(normalizePath(commandArgs(T)[1]))" "$1";}

Để thực sự bắt chước readlink -f <path>, $ 2 thay vì $ 1 sẽ cần được sử dụng.


Ngoại trừ R hầu như không có trên bất kỳ hệ thống nào theo mặc định. Chưa có trên Mac (kể từ 10.15), đây là câu hỏi gì.
mã số

0

readlink -fTriển khai tuân thủ POSIX cho tập lệnh shell POSIX

https://github.com/ko1nksm/readlinkf

Đây là tuân thủ POSIX (không có bashism). Nó không sử dụng readlinkcũng không realpath. Tôi đã xác minh rằng nó hoàn toàn giống nhau bằng cách so sánh với GNU readlink -f(xem kết quả kiểm tra ). Nó có xử lý lỗi và hiệu suất tốt. Bạn có thể thay thế một cách an toàn từ readlink -f. Giấy phép là Muff, vì vậy bạn có thể sử dụng nó cho bất kỳ dự án nào.

# POSIX compliant version
readlinkf_posix() {
  [ "${1:-}" ] || return 1
  target=$1 max_symlinks=10 CDPATH=''

  [ -e "${target%/}" ] || target=${1%"${1##*[!/]}"} # trim trailing slashes
  [ -d "${target:-/}" ] && target="$target/"

  cd -P . 2>/dev/null || return 1
  while [ "$max_symlinks" -gt 0 ] && max_symlinks=$((max_symlinks - 1)); do
    if [ ! "$target" = "${target%/*}" ]; then
      cd -P "${target%/*}/" 2>/dev/null || break
      target=${target##*/}
    fi

    if [ ! -L "$target" ]; then
      target="${PWD%/}${target:+/}$target"
      printf '%s\n' "${target:-/}"
      return 0
    fi

    # `ls -dl` format: "%s %u %s %s %u %s %s -> %s\n",
    #   <file mode>, <number of links>, <owner name>, <group name>,
    #   <size>, <date and time>, <pathname of link>, <contents of link>
    # https://pubs.opengroup.org/onlinepubs/9699919799/utilities/ls.html
    link=$(ls -dl "$target" 2>/dev/null) || break
    target=${link#*" $target -> "}
  done
  return 1
}

Vui lòng tham khảo mã mới nhất. Nó có thể một số cố định.


-2

Perl có chức năng đọc liên kết (ví dụ: Làm cách nào để sao chép các liên kết tượng trưng trong Perl? ). Điều này hoạt động trên hầu hết các nền tảng, bao gồm cả OS X:

perl -e "print readlink '/path/to/link'"

Ví dụ:

$ mkdir -p a/b/c
$ ln -s a/b/c x
$ perl -e "print readlink 'x'"
a/b/c

5
Điều này thực hiện tương tự như readlinkkhông có -ftùy chọn - không đệ quy, không có đường dẫn tuyệt đối - vì vậy bạn cũng có thể sử dụng readlinktrực tiếp.
mkuity0

-2

Câu trả lời từ @Keith Smith đưa ra một vòng lặp vô hạn.

Đây là câu trả lời của tôi, mà tôi chỉ sử dụng trên SunOS (SunOS bỏ lỡ rất nhiều lệnh POSIX và GNU).

Đây là tệp tập lệnh mà bạn phải đặt vào một trong các thư mục $ PATH của mình:

#!/bin/sh
! (($#)) && echo -e "ERROR: readlink <link to analyze>" 1>&2 && exit 99

link="$1"
while [ -L "$link" ]; do
  lastLink="$link"
  link=$(/bin/ls -ldq "$link")
  link="${link##* -> }"
  link=$(realpath "$link")
  [ "$link" == "$lastlink" ] && echo -e "ERROR: link loop detected on $link" 1>&2 && break
done

echo "$link"


-5

Đường dẫn đến đường dẫn khác nhau giữa hệ thống của tôi và của bạn. Vui lòng thử chỉ định đường dẫn đầy đủ:

/sw/sbin/readlink -f


1
Và những gì - chính xác - là sự khác biệt giữa / sw / sbin và / usr / bin? Và tại sao hai nhị phân khác nhau?
troelskn

4
Aha .. vì vậy fink chứa một sự thay thế cho readlinkđó là tương thích gnu. Điều đó thật tuyệt khi biết, nhưng nó không giải quyết được vấn đề của tôi, vì tôi cần tập lệnh của mình để chạy trên máy của những người khác và tôi không thể yêu cầu họ cài đặt fink cho điều đó.
troelskn

Bạn không trả lời câu hỏi ban đầu. Hãy viết lại câu trả lời của bạn.
Jaime Hablutzel
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.