Jak znormalizować ścieżkę do pliku w Bash?


203

Chcę przekształcić /foo/bar/..do/foo

Czy istnieje polecenie bash, które to robi?


Edycja: w moim praktycznym przypadku katalog istnieje.


4
Czy to ważne, /foo/barczy /fooistnieje, czy interesuje Cię tylko aspekt manipulacji ciągiem zgodnie z regułami nazw ścieżek?
Chen Levy

5
@twalberg ... to trochę wymyślone ...
Camilo Martin

2
@CamiloMartin W ogóle nie wymyślone - robi dokładnie to, o co pyta - przekształca /foo/bar/..się /fooi używa bashpolecenia. Jeśli są inne wymagania, które nie zostały określone, być może powinny one być ...
twalberg

14
@twalberg Za dużo robiłeś TDD -_- '
Camilo Martin

Odpowiedzi:


192

jeśli chcesz odciąć część nazwy pliku ze ścieżki, „dirname” i „basename” są twoimi przyjaciółmi, a „realpath” jest również przydatny.

dirname /foo/bar/baz 
# /foo/bar 
basename /foo/bar/baz
# baz
dirname $( dirname  /foo/bar/baz  ) 
# /foo 
realpath ../foo
# ../foo: No such file or directory
realpath /tmp/../tmp/../tmp
# /tmp

realpath alternatywy

Jeśli realpathnie jest obsługiwany przez twoją powłokę, możesz spróbować

readlink -f /path/here/.. 

Również

readlink -m /path/there/../../ 

Działa tak samo jak

realpath -s /path/here/../../

ponieważ ścieżka nie musi istnieć, aby zostać znormalizowana.


5
Dla tych z Was, którzy potrzebują rozwiązania OS X, sprawdź odpowiedź Adama Lissa poniżej.
Trenton

stackoverflow.com/a/17744637/999943 To jest silnie powiązana odpowiedź! Natrafiłem na oba te posty w ciągu jednego dnia i chciałem je połączyć.
phyatt,

wydaje się, że realpath została dodana do coreutils w 2012 roku. Zobacz historię plików na github.com/coreutils/coreutils/commits/master/src/realpath.c .
Noah Lavine

1
Zarówno realpathi readlinkpochodzi z podstawowych narzędzi GNU, więc najprawdopodobniej masz albo obu lub żadnego. Jeśli dobrze pamiętam, wersja readlink na Macu różni się nieco od wersji GNU: - \
Antoine 'hashar' Musso

100

Nie wiem, czy jest do tego polecenie bezpośredniego uderzenia, ale zwykle to robię

normalDir="`cd "${dirToNormalize}";pwd`"
echo "${normalDir}"

i działa dobrze.


9
Spowoduje to normalizację, ale nie rozwiąże miękkich linków. Może to być błąd lub funkcja. :-)
Adam Liss,

4
Ma również problem, jeśli zdefiniowano $ CDPATH; ponieważ „cd foo” przełączy się na dowolny katalog „foo” będący podkatalogiem $ CDPATH, a nie tylko „foo” znajdujący się w bieżącym katalogu. Myślę, że musisz zrobić coś takiego: CDPATH = "" cd "$ {dirToNormalize}" && pwd -P.
mjs

7
Odpowiedź Tima jest zdecydowanie najprostsza i najbardziej przenośna. Z CDPATH łatwo sobie poradzić: dir = "$ (unset CDPATH && cd" $ dir "&& pwd)"
David Blevins

2
Może to być bardzo niebezpieczne ( rm -rf $normalDir), jeśli dirToNormalize nie istnieje!
Frank Meulenaar

4
Tak, prawdopodobnie najlepiej jest użyć znaku &&jak na komentarz @ DavidBlevins.
Elias Dorneles

54

Spróbować realpath. Poniżej znajduje się całe źródło, niniejszym przekazane na rzecz domeny publicznej.

// realpath.c: display the absolute path to a file or directory.
// Adam Liss, August, 2007
// This program is provided "as-is" to the public domain, without express or
// implied warranty, for any non-profit use, provided this notice is maintained.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <libgen.h>   
#include <limits.h>

static char *s_pMyName;
void usage(void);

int main(int argc, char *argv[])
{
    char
        sPath[PATH_MAX];


    s_pMyName = strdup(basename(argv[0]));

    if (argc < 2)
        usage();

    printf("%s\n", realpath(argv[1], sPath));
    return 0;
}    

