Ścieżka bezwzględna skryptu Bash w systemie OS X

98

Próbuję uzyskać bezwzględną ścieżkę do aktualnie uruchomionego skryptu w systemie OS X.

Widziałem wiele odpowiedzi readlink -f $0. Jednak ponieważ OS X readlinkjest taki sam jak BSD, po prostu nie działa (działa z wersją GNU).

Czy jest na to gotowe rozwiązanie?

Potężna gumowa kaczka
źródło

Odpowiedzi:

88

Jest realpath()funkcja C, która wykona zadanie, ale nie widzę niczego dostępnego w wierszu poleceń. Oto szybka i brudna wymiana:

#!/bin/bash

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

realpath "$0"

Spowoduje to wydrukowanie ścieżki dosłownie, jeśli zaczyna się od /. Jeśli nie, musi to być ścieżka względna, więc poprzedza ją na początku $PWD. #./Część Odcina ./się z przodu $1.

John Kugelman
źródło
1
Zauważyłem również funkcję C, ale nie mogłem znaleźć żadnego odpowiadającego pliku binarnego ani niczego. W każdym razie twoja funkcja po prostu ładnie pasuje do moich potrzeb. Dzięki!
The Mighty Rubber Duck
7
Należy zauważyć, że nie powoduje to wyłuskiwania linków symbolicznych.
Adam Vandenberg
17
realpath ../somethingpowroty$PWD/../something
Lri
U mnie to zadziałało, ale zmieniłem jego nazwę na „realpath_osx ()”, ponieważ MOGĘ muszę wysłać ten skrypt bash do powłoki systemu Linux i nie chcę, aby kolidował z „prawdziwą” ścieżką rzeczywistą! (Jestem pewien, że istnieje bardziej elegancki sposób, ale jestem bash n00b.)
Andrew Theken
8
command -v realpath >/dev/null 2>&1 || realpath() { ... }
Kara Brightwell
115

Te trzy proste kroki rozwiązują ten i wiele innych problemów z systemem OS X:

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

(3) można zmienić na zwykłe realpath, patrz (2) wyjście

Oleg Micheev
źródło
3
To rozwiązuje problem, ale wymaga zainstalowania rzeczy, których możesz nie chcieć lub których nie potrzebujesz, lub które mogą nie znajdować się w systemie docelowym, tj. OS X
Jason S
6
@JasonS GNU Coreutils jest zbyt dobry, aby go nie używać. Zawiera wiele świetnych narzędzi. W rzeczywistości jest tak dobre, że jądro Linuksa jest bezużyteczne bez niego i dlaczego niektórzy nazywają go GNU / Linux. Coreutils jest niesamowity. doskonała odpowiedź to powinna być wybrana odpowiedź.
7
@omouse Pytanie konkretnie wspomina o „gotowym rozwiązaniu” (dla OS X). Niezależnie od tego, jak niesamowity jest coreutils, nie jest on „po wyjęciu z pudełka” w systemie OS X, więc odpowiedź nie jest dobra. Nie jest to kwestia tego, jak dobry jest coreutils, ani czy jest lepszy niż to, co jest na OS X. Istnieją rozwiązania „po wyjęciu z pudełka”, które $( cd "$(dirname "$0")" ; pwd -P )dla mnie działają dobrze.
Jason S
2
Jedną z „funkcji” systemu OS X jest to, że w nazwach katalogów i plików nie jest rozróżniana wielkość liter. W rezultacie, jeśli masz katalog o nazwie XXXi ktoś cd xxxpotem pwdwróci .../xxx. Z wyjątkiem tej odpowiedzi, wszystkie powyższe rozwiązania powracają, xxxgdy naprawdę chcesz XXX. Dziękuję Ci!
Andrew
1
Nie wiem, jak nieskończone kopiowanie, wklejanie i odkrywanie kodu na nowo jest lepsze niż istniejące rozwiązanie menedżera pakietów. Jeśli potrzebujesz realpath, to co się stanie, gdy prawie na pewno będziesz potrzebować innych przedmiotów coreutils? Przepisać te funkcje również w bash? : P
Ezekiel Victor,
28

