Jalur absolut skrip Bash dengan OS X

98

Saya mencoba untuk mendapatkan jalur absolut ke skrip yang sedang berjalan di OS X.

Saya melihat banyak balasan datang readlink -f $0. Namun karena OS X readlinksama dengan BSD, itu tidak berfungsi (bekerja dengan versi GNU).

Apakah ada solusi out-of-the-box untuk ini?

Bebek Karet Perkasa
sumber

Jawaban:

88

Ada realpath()fungsi C yang akan melakukan pekerjaan itu, tapi saya tidak melihat apa pun yang tersedia di baris perintah. Ini pengganti yang cepat dan kotor:

#!/bin/bash

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

realpath "$0"

Ini mencetak jalan secara verbatim jika dimulai dengan a /. Jika tidak, itu harus berupa jalur relatif, jadi itu tergantung $PWDke depan. The #./bagian strip off ./dari depan $1.

John Kugelman
sumber
1
Saya juga memperhatikan fungsi C tetapi tidak dapat menemukan biner yang sesuai atau apa pun. Bagaimanapun, fungsi Anda sesuai dengan kebutuhan saya dengan baik. Terima kasih!
The Mighty Rubber Duck
7
Perhatikan bahwa ini tidak menghilangkan symlink.
Adam Vandenberg
17
realpath ../somethingkembali$PWD/../something
Lri
Ini berhasil untuk saya, tetapi saya menamainya "realpath_osx ()" karena saya MUNGKIN perlu mengirim skrip bash ini ke shell linux dan tidak ingin itu bertabrakan dengan realpath "nyata"! (Saya yakin ada cara yang lebih elegan, tapi saya adalah bash n00b.)
Andrew Theken
8
command -v realpath >/dev/null 2>&1 || realpath() { ... }
Kara Brightwell
115

Tiga langkah sederhana ini akan menyelesaikan ini dan banyak masalah OS X lainnya:

  1. Pasang Homebrew
  2. brew install coreutils
  3. grealpath .

(3) dapat diubah menjadi hanya realpath, lihat (2) keluaran

Oleg Mikheev
sumber
3
Ini memecahkan masalah, tetapi memerlukan penginstalan hal-hal yang mungkin tidak Anda inginkan atau butuhkan, atau yang mungkin tidak ada di sistem target, yaitu OS X
Jason S
6
@JasonS GNU Coreutils terlalu bagus untuk tidak digunakan. Ini mencakup banyak utilitas hebat. Sangat bagus bahwa kernel Linux tidak berguna tanpanya dan mengapa beberapa orang menyebutnya GNU / Linux. Coreutils luar biasa. jawaban yang sangat baik ini harus menjadi jawaban yang dipilih.
7
@omouse Pertanyaan tersebut secara khusus menyebutkan 'solusi di luar kotak' (untuk OS X). Terlepas dari betapa hebatnya coreutils, ini tidak 'di luar kotak' di OS X jadi jawabannya tidak bagus. Ini bukan pertanyaan tentang seberapa baik coreutils, atau apakah lebih baik daripada yang ada di OS X. Ada solusi 'di luar kotak' yang $( cd "$(dirname "$0")" ; pwd -P )berfungsi dengan baik untuk saya.
Jason S
2
Salah satu "fitur" OS X adalah bahwa direktori dan nama file tidak membedakan huruf besar / kecil. Akibatnya jika Anda memiliki sebuah direktori yang disebut XXXdan beberapa orang cd xxxkemudian pwdakan kembali .../xxx. Kecuali untuk jawaban ini, semua solusi di atas akan muncul xxxketika Anda benar-benar menginginkannya XXX. Terima kasih!
Andrew
1
Saya tidak tahu bagaimana menyalin, menempel, dan menemukan kembali kode tanpa henti lebih baik daripada solusi pengelola paket yang ada. Jika Anda membutuhkannya realpath, lalu apa yang terjadi ketika Anda hampir pasti akan membutuhkan barang lain dari coreutils? Tulis ulang fungsi-fungsi itu di bash juga? : P
Ezekiel Victor
28

