Làm thế nào để bạn bình thường hóa một đường dẫn tệp trong Bash?


203

Tôi muốn chuyển đổi /foo/bar/..sang/foo

Có một lệnh bash nào làm điều này?


Chỉnh sửa: trong trường hợp thực tế của tôi, thư mục không tồn tại.


4
Có vấn đề nếu /foo/barhoặc thậm chí /foothực sự tồn tại, hoặc bạn chỉ quan tâm đến khía cạnh thao tác chuỗi theo quy tắc tên đường dẫn?
Chen Levy

5
@twalberg ... điều đó hơi khó hiểu ...
Camilo Martin

2
@CamiloMartin Hoàn toàn không có ý định - nó thực hiện chính xác những gì câu hỏi yêu cầu - chuyển đổi /foo/bar/..thành /foovà sử dụng một bashlệnh. Nếu có những yêu cầu khác không được nêu, thì có lẽ chúng nên ...
twalberg

14
@twalberg Bạn đã làm quá nhiều TDD -_- '
Camilo Martin

Câu trả lời:


192

nếu bạn muốn chomp một phần tên tệp từ đường dẫn, "dirname" và "tên cơ sở" là bạn bè của bạn và "realpath" cũng tiện dụng.

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 lựa chọn thay thế

Nếu realpathkhông được hỗ trợ bởi shell của bạn, bạn có thể thử

readlink -f /path/here/.. 

Cũng thế

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

Hoạt động giống như

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

trong đó đường dẫn không cần phải tồn tại để được chuẩn hóa.


5
Đối với những người bạn cần giải pháp OS X, hãy xem câu trả lời của Adam Liss bên dưới.
Trenton

stackoverflow.com/a/17744637/999943 Đây là một câu trả lời liên quan mạnh mẽ! Tôi đã xem qua cả hai bài đăng QA này trong một ngày và tôi muốn liên kết chúng với nhau.
phyatt

realpath dường như đã được thêm vào coreutils vào năm 2012. Xem lịch sử tập tin tại github.com/coreutils/coreutils/commits/master/src/realpath.c .

1
Cả hai realpathreadlinkđều xuất phát từ các tiện ích lõi GNU, vì vậy hầu hết có thể bạn nhận được cả hai hoặc không. Nếu tôi nhớ chính xác, phiên bản readlink trên Mac hơi khác so với phiên bản GNU: - \
Antoine 'hashar' Musso

100

Tôi không biết nếu có lệnh bash trực tiếp để làm điều này, nhưng tôi thường làm

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

và nó hoạt động tốt.


9
Điều này sẽ bình thường hóa nhưng không giải quyết các liên kết mềm. Đây có thể là một lỗi hoặc một tính năng. :-)
Adam Liss

4
Nó cũng có vấn đề nếu $ CDPATH được xác định; bởi vì "cd foo" sẽ chuyển sang bất kỳ thư mục "foo" nào là thư mục con của $ CDPATH, không chỉ là "foo" trong thư mục hiện tại. Tôi nghĩ rằng bạn cần phải làm một cái gì đó như: CDPATH = "" cd "$ {dirToN normalize}" && pwd -P.
mjs

7
Câu trả lời của Tim chắc chắn là đơn giản và dễ mang theo nhất. CDPATH rất dễ đối phó với: dir = "$ (bỏ đặt CDPATH && cd" $ dir "&& pwd)"
David Blevins

2
Điều này có thể rất nguy hiểm ( rm -rf $normalDir) nếu dirToN normalize không tồn tại!
Frank Meulenaar

4
Vâng, có lẽ tốt nhất là sử dụng &&nhận xét theo @ DavidBlevins.
Elias Dornele

54

Hãy thử realpath. Dưới đây là toàn bộ nguồn, từ đây được tặng cho phạm vi công cộng.

// 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
Đây có phải là một phần của cài đặt bash tiêu chuẩn không? Tôi đang nhận được "lệnh không tìm thấy" trên hệ thống của chúng tôi (bash 3.00.15 (1) trên RHEL)
Tim Whitcomb

4
gnu.org/software/coreutils cho readlink, nhưng realpath xuất phát từ gói này: packages.debian.org/unstable/utils/realpath
Kent Fredric