void usage(void)
{
    fprintf(stderr, "usage: %s PATH\n", s_pMyName);
    exit(1);
}

1
Czy jest to uwzględnione w ramach standardowej instalacji bash? Dostaję „polecenie nie znaleziono” w naszym systemie (bash 3.00.15 (1) na RHEL)
Tim Whitcomb,

4
gnu.org/software/coreutils dla readlink, ale realpath pochodzi z tego pakietu: packages.debian.org/unstable/utils/realpath
Kent Fredric

8
Jest standardem na Ubuntu / BSD, a nie Centos / OSX
Erik Aronesty

4
Powinieneś naprawdę przekreślić tę część z „Bonus: to polecenie bashowe i dostępne wszędzie!” część o realpath. Zdarza się również, że jest moim ulubionym, ale zamiast kompilować narzędzie ze źródła. Wszystko zależy od wagi ciężkiej i przez większość czasu po prostu chcesz readlink -f... BTW, readlinkto również NIE jest wbudowane bash, ale jest częścią coreutilsUbuntu.
Tomasz Gandor

4
Powiedziałeś, że przekazujesz to na rzecz domeny publicznej, ale w nagłówku jest także napisane „do celów niekomercyjnych” - czy naprawdę masz zamiar przekazać to na rzecz domeny publicznej? Oznaczałoby to, że można go wykorzystać do komercyjnych celów zarobkowych, a także do organizacji non-profit…
me_i

36

Przenośne i niezawodne rozwiązanie polega na użyciu Pythona, który jest zainstalowany niemal wszędzie (w tym Darwin). Masz dwie opcje:

  1. abspath zwraca ścieżkę bezwzględną, ale nie rozpoznaje dowiązań symbolicznych:

    python -c "import os,sys; print(os.path.abspath(sys.argv[1]))" path/to/file

  2. realpath zwraca ścieżkę bezwzględną iw ten sposób rozwiązuje dowiązania symboliczne, generując ścieżkę kanoniczną:

    python -c "import os,sys; print(os.path.realpath(sys.argv[1]))" path/to/file

W każdym przypadku path/to/filemoże to być ścieżka względna lub bezwzględna.


7
Dzięki, to był jedyny, który zadziałał. readlink lub realpath nie są dostępne w OS X. Python powinien znajdować się na większości platform.
sorin

1
Dla wyjaśnienia, readlinkjest dostępny w systemie OS X, ale nie z tą -fopcją. Omówione tutaj przenośne obejścia .
StvnW

3
Dosłownie zastanawia mnie, że jest to jedyne rozsądne rozwiązanie, jeśli nie chcesz podążać za linkami. Uniksowy sposób na moją stopę.
DannoHung

34

Użyj narzędzia readlink z pakietu coreutils.

MY_PATH=$(readlink -f "$0")

1
BSD nie ma flagi -f, co oznacza, że ​​zawiedzie nawet w najnowszym MacOS Mojave i wielu innych systemach. Zapomnij o użyciu opcji -f, jeśli chcesz mieć przenośność, wpływa to na wiele systemów operacyjnych.
sorin

1
@sorin. Pytanie nie dotyczy Maca, tylko Linuksa.
mattalxndr

14

readlinkjest standardem bash dla uzyskania absolutnej ścieżki. Ma również tę zaletę, że zwraca puste ciągi, jeśli ścieżki lub ścieżka nie istnieje (biorąc pod uwagę flagi).

Aby uzyskać bezwzględną ścieżkę do katalogu, który może istnieć, ale którego rodzice nie istnieją, użyj:

abspath=$(readlink -f $path)

Aby uzyskać bezwzględną ścieżkę do katalogu, który musi istnieć wraz ze wszystkimi rodzicami:

abspath=$(readlink -e $path)

Aby kanonizować podaną ścieżkę i podążać za dowiązaniami symbolicznymi, jeśli takowe istnieją, ale w przeciwnym razie zignoruj ​​brakujące katalogi i po prostu zwróć ścieżkę, to:

abspath=$(readlink -m $path)

Jedynym minusem jest to, że readlink będzie podążał za linkami. Jeśli nie chcesz podążać za linkami, możesz skorzystać z tej alternatywnej konwencji:

abspath=$(cd ${path%/*} && echo $PWD/${path##*/})

