Đường dẫn tuyệt đối của tập lệnh Bash với OS X


98

Tôi đang cố gắng lấy đường dẫn tuyệt đối đến tập lệnh hiện đang chạy trên OS X.

Tôi đã thấy nhiều câu trả lời dành cho readlink -f $0. Tuy nhiên, vì OS X readlinkgiống với BSD nên nó không hoạt động (nó hoạt động với phiên bản của GNU).

Có một giải pháp tối ưu cho vấn đề này không?





16
$( cd "$(dirname "$0")" ; pwd -P )
Jason S,

Câu trả lời:


88

Có một realpath()hàm C sẽ thực hiện công việc, nhưng tôi không thấy bất kỳ thứ gì có sẵn trên dòng lệnh. Đây là một sự thay thế nhanh chóng và bẩn:

#!/bin/bash

realpath() {
    [[ $1 = /* ]] && echo "$1" || echo "$PWD/${1#./}"
}

realpath "$0"

Điều này in ra nguyên văn đường dẫn nếu nó bắt đầu bằng a /. Nếu không, nó phải là một con đường tương đối, vì vậy nó $PWDđi trước phía trước. Phần #./tách ra ./khỏi mặt trước của $1.


1
Tôi cũng nhận thấy hàm C nhưng không thể tìm thấy bất kỳ nhị phân tương ứng hoặc bất kỳ thứ gì. Dù sao, chức năng của bạn vừa vặn phù hợp với nhu cầu của tôi. Cảm ơn!
The Mighty Rubber Duck,

7
Lưu ý rằng đây không phải là liên kết biểu tượng bỏ qua hội nghị.
Adam Vandenberg

17
realpath ../somethingtrở lại$PWD/../something
Lri

Điều này phù hợp với tôi, nhưng tôi đã đổi tên nó thành "realpath_osx ()" vì TÔI CÓ THỂ cần gửi tập lệnh bash này tới một trình bao linux và không muốn nó va chạm với đường dẫn thực "thực"! (Tôi chắc rằng có một cách thanh lịch hơn, nhưng tôi là một n00b bash.)
Andrew Theken

8
command -v realpath >/dev/null 2>&1 || realpath() { ... }
Kara Brightwell

115

Ba bước đơn giản này sẽ giải quyết vấn đề này và nhiều vấn đề khác của OS X:

  1. Cài đặt Homebrew
  2. brew install coreutils
  3. grealpath .

(3) có thể được thay đổi thành chỉ realpath, xem (2) đầu ra


3
Điều này giải quyết được vấn đề, nhưng yêu cầu cài đặt nội dung bạn có thể không muốn hoặc không cần hoặc có thể không có trên hệ thống đích, tức là OS X
Jason S

6
@JasonS GNU Coreutils quá tốt để không sử dụng. Nó bao gồm rất nhiều tiện ích tuyệt vời. Trên thực tế, nhân Linux sẽ vô dụng nếu không có nó và tại sao một số người lại gọi nó là GNU / Linux. Coreutils thật tuyệt vời. câu trả lời xuất sắc đây sẽ là câu trả lời được chọn.

7
@omouse Câu hỏi đề cập cụ thể đến 'một giải pháp độc đáo' (dành cho OS X). Bất kể coreutils tuyệt vời như thế nào, nó không phải là 'vượt trội' trên OS X vì vậy câu trả lời không phải là một câu trả lời hay. Vấn đề không phải là coreutils tốt như thế nào hay nó tốt hơn những gì trên OS X. Có những giải pháp 'out of the box' $( cd "$(dirname "$0")" ; pwd -P )phù hợp với tôi.
Jason S,

2
Một trong những "tính năng" của OS X là thư mục và tên tệp không phân biệt chữ hoa chữ thường. Kết quả là nếu bạn có một thư mục được gọi XXXvà một số thư mục cd xxxsau đó pwdsẽ trả về .../xxx. Ngoại trừ câu trả lời này, tất cả các giải pháp trên đều trở lại xxxkhi những gì bạn thực sự muốn XXX. Cảm ơn bạn!
Andrew

1
Tôi không biết sao chép, dán và sáng tạo lại mã liên tục tốt hơn giải pháp quản lý gói hiện có. Nếu bạn cần realpath, thì điều gì sẽ xảy ra khi bạn gần như chắc chắn sẽ cần các mặt hàng khác từ đó coreutils? Viết lại các hàm đó trong bash? : P
Ezekiel Victor

28

Ặc. Tôi thấy các câu trả lời trước hơi mong muốn vì một vài lý do: cụ thể là chúng không giải quyết nhiều cấp độ liên kết tượng trưng và chúng cực kỳ "Bash-y". Mặc dù câu hỏi ban đầu yêu cầu rõ ràng "tập lệnh Bash", nó cũng đề cập đến BSD giống như Mac OS X. Không phải GNU readlink. Vì vậy, đây là một nỗ lực về tính di động hợp lý (tôi đã kiểm tra nó với bash là 'sh' và dấu gạch ngang), giải quyết một số liên kết tượng trưng tùy ý; và nó cũng sẽ hoạt động với khoảng trắng trong (các) đường dẫn, mặc dù tôi không chắc chắn về hành vi nếu có khoảng trắng tên cơ sở của chính tiện ích, vì vậy có thể, ừm, tránh điều đó?

#!/bin/sh
realpath() {
  OURPWD=$PWD
  cd "$(dirname "$1")"
  LINK=$(readlink "$(basename "$1")")
  while [ "$LINK" ]; do
    cd "$(dirname "$LINK")"
    LINK=$(readlink "$(basename "$1")")
  done
  REALPATH="$PWD/$(basename "$1")"
  cd "$OURPWD"
  echo "$REALPATH"
}
realpath "$@"

Hy vọng rằng có thể có ích cho ai đó.


1
Tôi chỉ khuyên bạn nên sử dụng localcho các biến được xác định bên trong hàm để không gây ô nhiễm không gian tên chung. Vd local OURPWD=.... Hoạt động ít nhất cho bash.
Michael Paesold

Ngoài ra, mã không nên sử dụng chữ hoa cho các biến riêng. Các biến chữ hoa được dành riêng cho việc sử dụng hệ thống.
tripleee

Cảm ơn vì kịch bản. Trong trường hợp liên kết (s) và các tập tin thực có tên cơ sở khác nhau nó có lẽ sẽ là một ý tưởng tốt để thêm một BASENAME=$(basename "$LINK") bên trong thời gian và sử dụng rằng trong setter LINK thứ hai và realpath setter
stroborobo

Điều này không xử lý các liên kết tượng trưng và ..các tham chiếu chính theo cách realpathsẽ làm. Với homebrew coreutilsđược cài đặt, hãy thử ln -s /var/log /tmp/linkexamplesau đó realpath /tmp/linkexample/../; bản in này /private/var. Nhưng hàm của bạn tạo ra /tmp/linkexample/..thay vì đó ..không phải là một liên kết tượng trưng.
Martijn Pieters

10

Một biến thể thân thiện với dòng lệnh hơn của giải pháp Python:

python -c "import os; print(os.path.realpath('$1'))"

2
Chỉ trong trường hợp bất cứ ai cũng đủ điên để bắt đầu một thông dịch viên python cho một lệnh duy nhất ...
Bachsau

Tôi đã đủ điên, nhưng đã được sử dụngpython -c "import os; import sys; print(os.path.realpath(sys.argv[1]))"
Alex Chamberlain

7

Tôi đang tìm kiếm giải pháp để sử dụng trong tập lệnh cung cấp hệ thống, tức là chạy trước khi Homebrew thậm chí được cài đặt. Thiếu một giải pháp thích hợp, tôi chỉ muốn tải nhiệm vụ sang một ngôn ngữ đa nền tảng, ví dụ: Perl:

script_abspath=$(perl -e 'use Cwd "abs_path"; print abs_path(@ARGV[0])' -- "$0")

Thường thì những gì chúng tôi thực sự muốn là thư mục chứa:

here=$(perl -e 'use File::Basename; use Cwd "abs_path"; print dirname(abs_path(@ARGV[0]));' -- "$0")

Tuyệt quá! Perl là tốt cho chi phí nhỏ! Bạn có thể đơn giản hóa phiên bản đầu tiên thành FULLPATH=$(perl -e "use Cwd 'abs_path'; print abs_path('$0')"). Bất kỳ lý do chống lại?
F Pereira

@FPereira Tạo mã chương trình từ chuỗi không thoát do người dùng cung cấp không bao giờ là một ý tưởng hay. ''không chống đạn. $0Ví dụ: nó bị hỏng nếu chứa một trích dẫn duy nhất. Một ví dụ rất đơn giản: hãy thử phiên bản của bạn trong /tmp/'/test.shvà gọi /tmp/'/test.shtheo đường dẫn đầy đủ của nó.
4ae1e1

Hoặc đơn giản hơn /tmp/'.sh,.
4ae1e1

6

Kể từ khi có một realpath như những người khác có ra nhọn:

// realpath.c
#include <stdio.h>
#include <stdlib.h>

int main (int argc, char* argv[])
{
  if (argc > 1) {
    for (int argIter = 1; argIter < argc; ++argIter) {
      char *resolved_path_buffer = NULL;
      char *result = realpath(argv[argIter], resolved_path_buffer);

      puts(result);

      if (result != NULL) {
        free(result);
      }
    }
  }

  return 0;
}

Makefile:

#Makefile
OBJ = realpath.o

%.o: %.c
      $(CC) -c -o $@ $< $(CFLAGS)

realpath: $(OBJ)
      gcc -o $@ $^ $(CFLAGS)

Sau đó, biên dịch makevà đưa vào một liên kết mềm với:
ln -s $(pwd)/realpath /usr/local/bin/realpath


chúng ta có thể làm gì gcc realpath.c -o /usr/local/bin/realpath?
Alexander Mills

1
@AlexanderMills Bạn không nên chạy trình biên dịch dưới dạng root; và sau đó nếu bạn không, bạn sẽ không có đặc quyền để ghi vào/usr/local/bin
tripleee

6

Sử dụng Python để lấy nó:

#!/usr/bin/env python
import os
import sys

print(os.path.realpath(sys.argv[1]))

2

Trên macOS, giải pháp duy nhất mà tôi tìm thấy để xử lý các liên kết tượng trưng một cách đáng tin cậy là sử dụng realpath. Vì điều này yêu cầu brew install coreutils, tôi chỉ tự động hóa bước đó. Việc triển khai của tôi trông như thế này:

#!/usr/bin/env bash

set -e

if ! which realpath >&/dev/null; then
  if ! which brew >&/dev/null; then
    msg="ERROR: This script requires brew. See https://brew.sh for installation instructions."
    echo "$(tput setaf 1)$msg$(tput sgr0)" >&2
    exit 1
  fi
  echo "Installing coreutils/realpath"
  brew install coreutils >&/dev/null
fi

thisDir=$( dirname "`realpath "$0"`" )
echo "This script is run from \"$thisDir\""


Lỗi này nếu họ chưa brewcài đặt, nhưng bạn cũng có thể cài đặt nó. Tôi chỉ không cảm thấy thoải mái khi tự động hóa thứ gì đó cuộn mã ruby ​​tùy ý từ mạng.

Lưu ý rằng đây là một biến thể tự động đối với câu trả lời của Oleg Mikheev .


Một bài kiểm tra quan trọng

Một thử nghiệm tốt của bất kỳ giải pháp nào trong số này là:

  1. đặt mã trong một tệp script ở đâu đó
  2. trong một thư mục khác, liên kết biểu tượng ( ln -s) với tệp đó
  3. chạy tập lệnh từ liên kết biểu tượng đó

Giải pháp có bỏ qua liên kết tượng trưng và cung cấp cho bạn thư mục gốc không? Nếu vậy, nó hoạt động.


2
abs_path () {    
   echo "$(cd $(dirname "$1");pwd)/$(basename "$1")"
}

dirnamesẽ cung cấp tên thư mục của /path/to/file, tức là /path/to.

cd /path/to; pwd đảm bảo rằng đường dẫn là tuyệt đối.

basenamesẽ chỉ cung cấp tên tệp /path/to/file, tức là file.


Mặc dù mã này có thể trả lời câu hỏi, nhưng việc cung cấp thêm ngữ cảnh liên quan đến lý do và / hoặc cách mã này trả lời câu hỏi sẽ cải thiện giá trị lâu dài của nó.
Igor F.

1

Vì vậy, như bạn có thể thấy ở trên, tôi đã chụp ảnh này khoảng 6 tháng trước. Tôi hoàn toàn quên điều đó cho đến khi tôi thấy mình lại cần một thứ tương tự. Tôi hoàn toàn bị sốc thấy nó thô sơ đến mức nào; Tôi đã tự dạy mình viết mã khá chuyên sâu trong khoảng một năm nay, nhưng tôi thường cảm thấy có lẽ mình chưa học được gì khi mọi thứ đang ở mức tồi tệ nhất.

Tôi sẽ loại bỏ 'giải pháp' ở trên, nhưng tôi thực sự thích nó giống như một bản ghi về số lượng tôi thực sự đã học được trong vài tháng qua.

Nhưng tôi lạc đề. Tôi đã ngồi xuống và làm việc với nó vào đêm qua. Giải thích trong các bình luận phải là đủ. Nếu bạn muốn theo dõi bản sao tôi đang tiếp tục làm việc, bạn có thể theo dõi ý chính này. Điều này có thể làm những gì bạn cần.

#!/bin/sh # dash bash ksh # !zsh (issues). G. Nixon, 12/2013. Public domain.

## 'linkread' or 'fullpath' or (you choose) is a little tool to recursively
## dereference symbolic links (ala 'readlink') until the originating file
## is found. This is effectively the same function provided in stdlib.h as
## 'realpath' and on the command line in GNU 'readlink -f'.

## Neither of these tools, however, are particularly accessible on the many
## systems that do not have the GNU implementation of readlink, nor ship
## with a system compiler (not to mention the requisite knowledge of C).

## This script is written with portability and (to the extent possible, speed)
## in mind, hence the use of printf for echo and case statements where they
## can be substituded for test, though I've had to scale back a bit on that.

## It is (to the best of my knowledge) written in standard POSIX shell, and
## has been tested with bash-as-bin-sh, dash, and ksh93. zsh seems to have
## issues with it, though I'm not sure why; so probably best to avoid for now.

## Particularly useful (in fact, the reason I wrote this) is the fact that
## it can be used within a shell script to find the path of the script itself.
## (I am sure the shell knows this already; but most likely for the sake of
## security it is not made readily available. The implementation of "$0"
## specificies that the $0 must be the location of **last** symbolic link in
## a chain, or wherever it resides in the path.) This can be used for some
## ...interesting things, like self-duplicating and self-modifiying scripts.

## Currently supported are three errors: whether the file specified exists
## (ala ENOENT), whether its target exists/is accessible; and the special
## case of when a sybolic link references itself "foo -> foo": a common error
## for beginners, since 'ln' does not produce an error if the order of link
## and target are reversed on the command line. (See POSIX signal ELOOP.)

## It would probably be rather simple to write to use this as a basis for
## a pure shell implementation of the 'symlinks' util included with Linux.

## As an aside, the amount of code below **completely** belies the amount
## effort it took to get this right -- but I guess that's coding for you.

##===-------------------------------------------------------------------===##

for argv; do :; done # Last parameter on command line, for options parsing.

## Error messages. Use functions so that we can sub in when the error occurs.

recurses(){ printf "Self-referential:\n\t$argv ->\n\t$argv\n" ;}
dangling(){ printf "Broken symlink:\n\t$argv ->\n\t"$(readlink "$argv")"\n" ;}
errnoent(){ printf "No such file: "$@"\n" ;} # Borrow a horrible signal name.

# Probably best not to install as 'pathfull', if you can avoid it.

pathfull(){ cd "$(dirname "$@")"; link="$(readlink "$(basename "$@")")"

## 'test and 'ls' report different status for bad symlinks, so we use this.

 if [ ! -e "$@" ]; then if $(ls -d "$@" 2>/dev/null) 2>/dev/null;  then
    errnoent 1>&2; exit 1; elif [ ! -e "$@" -a "$link" = "$@" ];   then
    recurses 1>&2; exit 1; elif [ ! -e "$@" ] && [ ! -z "$link" ]; then
    dangling 1>&2; exit 1; fi
 fi

## Not a link, but there might be one in the path, so 'cd' and 'pwd'.

 if [ -z "$link" ]; then if [ "$(dirname "$@" | cut -c1)" = '/' ]; then
   printf "$@\n"; exit 0; else printf "$(pwd)/$(basename "$@")\n"; fi; exit 0
 fi

## Walk the symlinks back to the origin. Calls itself recursivly as needed.

 while [ "$link" ]; do
   cd "$(dirname "$link")"; newlink="$(readlink "$(basename "$link")")"
   case "$newlink" in
    "$link") dangling 1>&2 && exit 1                                       ;;
         '') printf "$(pwd)/$(basename "$link")\n"; exit 0                 ;;
          *) link="$newlink" && pathfull "$link"                           ;;
   esac
 done
 printf "$(pwd)/$(basename "$newlink")\n"
}

## Demo. Install somewhere deep in the filesystem, then symlink somewhere 
## else, symlink again (maybe with a different name) elsewhere, and link
## back into the directory you started in (or something.) The absolute path
## of the script will always be reported in the usage, along with "$0".

if [ -z "$argv" ]; then scriptname="$(pathfull "$0")"

# Yay ANSI l33t codes! Fancy.
 printf "\n\033[3mfrom/as: \033[4m$0\033[0m\n\n\033[1mUSAGE:\033[0m   "
 printf "\033[4m$scriptname\033[24m [ link | file | dir ]\n\n         "
 printf "Recursive readlink for the authoritative file, symlink after "
 printf "symlink.\n\n\n         \033[4m$scriptname\033[24m\n\n        "
 printf " From within an invocation of a script, locate the script's "
 printf "own file\n         (no matter where it has been linked or "
 printf "from where it is being called).\n\n"

else pathfull "$@"
fi

1
Liên kết ý chính dường như bị hỏng.
Alex

câu trả lời này hoạt động: stackoverflow.com/a/46772980/1223975 .... bạn chỉ nên sử dụng câu trả lời đó.
Alexander Mills

Lưu ý rằng việc triển khai của bạn không giải quyết các liên kết tượng trưng trước các ..tham chiếu gốc; Ví dụ: /foo/link_to_other_directory/..được giải quyết /foothay vì là cha của bất kỳ đường dẫn nào mà liên kết biểu tượng /foo/link_to_other_directorytrỏ đến. readlink -frealpathgiải quyết từng thành phần đường dẫn bắt đầu từ gốc và cập nhật các mục tiêu liên kết ưu tiên cho phần còn lại vẫn đang được xử lý. Tôi đã thêm một câu trả lời cho câu hỏi này để thực hiện lại logic đó.
Martijn Pieters

1

realpath dành cho Mac OS X

realpath() {
    path=`eval echo "$1"`
    folder=$(dirname "$path")
    echo $(cd "$folder"; pwd)/$(basename "$path"); 
}

Ví dụ với đường dẫn liên quan:

realpath "../scripts/test.sh"

Ví dụ với thư mục chính

realpath "~/Test/../Test/scripts/test.sh"

1
thoải mái giải pháp đơn giản, tôi chỉ tìm thấy một caveat, khi gọi nó với ..nó không tạo ra các câu trả lời đúng, vì vậy tôi đã thêm một tấm séc nếu con đường nhất định là một thư mục: if test -d $path ; then echo $(cd "$path"; pwd) ; else [...]
Herbert

Không làm việc cho tôi, trong khi "$(dirname $(dirname $(realpath $0)))"làm việc, vì vậy cần cái gì khác ...
Alexander Mills

Việc sử dụng vô íchecho cũng không nên được lặp lại.
tripleee

Điều này không thực sự giải quyết các liên kết tượng trưng theo cách realpath. Nó giải quyết các ..tham chiếu gốc trước khi giải quyết các liên kết tượng trưng, ​​không phải sau; thử điều này với homebrew coreutilsđược cài đặt, tạo liên kết với ln -s /var/log /tmp/linkexamplesau đó chạy realpath /tmp/linkexample/../; bản in này /private/var. Nhưng hàm của bạn tạo ra /tmp/linkexample/..thay thế, vì pwdvẫn hiển thị /tmp/linkexamplesau cd.
Martijn Pieters

0

Điều này dường như hoạt động đối với OSX, không yêu cầu bất kỳ mã nhị phân nào và được lấy từ đây

function normpath() {
  # Remove all /./ sequences.
  local path=${1//\/.\//\/}

  # Remove dir/.. sequences.
  while [[ $path =~ ([^/][^/]*/\.\./) ]]; do
    path=${path/${BASH_REMATCH[0]}/}
  done
  echo $path
}