8
Đó là tiêu chuẩn trên Ubuntu / BSD, không phải Centos / OSX
Erik Aronesty

4
Bạn nên thực sự tấn công phần này bằng "Tiền thưởng: đó là lệnh bash, và có sẵn ở mọi nơi!" một phần về realpath. Nó cũng là sở thích của tôi, nhưng thay vì bắt buộc công cụ của bạn từ nguồn. Đó là tất cả để nặng, và hầu hết thời gian bạn chỉ muốn readlink -f... BTW, readlinkcũng KHÔNG phải là một bash dựng sẵn, mà là một phần của coreutilsUbuntu.
Tomasz Gandor

4
Bạn đã nói rằng bạn quyên tặng cái này cho miền công cộng, nhưng tiêu đề cũng nói "cho bất kỳ mục đích phi lợi nhuận nào" - bạn có thực sự muốn tặng nó cho miền công cộng không? Điều đó có nghĩa là nó có thể được sử dụng cho mục đích thương mại cũng như cho mục đích phi lợi nhuận
me_and

36

Một giải pháp di động và đáng tin cậy là sử dụng python, được cài đặt sẵn khá nhiều ở mọi nơi (bao gồm cả Darwin). Bạn có hai lựa chọn:

  1. abspath trả về một đường dẫn tuyệt đối nhưng không giải quyết được các liên kết tượng trưng:

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

  2. realpath trả về một đường dẫn tuyệt đối và khi làm như vậy sẽ giải quyết các liên kết tượng trưng, ​​tạo ra một đường dẫn chính tắc:

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

Trong mỗi trường hợp, path/to/filecó thể là một đường dẫn tương đối hoặc tuyệt đối.


7
Cảm ơn, đó là người duy nhất làm việc. readlink hoặc realpath không khả dụng trong OS X. Python phải có trên hầu hết các nền tảng.
sorin

1
Chỉ cần làm rõ, readlinkcó sẵn trên OS X, không phải với -ftùy chọn. Giải pháp di động thảo luận ở đây .
StvnW

3
Theo nghĩa đen, tôi nghĩ rằng đây là giải pháp lành mạnh duy nhất nếu bạn không muốn theo liên kết. Unix theo cách CHÂN của tôi.
DannoHung

34

Sử dụng tiện ích readlink từ gói coreutils.

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

1
BSD không có cờ -f, có nghĩa là điều này sẽ thất bại ngay cả trên MacOS Mojave mới nhất và nhiều hệ thống khác. Hãy quên việc sử dụng -f nếu bạn muốn tính di động, rất nhiều OS-es bị ảnh hưởng.
sorin

1
@sorin. Câu hỏi không phải là về Mac, mà là về Linux.
mattalxndr

14

readlinklà tiêu chuẩn bash để có được đường dẫn tuyệt đối. Nó cũng có lợi thế là trả về các chuỗi trống nếu các đường dẫn hoặc một đường dẫn không tồn tại (được cung cấp các cờ để làm như vậy).

Để có được đường dẫn tuyệt đối đến một thư mục có thể tồn tại hoặc không tồn tại, nhưng cha mẹ của ai có tồn tại, hãy sử dụng:

abspath=$(readlink -f $path)

Để có được đường dẫn tuyệt đối đến một thư mục phải tồn tại cùng với tất cả các bậc cha mẹ:

abspath=$(readlink -e $path)

Để chuẩn hóa đường dẫn đã cho và theo liên kết tượng trưng nếu chúng tình cờ tồn tại, nhưng nếu không thì bỏ qua các thư mục bị thiếu và chỉ cần trả lại đường dẫn, đó là:

abspath=$(readlink -m $path)

Nhược điểm duy nhất là readlink sẽ theo liên kết. Nếu bạn không muốn theo liên kết, bạn có thể sử dụng quy ước thay thế này:

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

Điều đó sẽ chdir đến phần thư mục của $ path và in thư mục hiện tại cùng với phần tệp của $ path. Nếu nó không thành công, bạn nhận được một chuỗi rỗng và lỗi trên stderr.