Spowoduje to przejście do części katalogu $ path i wydrukowanie bieżącego katalogu wraz z częścią pliku $ path. Jeśli nie uda się zmienić chdir, otrzymasz pusty ciąg i błąd na stderr.


7
readlinkto dobra opcja, jeśli jest dostępna. Wersja systemu OS X nie obsługuje opcji -elub -f. W pierwszych trzech przykładach powinieneś mieć podwójne cudzysłowy $pathdo obsługi spacji lub symboli wieloznacznych w nazwie pliku. +1 za rozszerzenie parametrów, ale ma to lukę w zabezpieczeniach. Jeśli pathjest pusty, zostanie to przeniesione cddo katalogu domowego. Potrzebujesz podwójnych cudzysłowów. abspath=$(cd "${path%/*}" && echo "$PWD/${path##*/}")
toxalot

To był tylko przykład. Jeśli zależy ci na bezpieczeństwie, naprawdę nie powinieneś używać bash ani żadnego innego wariantu powłoki. Ponadto bash ma swoje własne problemy, jeśli chodzi o zgodność między platformami, a także problemy ze zmianą funkcjonalności między głównymi wersjami. OSX jest tylko jedną z wielu platform z problemami związanymi ze skryptami powłoki, nie wspominając już, że jest oparty na BSD. Jeśli musisz być naprawdę wieloplatformowy, musisz być zgodny z POSIX, więc rozszerzenie parametrów naprawdę wychodzi poza okno. Przyjrzyj się kiedyś Solarisowi lub HP-UX.
Craig

6
Nie oznacza to żadnego przestępstwa, ale wskazanie niejasnych kwestii, takich jak to, jest ważne. Chcę tylko szybkiej odpowiedzi na ten trywialny problem i ufałbym temu kodowi przy użyciu dowolnego / wszystkich danych wejściowych, gdyby nie powyższy komentarz. Ważne jest również wsparcie OS-X w tych dyskusjach dotyczących bash. Istnieje wiele poleceń, które niestety nie są obsługiwane w systemie OS-X, a wiele forów bierze to za pewnik podczas omawiania Bash, co oznacza, że ​​będziemy nadal otrzymywać wiele problemów na różnych platformach, chyba że zostanie to rozwiązane wcześniej niż później.
Rebs

13

Stare pytanie, ale jest o wiele prostszy sposób, jeśli masz do czynienia z pełnymi nazwami ścieżek na poziomie powłoki:

   abspath = "$ (cd" $ path "&& pwd)"

Ponieważ cd dzieje się w podpowłoce, nie ma to wpływu na główny skrypt.

Dwie odmiany, zakładając, że wbudowane polecenia powłoki akceptują -L i -P, to:

   abspath = "$ (cd -P" $ path "&& pwd -P)" # ścieżka fizyczna z rozwiązanymi dowiązaniami symbolicznymi
   abspath = "$ (cd -L" $ path "&& pwd -L)" #logiczna ścieżka zachowująca dowiązania symboliczne

Osobiście rzadko potrzebuję tego późniejszego podejścia, chyba że z jakiegoś powodu fascynują mnie dowiązania symboliczne.

FYI: wariant uzyskania katalogu początkowego skryptu, który działa, nawet jeśli skrypt zmieni później swój katalog bieżący.

name0 = "$ (basename" $ ​​0 ")"; # nazwa bazy skryptu
dir0 = "$ (cd" $ (nazwa katalogu "$ 0") "&& pwd)"; #absolute start reż

Korzystanie z CD zapewnia, że ​​zawsze masz katalog absolutny, nawet jeśli skrypt jest uruchamiany przez polecenia takie jak ./script.sh, które bez cd / pwd często dają po prostu ... Bezużyteczne, jeśli skrypt zrobi później płytę CD.


8

Jak zauważył Adam Liss, realpathnie jest on zawarty w każdej dystrybucji. Szkoda, bo to najlepsze rozwiązanie. Podany kod źródłowy jest świetny i prawdopodobnie zacznę go teraz używać. Oto, czego używałem do tej pory, które udostępniam tutaj tylko dla kompletności:

get_abs_path() {
     local PARENT_DIR=$(dirname "$1")
     cd "$PARENT_DIR"
     local ABS_PATH="$(pwd)"/"$(basename "$1")"
     cd - >/dev/null
     echo "$ABS_PATH"
} 

