Absoluter Pfad des Bash-Skripts unter OS X.

98

Ich versuche, den absoluten Pfad zum aktuell ausgeführten Skript unter OS X zu ermitteln.

Ich habe viele Antworten gesehen readlink -f $0. Da OS X readlinkmit BSD identisch ist, funktioniert es einfach nicht (es funktioniert mit der GNU-Version).

Gibt es dafür eine sofort einsatzbereite Lösung?

Die mächtige Gummiente
quelle

Antworten:

88

Es gibt eine realpath()C-Funktion, die den Job erledigt, aber ich sehe nichts in der Befehlszeile. Hier ist ein schneller und schmutziger Ersatz:

#!/bin/bash

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

realpath "$0"

Dies gibt den Pfad wörtlich aus, wenn er mit a beginnt /. Wenn nicht, muss es ein relativer Pfad sein, also geht es $PWDnach vorne. Das #./Teil wird ./von vorne abgezogen $1.

John Kugelman
quelle
1
Ich habe auch die C-Funktion bemerkt, konnte aber keine entsprechende Binärdatei oder ähnliches finden. Wie auch immer, Ihre Funktion passt einfach gut zu meinen Bedürfnissen. Vielen Dank!
Die mächtige Gummiente
7
Beachten Sie, dass dadurch keine Symlinks dereferenziert werden.
Adam Vandenberg
17
realpath ../somethingRückkehr$PWD/../something
Lri
Das hat bei mir funktioniert, aber ich habe es in "realpath_osx ()" umbenannt, da ich dieses Bash-Skript möglicherweise an eine Linux-Shell senden muss und nicht wollte, dass es mit dem "echten" Realpath kollidiert! (Ich bin sicher, es gibt einen eleganteren Weg, aber ich bin ein Bash n00b.)
Andrew Theken
8
command -v realpath >/dev/null 2>&1 || realpath() { ... }
Kara Brightwell
115

Diese drei einfachen Schritte werden dieses und viele andere OS X-Probleme lösen:

  1. Installieren Sie Homebrew
  2. brew install coreutils
  3. grealpath .

(3) kann auf nur geändert werden realpath, siehe (2) Ausgabe

Oleg Mikheev
quelle
3
Dies löst das Problem, erfordert jedoch die Installation von Dingen, die Sie möglicherweise nicht möchten oder benötigen oder die sich möglicherweise nicht auf dem Zielsystem befinden, z. B. OS X
Jason S,
6
@ Jason GNU Coreutils ist zu gut, um es nicht zu verwenden. Es enthält viele großartige Dienstprogramme. Es ist in der Tat so gut, dass der Linux-Kernel ohne ihn nutzlos ist und warum manche Leute ihn GNU / Linux nennen. Coreutils ist großartig. ausgezeichnete Antwort Dies sollte die ausgewählte Antwort sein.
7
@omouse In der Frage wird speziell "eine sofort einsatzbereite Lösung" (für OS X) erwähnt. Unabhängig davon, wie großartig Coreutils sind, ist es unter OS X nicht "out of the box", daher ist die Antwort nicht gut. Es geht nicht darum, wie gut Coreutils sind oder ob es besser ist als unter OS X. Es gibt Out-of-the-Box-Lösungen, $( cd "$(dirname "$0")" ; pwd -P )die für mich gut funktionieren.
Jason S
2
Eine der "Funktionen" von OS X ist, dass das Verzeichnis und die Dateinamen nicht zwischen Groß- und Kleinschreibung unterscheiden. Wenn Sie ein Verzeichnis aufgerufen haben XXXund eines cd xxxdann pwdzurückkehrt .../xxx. Mit Ausnahme dieser Antwort kehren alle oben genannten Lösungen zurück, xxxwenn Sie wirklich wollen XXX. Danke dir!
Andrew
1
Ich weiß nicht, wie das endlose Kopieren, Einfügen und Neuerfinden von Code besser ist als eine vorhandene Paketmanagerlösung. Wenn Sie gebraucht haben realpath, was passiert dann, wenn Sie mit ziemlicher Sicherheit andere Artikel von benötigen coreutils? Diese Funktionen auch in Bash umschreiben? : P
Ezekiel Victor
28