Ugh. Saya menemukan jawaban sebelumnya agak kurang karena beberapa alasan: khususnya, jawaban tersebut tidak menyelesaikan beberapa tingkat tautan simbolis, dan mereka sangat "Bash-y". Meskipun pertanyaan awal secara eksplisit meminta "skrip Bash", pertanyaan tersebut juga menyebutkan tentang Mac OS X BSD-like, non-GNU readlink. Jadi, inilah upaya pada beberapa portabilitas yang masuk akal (saya telah memeriksanya dengan bash sebagai 'sh' dan tanda hubung), menyelesaikan sejumlah tautan simbolik; dan itu juga harus bekerja dengan spasi di jalur, meskipun saya tidak yakin perilakunya jika ada spasi putih nama dasar utilitas itu sendiri, jadi mungkin, um, hindari itu?

#!/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 "$@"

Harapan itu bisa berguna bagi seseorang.

Geoff Nixon
sumber
1
Saya hanya akan merekomendasikan untuk menggunakan localvariabel yang didefinisikan di dalam fungsi agar tidak mencemari namespace global. Mis local OURPWD=.... Bekerja setidaknya untuk pesta.
Michael Paesold
Selain itu, kode tidak boleh menggunakan huruf besar untuk variabel privat. Variabel huruf besar dicadangkan untuk penggunaan sistem.
tripleee
Terima kasih untuk naskahnya. Jika tautan dan file asli memiliki nama dasar yang berbeda, mungkin merupakan ide yang baik untuk menambahkan BASENAME=$(basename "$LINK") inside the while dan menggunakannya di penyetel LINK kedua dan penyetel
REALPATH
Ini tidak menangani symlink dan ..referensi induk dengan cara yang realpathsama. Dengan homebrew coreutilsterinstal, coba ln -s /var/log /tmp/linkexamplekemudian realpath /tmp/linkexample/../; cetakan ini /private/var. Tetapi fungsi Anda menghasilkan /tmp/linkexample/..sebaliknya, karena ..bukan symlink.
Martijn Pieters
10

Varian yang lebih ramah baris perintah dari solusi Python:

python -c "import os; print(os.path.realpath('$1'))"
nanav yorbiz
sumber
2
Untuk berjaga-jaga jika ada yang cukup gila untuk memulai penerjemah python untuk satu perintah…
Bachsau
Saya cukup marah, tetapi terbiasapython -c "import os; import sys; print(os.path.realpath(sys.argv[1]))"
Alex Chamberlain
7

Saya sedang mencari solusi untuk digunakan dalam skrip penyediaan sistem, yaitu, dijalankan bahkan sebelum Homebrew diinstal. Kurangnya solusi yang tepat, saya baru saja memindahkan tugas ke bahasa lintas platform, misalnya, Perl:

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

Lebih sering yang sebenarnya kita inginkan adalah direktori yang berisi:

here=$(perl -e 'use File::Basename; use Cwd "abs_path"; print dirname(abs_path(@ARGV[0]));' -- "$0")
4ae1e1
sumber
Bagus! Perl bagus untuk overhead kecil! Anda mungkin dapat menyederhanakan versi pertama menjadi FULLPATH=$(perl -e "use Cwd 'abs_path'; print abs_path('$0')"). Ada alasan melawan?
F Pereira
@FPereira Membuat kode program dari string yang disediakan pengguna yang tidak lolos bukanlah ide yang baik. ''tidak anti peluru. Ini rusak jika $0berisi satu kutipan, misalnya. Contoh yang sangat sederhana: coba versi Anda di /tmp/'/test.sh, dan panggil /tmp/'/test.shdengan jalur lengkapnya.
4ae1e1
Atau bahkan lebih sederhana /tmp/'.sh,.
4ae1e1
6

Karena ada realpath yang lain memiliki keluar menunjuk:

// 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)

Kemudian kompilasi dengan makedan masukkan tautan lunak dengan:
ln -s $(pwd)/realpath /usr/local/bin/realpath

WaffleSouffle
sumber
bisakah kita melakukannya gcc realpath.c -o /usr/local/bin/realpath?
Alexander Mills
1
@AlexanderMills Anda sebaiknya tidak menjalankan kompilator sebagai root; dan jika tidak, Anda tidak akan memiliki hak istimewa untuk /usr/local/bin
mengirim
6

Gunakan Python untuk mendapatkannya:

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

print(os.path.realpath(sys.argv[1]))
acrazing
sumber
2
abs_path () {    
   echo "$(cd $(dirname "$1");pwd)/$(basename "$1")"
}