Jeśli chcesz go do dowiązania postanowienie, wystarczy wymienić pwdz pwd -P.


Jedną mam pwd -Popcję dla tej sprawy ... Zastanów się, co by się stało, gdyby $(basename "$1")dowiązanie symboliczne do pliku w innym katalogu. pwd -PRozwiązuje tylko dowiązania w części katalogowej ścieżki, ale nie część basename.
toxalot

7

Moje ostatnie rozwiązanie to:

pushd foo/bar/..
dir=`pwd`
popd

Na podstawie odpowiedzi Tima Whitcomba.


Podejrzewam, że to się nie powiedzie, jeśli argument nie jest katalogiem. Załóżmy, że chcę wiedzieć, dokąd prowadzi / usr / bin / java?
Edward Falk,

1
Jeśli wiesz, że to plik, możesz pushd $(dirname /usr/bin/java)spróbować.
schmunk

5

Nie do końca odpowiedź, ale być może pytanie uzupełniające (pierwotne pytanie nie było jednoznaczne):

readlinkjest w porządku, jeśli faktycznie chcesz podążać za dowiązaniami symbolicznymi. Ale jest też przypadek użycia jedynie dla normalizacji ./i ../i //sekwencje, które można zrobić czysto składniowo, bez canonicalizing dowiązania. readlinknie nadaje się do tego i nie jest realpath.

for f in $paths; do (cd $f; pwd); done

działa dla istniejących ścieżek, ale zrywa dla innych.

sedScenariusz wydaje się być dobry zakład, z wyjątkiem, że nie można iteracyjnie zastąpić sekwencje ( /foo/bar/baz/../..-> /foo/bar/..-> /foo) bez użycia coś jak Perl, których nie można bezpiecznie przyjąć na wszystkich systemach, lub przy użyciu pewnego brzydkiego pętlę porównać dane wyjściowe seddo jego wkład.

FWIW, jedno-liniowy przy użyciu Java (JDK 6+):

jrunscript -e 'for (var i = 0; i < arguments.length; i++) {println(new java.io.File(new java.io.File(arguments[i]).toURI().normalize()))}' $paths

realpathma -smożliwość nie rozstrzygnięcia dowiązania symboliczne i tylko referencje postanowienie /./, /../i usunąć dodatkowe /znaki. W połączeniu z -mopcją realpathdziała tylko na nazwie pliku i nie dotyka żadnego rzeczywistego pliku. To brzmi jak idealne rozwiązanie. Niestety, realpathwciąż brakuje go na wielu systemach.
toxalot

Usunięcia ..składników nie można wykonać składniowo, gdy w grę wchodzą dowiązania symboliczne. /one/two/../threenie jest tym samym co /one/threejeśli twojest dowiązaniem do /foo/bar.
jrw32982 obsługuje Monikę

@ jrw32982 tak, jak powiedziałem w mojej odpowiedzi, jest to przypadek użycia, gdy kanonizacja dowiązań symbolicznych nie jest potrzebna ani potrzebna.
Jesse Glick,

@JesseGlick to nie tylko kwestia, czy chcesz kanonizować dowiązania symboliczne. Twój algorytm faktycznie podaje złą odpowiedź. Aby Twoja odpowiedź była prawidłowa, musisz z góry wiedzieć, że nie było w niej żadnych dowiązań symbolicznych (lub że miały one tylko określoną formę). Twoja odpowiedź mówi, że nie chcesz ich kanonizować, nie że w ścieżce nie ma żadnych dowiązań symbolicznych.
jrw32982 obsługuje Monikę

Istnieją przypadki użycia, w których normalizację należy przeprowadzić bez zakładania żadnej stałej, istniejącej struktury katalogów. Normalizacja URI jest podobna. W takich przypadkach nieodłącznym ograniczeniem jest to, że wynik na ogół nie będzie poprawny, jeśli w pobliżu katalogu, w którym wynik zostanie później zastosowany, pojawią się dowiązania symboliczne.
Jesse Glick,

5

Jestem spóźniony na imprezę, ale oto rozwiązanie, które stworzyłem po przeczytaniu kilku takich wątków:

resolve_dir() {
        (builtin cd `dirname "${1/#~/$HOME}"`'/'`basename "${1/#~/$HOME}"` 2>/dev/null; if [ $? -eq 0 ]; then pwd; fi)
}