Pfui. Ich fand die vorherigen Antworten aus einigen Gründen etwas mangelhaft: Insbesondere lösen sie nicht mehrere Ebenen symbolischer Links auf und sind extrem "Bash-y". Während in der ursprünglichen Frage ausdrücklich nach einem "Bash-Skript" gefragt wird, wird auch die BSD-ähnliche Nicht-GNU von Mac OS X erwähnt readlink. Hier ist also ein Versuch einer vernünftigen Portabilität (ich habe ihn mit bash als 'sh' und einem Strich überprüft), bei dem eine beliebige Anzahl symbolischer Links aufgelöst wird. und es sollte auch mit Leerzeichen in den Pfaden funktionieren, obwohl ich mir nicht sicher bin, ob der Basisname des Dienstprogramms selbst Leerzeichen enthält. Vermeiden Sie das also, ähm?

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

Hoffe, das kann jemandem von Nutzen sein.

Geoff Nixon
quelle
1
Ich würde nur empfehlen, localVariablen zu verwenden , die innerhalb der Funktion definiert sind, um den globalen Namespace nicht zu verschmutzen. ZB local OURPWD=.... Funktioniert zumindest für Bash.
Michael Paesold
Außerdem sollte der Code für private Variablen keinen Großbuchstaben verwenden. Großbuchstabenvariablen sind für die Systemnutzung reserviert.
Tripleee
Danke für das Skript. Falls der / die Link (s) und die reale Datei unterschiedliche BASENAME=$(basename "$LINK") Basisnamen haben, ist es wahrscheinlich eine gute Idee, innerhalb der Weile einen hinzuzufügen und diesen im zweiten LINK-Setter und im REALPATH-Setter zu verwenden
stroborobo
Dies behandelt Symlinks und ..übergeordnete Refs nicht so, wie es der Fall realpathwäre. coreutilsVersuchen Sie es ln -s /var/log /tmp/linkexampledann mit installiertem Homebrew realpath /tmp/linkexample/../. Dies wird gedruckt /private/var. Aber Ihre Funktion erzeugt /tmp/linkexample/..stattdessen, weil ..es sich nicht um einen Symlink handelt.
Martijn Pieters
10

Eine befehlszeilenfreundlichere Variante der Python-Lösung:

python -c "import os; print(os.path.realpath('$1'))"
nanav yorbiz
quelle
2
Nur für den Fall, dass jemand verrückt genug ist, einen Python-Interpreter für einen einzelnen Befehl zu starten…
Bachsau
Ich war verrückt genug, aber benutztpython -c "import os; import sys; print(os.path.realpath(sys.argv[1]))"
Alex Chamberlain
7

Ich suchte nach einer Lösung für die Verwendung in einem Systembereitstellungsskript, dh vor der Installation von Homebrew. Ohne eine geeignete Lösung würde ich die Aufgabe einfach in eine plattformübergreifende Sprache verlagern, z. B. Perl:

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

Was wir eigentlich wollen, ist das enthaltende Verzeichnis:

here=$(perl -e 'use File::Basename; use Cwd "abs_path"; print dirname(abs_path(@ARGV[0]));' -- "$0")
4ae1e1
quelle
Toll! Perl ist schön für den kleinen Overhead! Sie könnten wahrscheinlich die erste Version vereinfachen FULLPATH=$(perl -e "use Cwd 'abs_path'; print abs_path('$0')"). Irgendein Grund dagegen?
F Pereira
@FPereira Das Generieren von Programmcode aus nicht vom Benutzer bereitgestellten Zeichenfolgen ist niemals eine gute Idee. ''ist nicht kugelsicher. Es bricht ab, wenn es beispielsweise $0ein einfaches Anführungszeichen enthält. Ein sehr einfaches Beispiel: Probieren Sie Ihre Version aus /tmp/'/test.shund rufen Sie /tmp/'/test.shden vollständigen Pfad auf.
4ae1e1
Oder noch einfacher /tmp/'.sh.
4ae1e1
6

Da gibt es einen realen Weg, wie andere betont haben:

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

Dann kompilieren Sie mit makeund fügen Sie einen Softlink ein mit:
ln -s $(pwd)/realpath /usr/local/bin/realpath

Waffel Souffle
quelle
könnten wir einfach tun gcc realpath.c -o /usr/local/bin/realpath?
Alexander Mills
1
@AlexanderMills Sie sollten den Compiler nicht als root ausführen. und wenn Sie dies nicht tun, haben Sie nicht die Privilegien, an die Sie schreiben können/usr/local/bin
Tripleee
6

Verwenden Sie Python, um es zu bekommen:

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

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