dirnameakan memberikan nama direktori /path/to/file, yaitu /path/to.

cd /path/to; pwd memastikan bahwa jalurnya mutlak.

basenameakan memberikan nama file saja /path/to/file, yaitu file.

Logu
sumber
Meskipun kode ini dapat menjawab pertanyaan, memberikan konteks tambahan tentang mengapa dan / atau bagaimana kode ini menjawab pertanyaan tersebut meningkatkan nilai jangka panjangnya.
Igor F.
1

Jadi seperti yang Anda lihat di atas, saya memotretnya sekitar 6 bulan yang lalu. Saya benar-benar melupakannya sampai saya menemukan diri saya membutuhkan hal yang sama lagi. Saya benar - benar terkejut melihat betapa belum sempurnanya itu; Saya telah mengajar diri saya sendiri untuk membuat kode cukup intensif selama sekitar satu tahun sekarang, tetapi saya sering merasa mungkin saya belum belajar apa pun sama sekali ketika hal-hal yang paling buruk.

Saya akan menghapus 'solusi' di atas, tetapi saya sangat menyukainya sebagai catatan tentang seberapa banyak yang telah saya pelajari selama beberapa bulan terakhir.

Tapi saya ngelantur. Saya duduk dan mengerjakan semuanya tadi malam. Penjelasan di komentar seharusnya cukup. Jika Anda ingin melacak salinan yang sedang saya kerjakan, Anda dapat mengikuti intinya. Ini mungkin melakukan apa yang Anda butuhkan.

#!/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
Geoff Nixon
sumber
1
Tautan intinya sepertinya rusak.
Alex
jawaban ini berhasil: stackoverflow.com/a/46772980/1223975 .... Anda sebaiknya menggunakan itu saja.
Alexander Mills
Perhatikan bahwa implementasi Anda tidak menyelesaikan symlink sebelum ..referensi induk; misalnya /foo/link_to_other_directory/..diselesaikan sebagai /foobukan induk dari jalur apa pun yang /foo/link_to_other_directoryditunjuk oleh symlink . readlink -fdan realpathmenyelesaikan setiap komponen jalur mulai dari akar dan memperbarui target tautan sebelumnya ke sisa yang masih diproses. Saya telah menambahkan jawaban untuk pertanyaan ini dengan menerapkan ulang logika itu.
Martijn Pieters
1

realpath untuk Mac OS X

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

Contoh dengan jalur terkait:

realpath "../scripts/test.sh"

Contoh dengan folder home

realpath "~/Test/../Test/scripts/test.sh"
Evgeny Karpov
sumber
1
solusi sederhana yang bagus, saya baru saja menemukan satu peringatan, ketika memanggilnya dengan ..itu tidak menghasilkan jawaban yang benar, jadi saya menambahkan tanda centang apakah jalur yang diberikan adalah direktori: if test -d $path ; then echo $(cd "$path"; pwd) ; else [...]
herbert
Tidak berhasil untuk saya, sedangkan "$(dirname $(dirname $(realpath $0)))"berhasil, jadi butuh sesuatu yang lain ...
Alexander Mills
Penggunaan yang tidak bergunaecho juga tidak boleh dilakukan.
tripleee
Ini tidak benar-benar menyelesaikan symlink seperti yang dilakukan realpath. Ini menyelesaikan ..referensi induk sebelum menyelesaikan symlink, bukan setelah; coba ini dengan homebrew coreutilsterinstal, buat tautan dengan ln -s /var/log /tmp/linkexamplelalu jalankan realpath /tmp/linkexample/../; cetakan ini /private/var. Tetapi fungsi Anda menghasilkan /tmp/linkexample/..sebaliknya, karena pwdmasih muncul /tmp/linkexamplesetelah cd.
Martijn Pieters
1

Di macOS, satu-satunya solusi yang saya temukan untuk menangani symlink secara andal adalah dengan menggunakan realpath. Karena ini memerlukan brew install coreutils, saya baru saja mengotomatiskan langkah itu. Implementasi saya terlihat seperti ini:

#!/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\""


Kesalahan ini jika mereka tidak brewmenginstal, tetapi Anda dapat menginstalnya juga. Saya hanya tidak merasa nyaman mengotomatiskan sesuatu yang menggulung kode ruby ​​dari internet.