Fuj. Wydaje mi się, że poprzednie odpowiedzi były trochę nieciekawe z kilku powodów: w szczególności nie rozwiązują wielu poziomów dowiązań symbolicznych i są wyjątkowo „bash-y”. Podczas gdy pierwotne pytanie wyraźnie zawiera prośbę o „skrypt Bash”, wspomina się również o BSD systemu Mac OS X, który nie jest GNU readlink. Oto próba rozsądnego przenoszenia (sprawdziłem to z bash jako „sh” i myślnik), rozwiązując dowolną liczbę dowiązań symbolicznych; i powinno również działać z białymi znakami w ścieżce (ścieżkach), chociaż nie jestem pewien zachowania, jeśli jest spacja w podstawowej nazwie samego narzędzia, więc może, hm, unikaj tego?

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

Mam nadzieję, że to może być dla kogoś przydatne.

Geoff Nixon
źródło
1
Zalecałbym tylko używanie localdla zmiennych zdefiniowanych wewnątrz funkcji, aby nie zanieczyszczać globalnej przestrzeni nazw. Np local OURPWD=.... Działa przynajmniej na bash.
Michael Paesold
Ponadto kod nie powinien używać wielkich liter dla zmiennych prywatnych. Zmienne pisane dużymi literami są zarezerwowane do użytku systemowego.
tripleee
Dzięki za scenariusz. W przypadku, gdy łącze (a) i rzeczywisty plik mają różne nazwy bazowe, prawdopodobnie dobrym pomysłem byłoby dodanie BASENAME=$(basename "$LINK") wewnątrz while i użycie tego w drugim ustawiającym LINK i ustawiającym
REALPATH
To nie obsługuje linków symbolicznych i referencji ..nadrzędnych w taki sposób, jak realpathby to zrobiło. Po coreutilszainstalowaniu homebrew spróbuj ln -s /var/log /tmp/linkexamplewtedy realpath /tmp/linkexample/../; to drukuje /private/var. Ale /tmp/linkexample/..zamiast tego twoja funkcja produkuje , ponieważ ..nie jest dowiązaniem symbolicznym.
Martijn Pieters
10

Bardziej przyjazny dla wiersza poleceń wariant rozwiązania Pythona:

python -c "import os; print(os.path.realpath('$1'))"
nanav yorbiz
źródło
2
Na wypadek, gdyby ktoś był na tyle szalony, by uruchomić interpreter Pythona dla jednego polecenia…
Bachsau
Byłem wystarczająco wściekły, ale użyłempython -c "import os; import sys; print(os.path.realpath(sys.argv[1]))"
Alex Chamberlain
7

Szukałem rozwiązania do wykorzystania w skrypcie obsługi systemu, tj. Uruchomionym przed zainstalowaniem Homebrew. Z braku odpowiedniego rozwiązania po prostu przeładowałbym zadanie do języka wieloplatformowego, np. Perl:

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

Częściej to, czego faktycznie chcemy, to katalog zawierający:

here=$(perl -e 'use File::Basename; use Cwd "abs_path"; print dirname(abs_path(@ARGV[0]));' -- "$0")
4ae1e1
źródło
Wspaniały! Perl jest miły z powodu małego obciążenia! Prawdopodobnie mógłbyś uprościć pierwszą wersję do FULLPATH=$(perl -e "use Cwd 'abs_path'; print abs_path('$0')"). Jakiś powód przeciwko?
F Pereira
@FPereira Generowanie kodu programu z niezmienionego łańcucha podanego przez użytkownika nigdy nie jest dobrym pomysłem. ''nie jest kuloodporny. Na przykład pęka, jeśli $0zawiera pojedynczy cytat. Bardzo prosty przykład: wypróbuj swoją wersję /tmp/'/test.shi /tmp/'/test.shkorzystaj z pełnej ścieżki.
4ae1e1
Albo jeszcze prościej, /tmp/'.sh.
4ae1e1
6

Ponieważ istnieje rzeczywista ścieżka, jak wskazywali inni:

// 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;
}

Plik Makefile:

#Makefile
OBJ = realpath.o

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

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

Następnie skompiluj makei umieść miękki link za pomocą:
ln -s $(pwd)/realpath /usr/local/bin/realpath

WaffleSouffle
źródło
czy moglibyśmy po prostu zrobić gcc realpath.c -o /usr/local/bin/realpath?
Alexander Mills,
1
@AlexanderMills Nie powinieneś uruchamiać kompilatora jako root; a jeśli tego nie zrobisz, nie będziesz miał uprawnień do pisania/usr/local/bin
tripleee
6

Użyj Pythona, aby to uzyskać:

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