7
readlinklà một lựa chọn tốt nếu nó có sẵn. Phiên bản OS X không hỗ trợ -ehoặc -ftùy chọn. Trong ba ví dụ đầu tiên, bạn nên có dấu ngoặc kép xung quanh $pathđể xử lý khoảng trắng hoặc ký tự đại diện trong tên tệp. +1 để mở rộng tham số, nhưng điều này có lỗ hổng bảo mật. Nếu pathtrống, điều này sẽ cdvào thư mục nhà của bạn. Bạn cần báo giá gấp đôi. abspath=$(cd "${path%/*}" && echo "$PWD/${path##*/}")
toxalot

Đây chỉ là một ví dụ. Nếu bạn không muốn bảo mật, thì bạn thực sự không nên sử dụng bash hoặc bất kỳ biến thể vỏ nào khác. Ngoài ra, bash có vấn đề riêng khi tương thích đa nền tảng, cũng như có vấn đề với thay đổi chức năng giữa các phiên bản chính. OSX chỉ là một trong nhiều nền tảng có vấn đề liên quan đến kịch bản shell, chưa kể nó dựa trên BSD. Khi bạn phải thực sự đa nền tảng, bạn cần tuân thủ POSIX, vì vậy việc mở rộng tham số thực sự vượt ra khỏi cửa sổ. Hãy xem Solaris hoặc HP-UX một thời gian.
Craig

6
Không có nghĩa là bất kỳ hành vi phạm tội ở đây, nhưng chỉ ra các vấn đề tối nghĩa như điều này là quan trọng. Tôi chỉ muốn một câu trả lời nhanh cho vấn đề tầm thường này và tôi đã tin tưởng mã đó với bất kỳ / tất cả đầu vào nếu nó không cho nhận xét ở trên. Điều quan trọng nữa là hỗ trợ OS-X trong các cuộc thảo luận bash này. Rất tiếc, có rất nhiều lệnh không được hỗ trợ trên OS-X và nhiều diễn đàn cho rằng khi được thảo luận về Bash, điều đó có nghĩa là chúng ta sẽ tiếp tục gặp nhiều vấn đề đa nền tảng trừ khi nó được xử lý sớm hơn.
Hoàn trả

13

Câu hỏi cũ, nhưng có cách đơn giản hơn nhiều nếu bạn đang xử lý tên đường dẫn đầy đủ ở cấp độ vỏ:

   abspath = "$ (cd" $ đường dẫn "&& pwd)"

Vì cd xảy ra trong một subshell, nó không ảnh hưởng đến tập lệnh chính.

Hai biến thể, giả sử các lệnh tích hợp trong shell của bạn chấp nhận -L và -P, là:

   abspath = "$ (cd -P" $ path "&& pwd -P)" #physical path với các liên kết được giải quyết
   abspath = "$ (cd -L" $ path "&& pwd -L)" #logical path bảo tồn các liên kết tượng trưng

Cá nhân, tôi hiếm khi cần cách tiếp cận sau này trừ khi tôi say mê với các liên kết tượng trưng vì một số lý do.

FYI: biến thể trong việc lấy thư mục bắt đầu của tập lệnh hoạt động ngay cả khi tập lệnh thay đổi thư mục hiện tại sau này.

name0 = "$ (tên cơ sở" $ 0 ")"; #base tên của tập lệnh
dir0 = "$ (cd" $ (tên hiệu "$ 0") "&& pwd)"; #absolute bắt đầu dir

Việc sử dụng CD đảm bảo bạn luôn có thư mục tuyệt đối, ngay cả khi tập lệnh được chạy bởi các lệnh như ./script.sh, mà không có cd / pwd, thường chỉ cung cấp .. Vô dụng nếu tập lệnh thực hiện cd sau này.


8

Như Adam Liss lưu ý realpathkhông đi kèm với mọi phân phối. Đó là một sự xấu hổ, bởi vì đó là giải pháp tốt nhất. Mã nguồn được cung cấp là tuyệt vời, và tôi có thể sẽ bắt đầu sử dụng nó ngay bây giờ. Đây là những gì tôi đã sử dụng cho đến bây giờ, mà tôi chia sẻ ở đây chỉ để hoàn thiện:

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

Nếu bạn muốn nó giải quyết các liên kết tượng trưng, ​​chỉ cần thay thế pwdbằng pwd -P.