Perhatikan bahwa ini merupakan variasi otomatis dari jawaban Oleg Mikheev .


Satu ujian penting

Salah satu pengujian yang bagus dari salah satu solusi ini adalah:

  1. letakkan kode di file script di suatu tempat
  2. di direktori lain, symlink ( ln -s) ke file itu
  3. jalankan skrip dari symlink itu

Apakah solusinya membedakan symlink, dan memberi Anda direktori aslinya? Jika demikian, itu berhasil.

Clay Bridges
sumber
0

Ini tampaknya berfungsi untuk OSX, tidak memerlukan binari apa pun, dan ditarik dari sini

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

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

Saya suka ini:

#!/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"
}
dcmorse
sumber
0

Aku butuh realpathpengganti pada OS X, salah satu yang beroperasi dengan benar pada jalur dengan symlink dan orangtua referensi seperti readlink -fWould . Ini termasuk menyelesaikan symlink di jalur sebelum menyelesaikan referensi induk; misalnya jika Anda telah menginstal coreutilsbotol homebrew , kemudian jalankan:

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

Catatan yang readlink -ftelah diselesaikan /tmp/linkeddir sebelum menyelesaikan ..referensi dir induk. Tentu saja, tidak ada readlink -fdi Mac juga .

Jadi sebagai bagian dari implementasi bash untuk realpathsaya mengimplementasikan kembali apa yang dilakukan pemanggilan canonicalize_filename_mode(path, CAN_ALL_BUT_LAST)fungsi GNUlib , di Bash 3.2; ini juga pemanggilan fungsi yang dilakukan GNU readlink -f:

# 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"
}

Ini mencakup deteksi symlink melingkar, keluar jika jalur (perantara) yang sama terlihat dua kali.

Jika yang Anda butuhkan hanyalah readlink -f, Anda dapat menggunakan cara di atas sebagai:

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
}

Karena realpath, saya juga membutuhkan --relative-todan --relative-basemendukung, yang memberi Anda jalur relatif setelah dikanonikalisasi:

_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

Saya menyertakan pengujian unit dalam permintaan Tinjauan Kode saya untuk kode ini .

Martijn Pieters
sumber
-2

Berdasarkan komunikasi dengan komentator, saya setuju bahwa ini sangat sulit dan tidak memiliki cara trival untuk mengimplementasikan realpath yang berperilaku sama seperti Ubuntu.

Tetapi versi berikut, dapat menangani kasus sudut, jawaban terbaik tidak bisa dan memenuhi kebutuhan harian saya di macbook. Masukkan kode ini ke ~ / .bashrc Anda dan ingat:

  • arg hanya bisa 1 file atau dir, tidak ada wildcard
  • tidak ada spasi di dir atau nama file
  • setidaknya file atau dir induk dir ada
  • jangan ragu untuk menggunakan. .. / hal, ini aman

    # 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
    }
Occia
sumber
Anda ingin menghindari penggunaan yang tidak bergunaecho . Lakukan pwdhal yang sama seperti echo $(pwd)tanpa harus menelurkan salinan kedua dari cangkang. Juga, tidak mengutip argumen ke echoadalah bug (Anda akan kehilangan spasi kosong di depan atau di belakang, karakter spasi putih internal yang berdekatan, dan karakter pengganti diperluas, dll). Lihat lebih lanjut stackoverflow.com/questions/10067266/…
tripleee
Selain itu, perilaku untuk jalur yang tidak ada bermasalah; tapi saya rasa itulah yang mungkin coba dikatakan oleh kalimat "tapi ingat". Meskipun perilaku di Ubuntu jelas tidak untuk mencetak direktori saat ini ketika Anda meminta realpathdirektori yang tidak ada.
tripleee
Untuk konsistensi, mungkin lebih suka dir=$(dirname "$1"); file=$(basename "$1")daripada sintaks backtick yang sudah lama usang. Juga perhatikan kutipan argumen yang benar, sekali lagi.
tripleee
Jawaban Anda yang diperbarui tampaknya gagal memperbaiki banyak bug, dan menambahkan yang baru.
tripleee
tolong beri saya beberapa kasus gagal tertentu, karena semua tes yang saya lakukan di desktop ubuntu 18.04 baik-baik saja, terima kasih.
Occia