To rozwiąże bezwzględną ścieżkę 1 $, będziesz grać fajnie z ~, utrzymywać dowiązania symboliczne na ścieżce, na której się znajdują, i nie będzie bałaganu ze stosem katalogów. Zwraca pełną ścieżkę lub nic, jeśli nie istnieje. Oczekuje, że 1 USD będzie katalogiem i prawdopodobnie nie powiedzie się, jeśli tak nie jest, ale łatwo to zrobić samodzielnie.


4

Rozmowna i nieco spóźniona odpowiedź. Muszę napisać jeden, ponieważ utknąłem na starszym RHEL4 / 5. Obsługuję linki bezwzględne i względne oraz upraszczam //, /./ i somedir /../ wpisy.

test -x /usr/bin/readlink || readlink () {
        echo $(/bin/ls -l $1 | /bin/cut -d'>' -f 2)
    }


test -x /usr/bin/realpath || realpath () {
    local PATH=/bin:/usr/bin
    local inputpath=$1
    local changemade=1
    while [ $changemade -ne 0 ]
    do
        changemade=0
        local realpath=""
        local token=
        for token in ${inputpath//\// }
        do 
            case $token in
            ""|".") # noop
                ;;
            "..") # up one directory
                changemade=1
                realpath=$(dirname $realpath)
                ;;
            *)
                if [ -h $realpath/$token ] 
                then
                    changemade=1
                    target=`readlink $realpath/$token`
                    if [ "${target:0:1}" = '/' ]
                    then
                        realpath=$target
                    else
                        realpath="$realpath/$target"
                    fi
                else
                    realpath="$realpath/$token"
                fi
                ;;
            esac
        done
        inputpath=$realpath
    done
    echo $realpath
}

mkdir -p /tmp/bar
(cd /tmp ; ln -s /tmp/bar foo; ln -s ../.././usr /tmp/bar/link2usr)
echo `realpath /tmp/foo`

3

Wypróbuj nasz nowy produkt biblioteczny Bash realpath-lib , który umieściliśmy w GitHub do bezpłatnego i nieobciążonego użytku. Jest dokładnie udokumentowany i stanowi świetne narzędzie do nauki.

Rozwiązuje ścieżki lokalne, względne i bezwzględne i nie ma żadnych zależności oprócz Bash 4+; więc powinno działać prawie wszędzie. Jest bezpłatny, czysty, prosty i pouczający.

Możesz to zrobić:

get_realpath <absolute|relative|symlink|local file path>

Ta funkcja jest rdzeniem biblioteki:

function get_realpath() {

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

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

}

Zawiera także funkcje do get_dirname, get_filename, get_ stemname i validate_path. Wypróbuj go na różnych platformach i pomóż go ulepszyć.


2

Problem realpathpolega na tym, że nie jest on dostępny w BSD (lub OSX, jeśli o to chodzi). Oto prosty przepis wyodrębniony z dość starego (2009) artykułu z Linux Journal , który jest dość przenośny:

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

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

Zauważ, że ten wariant również nie wymaga istnienia ścieżki.


Nie rozwiązuje to jednak dowiązań symbolicznych. Realpath przetwarza ścieżki zaczynające się od katalogu głównego i postępuje zgodnie z dowiązaniami symbolicznymi. Wszystko to zwija referencje nadrzędne.
Martijn Pieters

2

W oparciu o odpowiedź @ Andre, mogę mieć nieco lepszą wersję, na wypadek, gdyby ktoś szukał rozwiązania bez pętli, całkowicie opartego na manipulowaniu ciągami. Jest także przydatny dla tych, którzy nie chcą rezygnować z żadnych dowiązań symbolicznych, co jest wadą używania realpathlub readlink -f.

Działa na wersjach bash 3.2.25 i wyższych.

shopt -s extglob

normalise_path() {
    local path="$1"
    # get rid of /../ example: /one/../two to /two
    path="${path//\/*([!\/])\/\.\./}"
    # get rid of /./ and //* example: /one/.///two to /one/two
    path="${path//@(\/\.\/|\/+(\/))//}"
    # remove the last '/.'
    echo "${path%%/.}"
}

$ normalise_path /home/codemedic/../codemedic////.config
/home/codemedic/.config