Một gotcha với pwd -Ptùy chọn cho trường hợp này ... Hãy xem xét điều gì sẽ xảy ra nếu $(basename "$1")là một liên kết tượng trưng đến một tệp trong thư mục khác. Các pwd -Pchỉ giải quyết liên kết tượng trưng trong phần thư mục của con đường, nhưng không phải là phần basename.
toxalot

7

Giải pháp gần đây của tôi là:

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

Dựa trên câu trả lời của Tim Whitcomb.


Tôi nghi ngờ điều này không thành công nếu đối số không phải là một thư mục. Giả sử tôi muốn biết / usr / bin / java dẫn đến đâu?
Edward Falk

1
Nếu bạn biết đó là một tập tin, bạn có thể pushd $(dirname /usr/bin/java)thử.
schmunk 17/03/2016

5

Không chính xác là một câu trả lời nhưng có lẽ là một câu hỏi tiếp theo (câu hỏi ban đầu không rõ ràng):

readlinklà tốt nếu bạn thực sự muốn theo symlink. Nhưng đó cũng là một trường hợp sử dụng cho chỉ bình thường hóa ./..///trình tự, có thể được thực hiện hoàn toàn cú pháp, mà không canonicalizing symlink. readlinkkhông tốt cho việc này, và cũng không realpath.

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

làm việc cho các đường dẫn hiện có, nhưng phá vỡ cho những người khác.

Một sedkịch bản có vẻ như là một lựa chọn tốt, ngoại trừ việc bạn không thể lặp đi lặp lại thay thế chuỗi ( /foo/bar/baz/../..-> /foo/bar/..-> /foo) mà không sử dụng một cái gì đó giống như Perl, mà không phải là an toàn để giả định trên tất cả các hệ thống, hoặc sử dụng một số vòng lặp xấu xí để so sánh sản lượng sedđể đầu vào của nó.

FWIW, một lớp lót sử dụng 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

realpathcó một -stùy chọn để không giải quyết được liên kết tượng trưng và chỉ có quyết tâm tham chiếu đến /./, /../và loại bỏ thêm /ký tự. Khi kết hợp với -mtùy chọn, realpathchỉ hoạt động trên tên tệp và không chạm vào bất kỳ tệp thực tế nào. Nó có vẻ như là giải pháp hoàn hảo. Nhưng than ôi, realpathvẫn còn thiếu trên nhiều hệ thống.
toxalot

Loại bỏ ..các thành phần không thể được thực hiện theo cú pháp khi có liên kết tượng trưng. /one/two/../threekhông giống như /one/threenếu twolà một liên kết tượng trưng đến /foo/bar.
jrw32982 hỗ trợ Monica

@ jrw32982 có như tôi đã nói trong phản hồi của mình, đây là trường hợp sử dụng khi không cần hoặc không cần chuẩn hóa liên kết tượng trưng .
Jesse Glick

@JlieGlick không chỉ là trường hợp bạn có muốn hợp thức hóa các liên kết tượng trưng hay không. Thuật toán của bạn thực sự tạo ra câu trả lời sai. Để câu trả lời của bạn là chính xác, bạn sẽ phải biết một tiên nghiệm rằng không có liên kết tượng trưng nào (hoặc chúng chỉ có một hình thức nhất định). Câu trả lời của bạn nói rằng bạn không muốn hợp thức hóa chúng, không phải là không có liên kết tượng trưng liên quan đến đường dẫn.
jrw32982 hỗ trợ Monica

Có những trường hợp sử dụng trong đó chuẩn hóa phải được thực hiện mà không giả sử bất kỳ cấu trúc thư mục cố định, hiện có. Chuẩn hóa URI là tương tự. Trong những trường hợp này, một hạn chế cố hữu là kết quả sẽ không chính xác nếu có các liên kết gần một thư mục nơi kết quả được áp dụng sau này.
Jesse Glick

5

Tôi đến bữa tiệc muộn, nhưng đây là giải pháp tôi đã tạo ra sau khi đọc một loạt các chủ đề như thế này:

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