0

Tôi thích điều này:

#!/usr/bin/env bash
function realpath() {
    local _X="$PWD"
    local _LNK=$1
    cd "$(dirname "$_LNK")"
    if [ -h "$_LNK" ]; then
        _LNK="$(readlink "$_LNK")"
        cd "$(dirname "$_LNK")"
    fi
    echo "$PWD/$(basename "$_LNK")"
    cd "$_X"
}

0

Tôi cần một bản realpaththay thế trên OS X, một bản thay thế hoạt động chính xác trên các đường dẫn có liên kết tượng trưng và tham chiếu gốc giống như readlink -fmuốn . Điều này bao gồm việc giải quyết các liên kết tượng trưng trong đường dẫn trước khi giải quyết các tham chiếu gốc; Ví dụ: nếu bạn đã cài đặt coreutilschai homebrew , hãy chạy:

$ ln -s /var/log/cups /tmp/linkeddir  # symlink to another directory
$ greadlink -f /tmp/linkeddir/..      # canonical path of the link parent
/private/var/log

Lưu ý rằng readlink -fđã giải quyết /tmp/linkeddir trước khi giải quyết ..tham chiếu dir mẹ. Tất nhiên, readlink -ftrên Mac cũng không có .

Vì vậy, là một phần của quá trình triển khai bash đối với realpathtôi, tôi đã triển khai lại những gì mà một lệnh canonicalize_filename_mode(path, CAN_ALL_BUT_LAST)gọi hàm GNUlib thực hiện , trong Bash 3.2; đây cũng là lệnh gọi hàm mà GNU readlink -fthực hiện:

# shellcheck shell=bash
set -euo pipefail

_contains() {
    # return true if first argument is present in the other arguments
    local elem value

    value="$1"
    shift

    for elem in "$@"; do 
        if [[ $elem == "$value" ]]; then
            return 0
        fi
    done
    return 1
}

_canonicalize_filename_mode() {
    # resolve any symlink targets, GNU readlink -f style
    # where every path component except the last should exist and is
    # resolved if it is a symlink. This is essentially a re-implementation
    # of canonicalize_filename_mode(path, CAN_ALL_BUT_LAST).
    # takes the path to canonicalize as first argument

    local path result component seen
    seen=()
    path="$1"
    result="/"
    if [[ $path != /* ]]; then  # add in current working dir if relative
        result="$PWD"
    fi
    while [[ -n $path ]]; do
        component="${path%%/*}"
        case "$component" in
            '') # empty because it started with /
                path="${path:1}" ;;
            .)  # ./ current directory, do nothing
                path="${path:1}" ;;
            ..) # ../ parent directory
                if [[ $result != "/" ]]; then  # not at the root?
                    result="${result%/*}"      # then remove one element from the path
                fi
                path="${path:2}" ;;
            *)
                # add this component to the result, remove from path
                if [[ $result != */ ]]; then
                    result="$result/"
                fi
                result="$result$component"
                path="${path:${#component}}"
                # element must exist, unless this is the final component
                if [[ $path =~ [^/] && ! -e $result ]]; then
                    echo "$1: No such file or directory" >&2
                    return 1
                fi
                # if the result is a link, prefix it to the path, to continue resolving
                if [[ -L $result ]]; then
                    if _contains "$result" "${seen[@]+"${seen[@]}"}"; then
                        # we've seen this link before, abort
                        echo "$1: Too many levels of symbolic links" >&2
                        return 1
                    fi
                    seen+=("$result")
                    path="$(readlink "$result")$path"
                    if [[ $path = /* ]]; then
                        # if the link is absolute, restart the result from /
                        result="/"
                    elif [[ $result != "/" ]]; then
                        # otherwise remove the basename of the link from the result
                        result="${result%/*}"
                    fi
                elif [[ $path =~ [^/] && ! -d $result ]]; then
                    # otherwise all but the last element must be a dir
                    echo "$1: Not a directory" >&2
                    return 1
                fi
                ;;
        esac
    done
    echo "$result"
}

Nó bao gồm phát hiện liên kết biểu tượng hình tròn, thoát ra nếu cùng một đường dẫn (trung gian) được nhìn thấy hai lần.

Nếu tất cả những gì bạn cần là có readlink -f, thì bạn có thể sử dụng ở trên như:

readlink() {
    if [[ $1 != -f ]]; then  # poor-man's option parsing
        # delegate to the standard readlink command
        command readlink "$@"
        return
    fi

    local path result seenerr
    shift
    seenerr=
    for path in "$@"; do
        # by default readlink suppresses error messages
        if ! result=$(_canonicalize_filename_mode "$path" 2>/dev/null); then
            seenerr=1
            continue
        fi
        echo "$result"
    done
    if [[ $seenerr ]]; then
        return 1;
    fi
}

Đối với realpath, tôi cũng cần --relative-to--relative-basehỗ trợ, cung cấp cho bạn các đường dẫn tương đối sau khi chuẩn hóa:

_realpath() {
    # GNU realpath replacement for bash 3.2 (OS X)
    # accepts --relative-to= and --relative-base options
    # and produces canonical (relative or absolute) paths for each
    # argument on stdout, errors on stderr, and returns 0 on success
    # and 1 if at least 1 path triggered an error.

    local relative_to relative_base seenerr path

    relative_to=
    relative_base=
    seenerr=

    while [[ $# -gt 0 ]]; do
        case $1 in
            "--relative-to="*)
                relative_to=$(_canonicalize_filename_mode "${1#*=}")
                shift 1;;
            "--relative-base="*)
                relative_base=$(_canonicalize_filename_mode "${1#*=}")
                shift 1;;
            *)
                break;;
        esac
    done

    if [[
        -n $relative_to
        && -n $relative_base
        && ${relative_to#${relative_base}/} == "$relative_to"
    ]]; then
        # relative_to is not a subdir of relative_base -> ignore both
        relative_to=
        relative_base=
    elif [[ -z $relative_to && -n $relative_base ]]; then
        # if relative_to has not been set but relative_base has, then
        # set relative_to from relative_base, simplifies logic later on
        relative_to="$relative_base"
    fi

    for path in "$@"; do
        if ! real=$(_canonicalize_filename_mode "$path"); then
            seenerr=1
            continue
        fi

        # make path relative if so required
        if [[
            -n $relative_to
            && ( # path must not be outside relative_base to be made relative
                -z $relative_base || ${real#${relative_base}/} != "$real"
            )
        ]]; then
            local common_part parentrefs

            common_part="$relative_to"
            parentrefs=
            while [[ ${real#${common_part}/} == "$real" ]]; do
                common_part="$(dirname "$common_part")"
                parentrefs="..${parentrefs:+/$parentrefs}"
            done

            if [[ $common_part != "/" ]]; then
                real="${parentrefs:+${parentrefs}/}${real#${common_part}/}"
            fi
        fi

        echo "$real"
    done
    if [[ $seenerr ]]; then
        return 1
    fi
}

if ! command -v realpath > /dev/null 2>&1; then
    # realpath is not available on OSX unless you install the `coreutils` brew
    realpath() { _realpath "$@"; }
fi

Tôi đã đưa các bài kiểm tra đơn vị vào yêu cầu Xem lại mã của mình cho mã này .


-2

Dựa trên giao tiếp với người bình luận, tôi đồng ý rằng rất khó và không có cách nào để thực hiện một đường dẫn thực hoạt động hoàn toàn giống như Ubuntu.

Nhưng phiên bản sau, có thể xử lý các trường hợp góc tốt nhất không thể và đáp ứng nhu cầu hàng ngày của tôi trên macbook. Đặt mã này vào ~ / .bashrc của bạn và nhớ:

  • arg chỉ có thể là 1 tệp hoặc dir, không có ký tự đại diện
  • không có khoảng trắng trong dir hoặc tên tệp
  • ít nhất tệp hoặc dir mẹ của dir tồn tại
  • cảm thấy tự do để sử dụng. .. / điều này an toàn

    # 1. if is a dir, try cd and pwd
    # 2. if is a file, try cd its parent and concat dir+file
    realpath() {
     [ "$1" = "" ] && return 1

     dir=`dirname "$1"`
     file=`basename "$1"`

     last=`pwd`

     [ -d "$dir" ] && cd $dir || return 1
     if [ -d "$file" ];
     then
       # case 1
       cd $file && pwd || return 1
     else
       # case 2
       echo `pwd`/$file | sed 's/\/\//\//g'
     fi

     cd $last
    }

Bạn muốn tránh việc sử dụng vô íchecho . Chỉ pwdlàm tương tự như echo $(pwd)mà không cần phải tạo ra một bản sao thứ hai của shell. Ngoài ra, việc không trích dẫn đối số echolà một lỗi (bạn sẽ mất mọi khoảng trắng ở đầu hoặc cuối, mọi ký tự khoảng trắng bên trong liền kề và các ký tự đại diện được mở rộng, v.v.). Xem thêm stackoverflow.com/questions/10067266/…
tripleee

Ngoài ra, hành vi đối với các đường dẫn không tồn tại là lỗi; nhưng tôi đoán đó là những gì câu "nhưng hãy nhớ" có lẽ đang muốn nói. Mặc dù hành vi trên Ubuntu chắc chắn là không in thư mục hiện tại khi bạn yêu cầu realpaththư mục không tồn tại.
tripleee

Để có tính nhất quán, có thể thích dir=$(dirname "$1"); file=$(basename "$1")thay vì cú pháp backtick lỗi thời quá lâu. Cũng lưu ý trích dẫn chính xác của các đối số, một lần nữa.
tripleee

Câu trả lời cập nhật của bạn dường như không thể sửa được nhiều lỗi và thêm những lỗi mới.
tripleee

xin vui lòng cho tôi một số trường hợp không thành công cụ thể, vì tất cả các bài kiểm tra tôi làm trong ubuntu 18.04 máy tính để bàn là ok, cảm ơn.
occia
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.