To fajny pomysł, ale zmarnowałem 20 minut, próbując zmusić go do działania w różnych wersjach bash. Okazuje się, że opcja powłoki extglob musi być włączona, aby to działało i nie jest to domyślnie. Jeśli chodzi o funkcjonalność bash, ważne jest, aby określić zarówno wymaganą wersję, jak i domyślne opcje, ponieważ te szczegóły mogą się różnić w zależności od systemu operacyjnego. Na przykład najnowsza wersja Mac OSX (Yosemite) zawiera tylko przestarzałą wersję bash (3.2).
drwatsoncode

Przepraszam @ricovox; Teraz je zaktualizowałem. Chętnie poznam dokładną wersję Basha, którą tam masz. Powyższa formuła (zaktualizowana) działa na CentOS 5.8, który jest dostarczany z bash
3.2.25

Przepraszam za zamieszanie. Ten kod DID działał w mojej wersji Mac OSX bash (3.2.57) po włączeniu extglob. Moja uwaga na temat wersji bash była bardziej ogólna (która faktycznie odnosi się bardziej do innej odpowiedzi tutaj dotyczącej wyrażeń regularnych w bash).
drwatsoncode

2
Doceniam twoją odpowiedź. Użyłem go jako podstawy do własnych celów. Nawiasem mówiąc, zauważyłem kilka przypadków, w których twój zawodzi: (1) Ścieżki względne hello/../world(2) Kropki w nazwie pliku /hello/..world(3) Kropki po podwójnym ukośniku /hello//../world(4) Kropka przed lub po podwójnym ukośniku /hello//./worldlub /hello/.//world (5) Rodzic po bieżącym: /hello/./../world/ (6) Rodzic after Parent: /hello/../../worlditp. - Niektóre z nich można naprawić za pomocą pętli, aby wprowadzić poprawki, dopóki ścieżka nie przestanie się zmieniać. (Usuń także dir/../, /dir/..ale nie usuń dir/..od końca.)
drwatsoncode

0

Na podstawie doskonałego fragmentu python loveborga napisałem:

#!/bin/sh

# Version of readlink that follows links to the end; good for Mac OS X

for file in "$@"; do
  while [ -h "$file" ]; do
    l=`readlink $file`
    case "$l" in
      /*) file="$l";;
      *) file=`dirname "$file"`/"$l"
    esac
  done
  #echo $file
  python -c "import os,sys; print os.path.abspath(sys.argv[1])" "$file"
done

0
FILEPATH="file.txt"
echo $(realpath $(dirname $FILEPATH))/$(basename $FILEPATH)

Działa to nawet wtedy, gdy plik nie istnieje. Wymaga istnienia katalogu zawierającego plik.


GNU Realpath nie wymaga również ostatniego elementu na ścieżce, chyba że użyjeszrealpath -e
Martijn Pieters

0

Potrzebowałem rozwiązania, które spełniłoby wszystkie trzy:

  • Pracuj na standardowym komputerze Mac. realpathireadlink -f są dodatkami
  • Rozwiąż dowiązania symboliczne
  • Mieć obsługę błędów

Żadna z odpowiedzi nie miała zarówno # 1, jak i # 2. Dodałem # 3, aby uratować innych przed dalszym goleniem jaków.

#!/bin/bash

P="${1?Specify a file path}"

[ -e "$P" ] || { echo "File does not exist: $P"; exit 1; }

while [ -h "$P" ] ; do
    ls="$(ls -ld "$P")"
    link="$(expr "$ls" : '.*-> \(.*\)$')"
    expr "$link" : '/.*' > /dev/null &&
        P="$link" ||
        P="$(dirname "$P")/$link"
done
echo "$(cd "$(dirname "$P")"; pwd)/$(basename "$P")"

Oto krótki przypadek testowy z zakrzywionymi spacjami na ścieżkach, aby w pełni wykonać cytowanie

mkdir -p "/tmp/test/ first path "
mkdir -p "/tmp/test/ second path "
echo "hello" > "/tmp/test/ first path / red .txt "
ln -s "/tmp/test/ first path / red .txt " "/tmp/test/ second path / green .txt "

cd  "/tmp/test/ second path "
fullpath " green .txt "
cat " green .txt "

0

Wiem, że to starożytne pytanie. Nadal oferuję alternatywę. Ostatnio spotkałem ten sam problem i nie znalazłem żadnego istniejącego i przenośnego polecenia, aby to zrobić. Napisałem więc następujący skrypt powłoki, który zawiera funkcję, która może załatwić sprawę.

#! /bin/sh                                                                                                                                                

function normalize {
  local rc=0
  local ret

  if [ $# -gt 0 ] ; then
    # invalid
    if [ "x`echo $1 | grep -E '^/\.\.'`" != "x" ] ; then
      echo $1
      return -1
    fi

    # convert to absolute path
    if [ "x`echo $1 | grep -E '^\/'`" == "x" ] ; then
      normalize "`pwd`/$1"
      return $?
    fi

    ret=`echo $1 | sed 's;/\.\($\|/\);/;g' | sed 's;/[^/]*[^/.]\+[^/]*/\.\.\($\|/\);/;g'`
  else
    read line
    normalize "$line"
    return $?
  fi

  if [ "x`echo $ret | grep -E '/\.\.?(/|$)'`" != "x" ] ; then
    ret=`normalize "$ret"`
    rc=$?
  fi

  echo "$ret"
  return $rc
}