Điều này sẽ giải quyết đường dẫn tuyệt đối của $ 1, chơi tốt với ~, giữ các liên kết tượng trưng trong đường dẫn của chúng và nó sẽ không gây rối với ngăn xếp thư mục của bạn. Nó trả về đường dẫn đầy đủ hoặc không có gì nếu nó không tồn tại. Nó hy vọng $ 1 sẽ là một thư mục và có thể sẽ thất bại nếu không, nhưng đó là một kiểm tra dễ dàng để tự làm.


4

Nói nhiều, và trả lời hơi muộn. Tôi cần phải viết một cái kể từ khi tôi bị mắc kẹt trên RHEL4 / 5 cũ. Tôi xử lý các liên kết tuyệt đối và tương đối, và đơn giản hóa các mục //, /./ và somedir /../.

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

Hãy dùng thử sản phẩm thư viện Bash mới của chúng tôi realpath-lib mà chúng tôi đã đặt trên GitHub để sử dụng miễn phí và không bị cản trở. Nó được ghi chép lại kỹ lưỡng và làm cho một công cụ học tập tuyệt vời.

Nó giải quyết các đường dẫn cục bộ, tương đối và tuyệt đối và không có bất kỳ phụ thuộc nào ngoại trừ Bash 4+; vì vậy nó nên hoạt động ở bất cứ đâu Đó là miễn phí, sạch sẽ, đơn giản và hướng dẫn.

Bạn có thể làm:

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

Chức năng này là cốt lõi của thư viện:

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

}

Nó cũng chứa các hàm để get_dirname, get_filename, get_ thân tên và validate_path. Hãy thử nó trên các nền tảng và giúp cải thiện nó.


2

Vấn đề với realpathlà nó không có sẵn trên BSD (hoặc OSX cho vấn đề đó). Đây là một công thức đơn giản được trích xuất từ một bài báo khá cũ (2009) từ Tạp chí Linux , khá dễ mang theo:

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

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

Lưu ý biến thể này cũng không yêu cầu đường dẫn để tồn tại.


Điều này không giải quyết các liên kết tượng trưng tuy nhiên. Realpath xử lý các đường dẫn bắt đầu từ thư mục gốc và theo các liên kết tượng trưng khi tiến trình. Tất cả điều này là thu gọn tài liệu tham khảo cha mẹ.
Martijn Pieters

2

Dựa trên câu trả lời của @ Andre, tôi có thể có một phiên bản tốt hơn một chút, trong trường hợp ai đó đang theo giải pháp dựa trên thao tác chuỗi hoàn toàn không có vòng lặp. Nó cũng hữu ích cho những người không muốn hủy bỏ bất kỳ liên kết tượng trưng nào, đó là nhược điểm của việc sử dụng realpathhoặc readlink -f.

Nó hoạt động trên các phiên bản bash 3.2.25 trở lên.

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

Đây là một ý tưởng hay, nhưng tôi đã lãng phí 20 phút để cố gắng làm cho nó hoạt động trên nhiều phiên bản khác nhau của bash. Nó chỉ ra rằng tùy chọn shell extglob cần phải được bật để nó hoạt động, và nó không theo mặc định. Khi nói đến chức năng bash, điều quan trọng là chỉ định cả phiên bản bắt buộc và tùy chọn không mặc định vì các chi tiết này có thể khác nhau giữa các HĐH. Ví dụ, phiên bản gần đây của Mac OSX (Yosemite) chỉ đi kèm với phiên bản lỗi thời của bash (3.2).
drwatson

Xin lỗi @ricovox; Bây giờ tôi đã cập nhật những cái đó. Tôi rất muốn biết phiên bản chính xác của Bash bạn có ở đó. Công thức trên (đã cập nhật) hoạt động trên CentOS 5.8 đi kèm với bash 3.2.25
Ϲ Ϲδεεδεδ

Xin lỗi vì sự nhầm lẫn. Mã DID này hoạt động trên phiên bản Mac OSX bash của tôi (3.2.57) sau khi tôi bật tính năng extglob. Lưu ý của tôi về các phiên bản bash là một tổng quát hơn (thực sự áp dụng nhiều hơn cho một câu trả lời khác ở đây liên quan đến regex trong bash).
drwatson