print(os.path.realpath(sys.argv[1]))
drażnienie
źródło
2
abs_path () {    
   echo "$(cd $(dirname "$1");pwd)/$(basename "$1")"
}

dirnamepoda nazwę katalogu /path/to/file, tj /path/to.

cd /path/to; pwd zapewnia, że ​​ścieżka jest bezwzględna.

basenamepoda tylko nazwę pliku w /path/to/file, tj file.

Logu
źródło
Chociaż ten kod może odpowiedzieć na pytanie, zapewnia dodatkowy kontekst dotyczący tego, dlaczego i / lub jak ten kod odpowiada, poprawia jego długoterminową wartość.
Igor F.
1

Jak widać powyżej, zrobiłem to około 6 miesięcy temu. Zupełnie o tym zapomniałem, aż znowu potrzebowałem czegoś podobnego. Byłem całkowicie zszokowany, widząc, jakie to podstawowe; Od około roku uczę się bardzo intensywnie programować, ale często mam wrażenie, że może w ogóle niczego się nie nauczyłem, gdy sytuacja jest najgorsza.

Usunąłbym powyższe „rozwiązanie”, ale naprawdę podoba mi się to, że jest zapisem tego, ile naprawdę nauczyłem się w ciągu ostatnich kilku miesięcy.

Ale błądzę. Usiadłem i rozpracowałem to zeszłej nocy. Wyjaśnienie w komentarzach powinno wystarczyć. Jeśli chcesz śledzić kopię, nad którą nadal pracuję, możesz skorzystać z tego sedna. To prawdopodobnie robi to, czego potrzebujesz.

#!/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
źródło
1
Link do istoty wydaje się uszkodzony.
Alex
ta odpowiedź działa: stackoverflow.com/a/46772980/1223975 .... powinieneś po prostu tego użyć.
Alexander Mills,
Zauważ, że Twoja implementacja nie rozwiązuje dowiązań symbolicznych przed ..odniesieniami do nadrzędnych; np. /foo/link_to_other_directory/..jest rozstrzygany /fooraczej jako niż jako rodzic ścieżki, na którą /foo/link_to_other_directorywskazuje dowiązanie symboliczne . readlink -fi realpathrozwiąż każdy składnik ścieżki, zaczynając od katalogu głównego i aktualizując oczekujące docelowe łącza do pozostałych, które są nadal przetwarzane. Dodałem odpowiedź na to pytanie, ponownie implementując tę ​​logikę.
Martijn Pieters
1

realpath dla Mac OS X

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

Przykład z powiązaną ścieżką:

realpath "../scripts/test.sh"

Przykład z folderem domowym

realpath "~/Test/../Test/scripts/test.sh"
Evgeny Karpov
źródło
1
fajne proste rozwiązanie, właśnie znalazłem jedno zastrzeżenie, wywołanie go z ..nim nie daje właściwej odpowiedzi, więc dodałem sprawdzenie, czy podana ścieżka jest katalogiem: if test -d $path ; then echo $(cd "$path"; pwd) ; else [...]
herbert
Nie działa dla mnie, "$(dirname $(dirname $(realpath $0)))"ale działa, więc potrzebuję czegoś innego ...
Alexander Mills,
Nie należy również dopuszczać się bezużytecznego używaniaecho .
tripleee
W rzeczywistości nie rozwiązuje to linków symbolicznych w taki sposób, jak zrobiłby to realpath. Rozwiązuje ..odniesienia do rodziców przed rozstrzygnięciem dowiązań symbolicznych, a nie po; spróbuj tego z coreutilszainstalowanym homebrew , utwórz łącze i ln -s /var/log /tmp/linkexampleuruchom realpath /tmp/linkexample/../; to drukuje /private/var. Ale twoja funkcja produkuje /tmp/linkexample/..zamiast tego, ponieważ pwdnadal pokazuje się /tmp/linkexamplepo cd.
Martijn Pieters
1

W systemie macOS jedynym rozwiązaniem, jakie znalazłem, które niezawodnie obsługuje łącza symboliczne, jest użycie realpath. Ponieważ to wymaga brew install coreutils, po prostu zautomatyzowałem ten krok. Moja realizacja wygląda następująco:

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


Te błędy, jeśli nie zostały brewzainstalowane, ale możesz też po prostu je zainstalować. Po prostu nie czułem się komfortowo, automatyzując coś, co powoduje usunięcie z sieci dowolnego kodu rubinowego.