https://gist.github.com/bestofsong/8830bdf3e5eb9461d27313c3c282868c


0

Zrobiłem tylko wbudowaną funkcję, aby poradzić sobie z tym, koncentrując się na najwyższej możliwej wydajności (dla zabawy). Nie rozpoznaje dowiązań symbolicznych, więc jest w zasadzie taki sam jak realpath -sm.

## A bash-only mimic of `realpath -sm`. 
## Give it path[s] as argument[s] and it will convert them to clean absolute paths
abspath () { 
  ${*+false} && { >&2 echo $FUNCNAME: missing operand; return 1; };
  local c s p IFS='/';  ## path chunk, absolute path, input path, IFS for splitting paths into chunks
  local -i r=0;         ## return value

  for p in "$@"; do
    case "$p" in        ## Check for leading backslashes, identify relative/absolute path
    '') ((r|=1)); continue;;
    //[!/]*)  >&2 echo "paths =~ ^//[^/]* are impl-defined; not my problem"; ((r|=2)); continue;;
    /*) ;;
    *)  p="$PWD/$p";;   ## Prepend the current directory to form an absolute path
    esac

    s='';
    for c in $p; do     ## Let IFS split the path at '/'s
      case $c in        ### NOTE: IFS is '/'; so no quotes needed here
      ''|.) ;;          ## Skip duplicate '/'s and '/./'s
      ..) s="${s%/*}";; ## Trim the previous addition to the absolute path string
      *)  s+=/$c;;      ### NOTE: No quotes here intentionally. They make no difference, it seems
      esac;
    done;

    echo "${s:-/}";     ## If xpg_echo is set, use `echo -E` or `printf $'%s\n'` instead
  done
  return $r;
}

Uwaga: Ta funkcja nie obsługuje ścieżek zaczynających się od //, ponieważ dokładnie dwa podwójne ukośniki na początku ścieżki są zachowaniem zdefiniowanym przez implementację. Jednakże, uchwyty /,/// i tak dalej dobrze.

Ta funkcja wydaje się poprawnie obsługiwać wszystkie przypadki krawędzi, ale może być jeszcze kilka takich, z którymi nie miałem do czynienia.

Uwaga dotycząca wydajności: wywołana z tysiącami argumentów abspathdziała około 10 razy wolniej niż realpath -sm; wywołany jednym argumentem abspathdziała> 110 razy szybciej niż realpath -smna moim komputerze, głównie z powodu braku konieczności uruchamiania nowego programu za każdym razem.


-1

Odkryłem dzisiaj, że możesz użyć stat polecenia do rozpoznawania ścieżek.

W przypadku katalogu takiego jak „~ / Documents”:

Możesz uruchomić to:

stat -f %N ~/Documents

Aby uzyskać pełną ścieżkę:

/Users/me/Documents

W przypadku dowiązań symbolicznych możesz użyć opcji formatu% Y:

stat -f %Y example_symlink

Co może zwrócić wynik taki jak:

/usr/local/sbin/example_symlink

Opcje formatowania mogą się różnić w innych wersjach * NIX, ale te działały dla mnie w OSX.


1
stat -f %N ~/Documentslinia jest czerwony śledź ... twoja powłoka zastępuje ~/Documentssię /Users/me/Documents, i statjest po prostu drukuje swój argument dosłownie.
danwyand

-4

Proste rozwiązanie wykorzystujące node.js:

#!/usr/bin/env node
process.stdout.write(require('path').resolve(process.argv[2]));
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.