2
Tôi đánh giá cao câu trả lời của bạn mặc dù. Tôi đã sử dụng nó như là một cơ sở cho riêng tôi. Tình cờ tôi nhận thấy một số trường hợp bạn thất bại: (1) Đường dẫn tương đối hello/../world(2) Dấu chấm trong tên tệp /hello/..world(3) Dấu chấm sau dấu gạch chéo kép /hello//../world(4) Dấu chấm trước hoặc sau dấu gạch chéo kép /hello//./worldhoặc /hello/.//world (5) Dấu ngoặc sau dòng: /hello/./../world/ (6) Cha mẹ sau Parent : /hello/../../world, v.v. - Một số trong số này có thể được sửa bằng cách sử dụng vòng lặp để chỉnh sửa cho đến khi đường dẫn dừng thay đổi. (Cũng xóa dir/../, /dir/..nhưng không xóa dir/..từ cuối.)
drwatsoncode

0

Dựa trên đoạn trích trăn tuyệt vời của loveborg, tôi đã viết điều này:

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

Điều này hoạt động ngay cả khi tập tin không tồn tại. Nó không yêu cầu thư mục chứa tập tin tồn tại.


GNU Realpath không yêu cầu phần tử cuối cùng trong đường dẫn tồn tại, trừ khi bạn sử dụngrealpath -e
Martijn Pieters

0

Tôi cần một giải pháp sẽ làm cả ba:

  • Làm việc trên một cổ phiếu Mac. realpathreadlink -flà addons
  • Giải quyết các liên kết tượng trưng
  • Có lỗi xử lý

Không có câu trả lời nào có cả # 1 và # 2. Tôi đã thêm số 3 để cứu người khác.

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

Dưới đây là một trường hợp thử nghiệm ngắn với một số không gian xoắn trong các đường dẫn để thực hiện đầy đủ việc trích dẫn

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

Tôi biết đây là một câu hỏi cổ xưa. Tôi vẫn đang cung cấp một sự thay thế. Gần đây tôi đã gặp vấn đề tương tự và thấy không có lệnh di động và hiện có để làm điều đó. Vì vậy, tôi đã viết kịch bản shell sau đây bao gồm một hàm có thể thực hiện thủ thuật.

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

Tôi đã thực hiện một chức năng chỉ có sẵn để xử lý việc này với trọng tâm là hiệu suất cao nhất có thể (cho vui). Nó không giải quyết các liên kết tượng trưng, ​​vì vậy về cơ bản nó giống như 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;
}

Lưu ý: Hàm này không xử lý các đường dẫn bắt đầu bằng //, vì chính xác hai dấu gạch chéo ở đầu một đường dẫn là hành vi được xác định theo thực hiện. Tuy nhiên, nó xử lý /,/// và vân vân tốt.

Chức năng này dường như xử lý tất cả các trường hợp cạnh đúng, nhưng vẫn có thể có một số trường hợp mà tôi chưa xử lý.

Lưu ý về hiệu suất: khi được gọi với hàng ngàn đối số, abspathchạy chậm hơn khoảng 10 lần so với realpath -sm; khi được gọi với một đối số duy nhất, abspathchạy nhanh hơn 110 lần so với realpath -smtrên máy của tôi, chủ yếu là do không cần phải thực thi chương trình mới mỗi lần.


-1

Tôi phát hiện ra rằng bạn có thể sử dụng statlệnh để giải quyết các đường dẫn.

Vì vậy, đối với một thư mục như "~ / Documents":

Bạn có thể chạy cái này:

stat -f %N ~/Documents

Để có được đường dẫn đầy đủ:

/Users/me/Documents

Đối với liên kết tượng trưng, ​​bạn có thể sử dụng tùy chọn định dạng% Y:

stat -f %Y example_symlink

Mà có thể trả về một kết quả như:

/usr/local/sbin/example_symlink

Các tùy chọn định dạng có thể khác nhau trên các phiên bản khác của * NIX nhưng các tùy chọn này hoạt động với tôi trên OSX.


1
các stat -f %N ~/Documentsdòng là một cá trích đỏ ... shell của bạn được thay thế ~/Documentsvới /Users/me/Documents, và statchỉ được in đối số của nó đúng nguyên văn.
danwyand

-4

Một giải pháp đơn giản sử dụng node.js:

#!/usr/bin/env node
process.stdout.write(require('path').resolve(process.argv[2]));
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.