Unter macOS ist die einzige Lösung, die ich gefunden habe, um Symlinks zuverlässig zu verarbeiten, die Verwendung von realpath. Da dies erforderlich ist brew install coreutils, habe ich diesen Schritt nur automatisiert. Meine Implementierung sieht folgendermaßen aus:

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


Diese Fehler, wenn sie nicht brewinstalliert haben, aber Sie können das alternativ auch einfach installieren. Ich fühlte mich einfach nicht wohl dabei, etwas zu automatisieren, das beliebigen Ruby-Code aus dem Netz kräuselt.

Beachten Sie, dass dies eine automatisierte Variation der Antwort von Oleg Mikheev ist .


Ein wichtiger Test

Ein guter Test für eine dieser Lösungen ist:

  1. Fügen Sie den Code irgendwo in eine Skriptdatei ein
  2. in einem anderen Verzeichnis symlink ( ln -s) zu dieser Datei
  3. Führen Sie das Skript über diesen Symlink aus

Dereferenziert die Lösung den Symlink und gibt Ihnen das ursprüngliche Verzeichnis? Wenn ja, funktioniert es.

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

dirnamegibt den Verzeichnisnamen von /path/to/file, dh /path/to.

cd /path/to; pwd stellt sicher, dass der Pfad absolut ist.

basenamegibt nur den Dateinamen ein /path/to/file, dh file.

Logu
quelle
Während dieser Code die Frage möglicherweise beantwortet, verbessert die Bereitstellung eines zusätzlichen Kontexts darüber, warum und / oder wie dieser Code die Frage beantwortet, ihren langfristigen Wert.
Igor F.
1

Wie Sie oben sehen können, habe ich vor ungefähr 6 Monaten einen Versuch unternommen. Ich habe es total vergessen, bis ich wieder etwas Ähnliches brauchte. Ich war völlig geschockt zu sehen, wie rudimentär es war; Ich lehre mich seit ungefähr einem Jahr ziemlich intensiv das Codieren, aber ich habe oft das Gefühl, dass ich vielleicht gar nichts gelernt habe, wenn die Dinge am schlimmsten sind.

Ich würde die oben genannte "Lösung" entfernen, aber ich mag es wirklich, wenn ich aufzeichne, wie viel ich in den letzten Monaten wirklich gelernt habe.

Aber ich schweife ab. Ich habe mich letzte Nacht hingesetzt und alles ausgearbeitet. Die Erklärung in den Kommentaren sollte ausreichend sein. Wenn Sie die Kopie verfolgen möchten, an der ich weiter arbeite, können Sie diesem Kern folgen. Dies macht wahrscheinlich das, was Sie brauchen.

#!/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
quelle
1
Die Hauptverbindung scheint unterbrochen zu sein.
Alex
Diese Antwort funktioniert: stackoverflow.com/a/46772980/1223975 .... Sie sollten das einfach verwenden.
Alexander Mills
Beachten Sie, dass Ihre Implementierung Symlinks nicht vor ..übergeordneten Referenzen auflöst . zB /foo/link_to_other_directory/..wird /fooeher als das übergeordnete Element des Pfads aufgelöst, auf den der Symlink /foo/link_to_other_directoryzeigt. readlink -fund jede Pfadkomponenterealpath auflösen, beginnend am Stammverzeichnis und Aktualisierung der vorangestellten Verbindungsziele auf den Rest, der noch verarbeitet wird. Ich habe dieser Frage eine Antwort hinzugefügt, die diese Logik neu implementiert.
Martijn Pieters
1

Realpath für Mac OS X.

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

Beispiel mit verwandtem Pfad:

realpath "../scripts/test.sh"

Beispiel mit Home-Ordner

realpath "~/Test/../Test/scripts/test.sh"
Evgeny Karpov
quelle
1
schöne einfache lösung, ich habe nur eine vorbehalt gefunden, wenn ich sie damit aufrufe, ..ergibt sich nicht die richtige antwort, also habe ich eine überprüfung hinzugefügt, ob der angegebene pfad ein verzeichnis ist: if test -d $path ; then echo $(cd "$path"; pwd) ; else [...]
herbert
Hat bei mir nicht funktioniert, wohingegen "$(dirname $(dirname $(realpath $0)))"funktioniert, also brauche ich etwas anderes ...
Alexander Mills
Die nutzlose Verwendung vonecho sollte auch nicht begangen werden.
Tripleee
Dies löst Symlinks nicht so auf, wie es realpath tun würde. Übergeordnete ..Verweise werden vor dem Auflösen von Symlinks aufgelöst, nicht danach. Versuchen Sie dies mit coreutilsinstalliertem Homebrew , erstellen Sie einen Link mit ln -s /var/log /tmp/linkexampleund führen Sie ihn aus realpath /tmp/linkexample/../. Dies wird gedruckt /private/var. Aber Ihre Funktion produziert /tmp/linkexample/..stattdessen, weil pwdimmer noch /tmp/linkexamplenach der CD angezeigt wird.
Martijn Pieters
0