Zwróć uwagę, że jest to automatyczna odmiana odpowiedzi Olega Micheeva .


Jeden ważny test

Jeden dobry test każdego z tych rozwiązań to:

  1. umieść kod gdzieś w pliku skryptu
  2. w innym katalogu, dowiązanie symboliczne ( ln -s) do tego pliku
  3. uruchom skrypt z tego dowiązania symbolicznego

Czy rozwiązanie odwołuje się do dowiązania symbolicznego i podaje oryginalny katalog? Jeśli tak, to działa.

Clay Bridges
źródło
0

Wydaje się, że działa to na OSX, nie wymaga żadnych plików binarnych i zostało pobrane stąd

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

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

Lubię to:

#!/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
źródło
0

Potrzebowałem realpathzamiennika na OS X, takiego, który działa poprawnie na ścieżkach z linkami symbolicznymi i odniesieniami do rodziców, tak jak readlink -fby to zrobił . Obejmuje to rozwiązywanie dowiązań symbolicznych w ścieżce przed rozwiązywaniem odniesień nadrzędnych; np. jeśli zainstalowałeś coreutilsbutelkę homebrew , uruchom:

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

Zauważ, że readlink -fproblem został rozwiązany /tmp/linkeddir przed rozwiązaniem ..odniesienia do katalogu nadrzędnego. Oczywiście readlink -fna Macu też nie ma .

Tak więc w ramach implementacji bash zaimplementowałem realpathponownie to, co robi canonicalize_filename_mode(path, CAN_ALL_BUT_LAST)wywołanie funkcji GNUlib , w Bash 3.2; jest to również wywołanie funkcji, które readlink -fwykonuje GNU :

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

Obejmuje wykrywanie okrągłego łącza symbolicznego, kończące się, jeśli ta sama (pośrednia) ścieżka jest widziana dwukrotnie.

Jeśli wszystko, czego potrzebujesz readlink -f, to możesz użyć powyższego jako:

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
}

Dla realpath, ja również potrzebne --relative-toi --relative-basewsparcie, które daje ścieżek względnych po canonicalizing:

_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

Umieściłem testy jednostkowe w mojej prośbie Code Review dla tego kodu .

Martijn Pieters
źródło
-2

Opierając się na komunikacji z komentatorem, zgodziłem się, że implementacja realpath jest bardzo trudna i nie ma trywialnego sposobu, zachowując się zupełnie tak samo jak Ubuntu.

Ale następująca wersja poradzi sobie z przypadkami narożnymi, na które najlepsza odpowiedź nie może i zaspokoi moje codzienne potrzeby na Macbooku. Umieść ten kod w swoim ~ / .bashrc i pamiętaj:

  • arg może być tylko 1 plikiem lub katalogiem, bez symbolu wieloznacznego
  • bez spacji w katalogu lub nazwie pliku
  • przynajmniej plik lub katalog nadrzędny katalogu istnieje
  • nie krępuj się używać. .. / rzecz, te są bezpieczne

    # 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
źródło
Chcesz uniknąć bezużytecznego używaniaecho . Po prostu pwddziała tak samo jak echo $(pwd)bez konieczności tarło drugą kopię powłoki. Ponadto, brak cytowania argumentu echojest błędem (utracisz wszelkie początkowe lub końcowe spacje, wszelkie sąsiednie wewnętrzne białe znaki i rozwiniesz symbole wieloznaczne itp.). Zobacz więcej stackoverflow.com/questions/10067266/…
tripleee
Ponadto zachowanie dla nieistniejących ścieżek jest błędne; ale myślę, że to właśnie jest to, co chyba próbuje powiedzieć zdanie „ale pamiętaj”. Chociaż zachowanie w Ubuntu z pewnością nie polega na drukowaniu bieżącego katalogu, gdy realpathżądasz nieistniejącego katalogu.
tripleee
Aby zachować spójność, prawdopodobnie wolę dir=$(dirname "$1"); file=$(basename "$1")zamiast dawno przestarzałej składni odwrotnego znaku. Zwróć także uwagę na prawidłowe cytowanie argumentów.
tripleee
Twoja zaktualizowana odpowiedź wydaje się nie naprawić wielu błędów i dodaje nowe.
tripleee
proszę o kilka konkretnych przypadków nieudanych, ponieważ wszystkie testy, które wykonuję na pulpicie Ubuntu 18.04 są w porządku, dzięki.
occia