tra-loi-cau-hoi-phat-trien-web.com

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

Tôi muốn chuyển đổi /foo/bar/.. thành /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.

164
Fabien

nếu bạn muốn kiểm tra 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 có ích. 

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 realpath không được Shell của bạn hỗ trợ, 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. 

161
Kent Fredric

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.

86
Tim Whitcomb

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);
}
53
Adam Liss

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 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/file có thể là đường dẫn tương đối hoặc tuyệt đối.

35
loevborg

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

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

readlink là 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 các liên kết tượng trưng nếu chúng tồn tại, nhưng nếu không thì bỏ qua các thư mục bị thiếu và dù sao cũng chỉ trả về đường dẫn:

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 thiết bị lỗi chuẩn.

13
Craig

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 độ Shell:

    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 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 tượng trưng đượ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" $ (dirname "$ 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 cho 

7
Gilbert

Như Adam Liss đã lưu ý realpath không được gói cùng với mọi bản 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ế pwd bằng pwd -P.

7
Jeet

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.

7
schmunk

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

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

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.

Tập lệnh sed dường như là một lựa chọn tốt, ngoại trừ việc bạn không thể lặp lại các chuỗi (/foo/bar/baz/../.. -> /foo/bar/.. -> /foo) mà không sử dụng một cái gì đó như Perl, không 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 để so sánh đầu ra của sed cho đầ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
5
Jesse Glick

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 ..

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`
4
alhernau

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 Nice với ~, giữ 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
apottere

Hãy 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 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 làm việc bất cứ nơi nào. Nó miễn phí, sạch sẽ, đơn giản và mang tính 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ó.

3
AsymLabs

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 đăng ký 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 realpath hoặ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
2
ϹοδεMεδιϲ

Vấn đề với realpath là nó không khả dụng 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 viết 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 ý rằng biến thể này cũng không không yêu cầu đường dẫn tồn tại.

1
André Anjos

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 -f là các addon
  • 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 bất kỳ việc cạo râu nào.

#!/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 đủ 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
David Blevins

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 "[email protected]"; 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
Edward Falk
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.

0
user240515

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
bestOfSong