Dies scheint für OSX zu funktionieren, erfordert keine Binärdateien und wurde von hier gezogen

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

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

Ich mag das:

#!/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
quelle
0

Ich brauchte einen realpathErsatz auf OS X, eine , die richtig auf Wegen mit Symlinks und Eltern Referenzen arbeitet wie readlink -fWould . Dies umfasst das Auflösen von Symlinks im Pfad vor dem Auflösen übergeordneter Referenzen. Wenn Sie beispielsweise die Homebrew- coreutilsFlasche installiert haben, führen Sie Folgendes aus:

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

Beachten Sie, dass dies vor dem Auflösen der übergeordneten Verzeichnisreferenz readlink -fbehoben wurde . Natürlich gibt es keine auf Mac entweder ./tmp/linkeddir ..readlink -f

Als Teil der a bash-Implementierung für habe realpathich in Bash 3.2 erneut implementiert, was ein GNUlib- canonicalize_filename_mode(path, CAN_ALL_BUT_LAST)Funktionsaufruf bewirkt . Dies ist auch der Funktionsaufruf, den GNU readlink -fausführt:

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

Es umfasst die Erkennung kreisförmiger Symlinks, die beendet wird, wenn derselbe (Zwischen-) Pfad zweimal gesehen wird.

Wenn Sie nur Folgendes benötigen readlink -f, können Sie Folgendes verwenden:

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
}

Denn realpathich brauchte --relative-tound --relative-baseunterstütze auch, die dir nach der Kanonisierung relative Wege geben:

_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

Ich habe Unit-Tests in meine Code Review-Anfrage für diesen Code aufgenommen .

Martijn Pieters
quelle
-2

Aufgrund der Kommunikation mit dem Kommentator stimmte ich zu, dass es sehr schwierig ist und keine andere Möglichkeit hat, einen Realpath zu implementieren, der sich genauso verhält wie Ubuntu.

Aber die folgende Version kann Eckfälle behandeln, die beste Antwort kann nicht und meine täglichen Bedürfnisse auf MacBook befriedigen. Fügen Sie diesen Code in Ihr ~ / .bashrc ein und denken Sie daran:

  • arg kann nur 1 Datei oder Verzeichnis sein, kein Platzhalter
  • Keine Leerzeichen im Verzeichnis oder Dateinamen
  • Zumindest das übergeordnete Verzeichnis der Datei oder des Verzeichnisses ist vorhanden
  • Fühlen Sie sich frei zu verwenden. .. / Ding, diese sind sicher

    # 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
quelle
Sie möchten die nutzlose Verwendung vonecho vermeiden . Genauso pwdwie echo $(pwd)ohne eine zweite Kopie der Shell erzeugen zu müssen. Außerdem echoist es ein Fehler , das Argument nicht zu zitieren (Sie verlieren führende oder nachfolgende Leerzeichen, benachbarte interne Leerzeichen und erweiterte Platzhalter usw.). Siehe weitere stackoverflow.com/questions/10067266/…
Tripleee
Außerdem ist das Verhalten für nicht vorhandene Pfade fehlerhaft. aber ich denke, das ist es, was der Satz "aber erinnere dich" vielleicht zu sagen versucht. Obwohl das Verhalten unter Ubuntu sicherlich nicht darin besteht, das aktuelle Verzeichnis zu drucken, wenn Sie das realpatheines nicht vorhandenen Verzeichnisses anfordern .
Tripleee
Aus dir=$(dirname "$1"); file=$(basename "$1")Gründen der Konsistenz bevorzugen Sie wahrscheinlich anstelle der lang veralteten Backtick-Syntax. Beachten Sie auch wieder das korrekte Zitieren von Argumenten.
Tripleee
Ihre aktualisierte Antwort scheint viele der Fehler nicht zu beheben und fügt neue hinzu.
Tripleee
Bitte geben Sie mir einige spezifische fehlgeschlagene Fälle, da alle Tests, die ich in Ubuntu 18.04 Desktop mache, in Ordnung sind, danke.
Occia