将相对路径转换为绝对路径?

64
我不确定这些路径是否重复。给定相对路径,如何使用Shell脚本确定绝对路径?
示例:
relative path: /x/y/../../a/b/z/../c/d

absolute path: /a/b/c/d

15
你的“相对路径”实际上是个绝对路径,只是它没有采用规范形式。相对路径从不以斜杠“/”开头。或许可以搜索一下“规范路径(canonical path)”在 Stack Overflow 上的相关内容。 - Stephen P
可能是重复的问题:在bash/fish中打印文件的绝对路径的命令 - Trevor Boyd Smith
这个回答解决了你的问题吗?如何获取相对路径给出绝对路径 - Karl Knechtel
7个回答

59

在Unix中,我遇到的最可靠的方法是readlink -f

$ readlink -f /x/y/../../a/b/z/../c/d
/a/b/c/d

几点注意事项:

  1. 这也会副作用地解析所有符号链接。这可能有利也可能无利,但通常是有利的。
  2. readlink 如果引用不存在的目录,则会得到空结果。如果你想要支持不存在的路径,请使用 readlink -m。不幸的是,这个选项在 ~2005 年之前发布的 readlink 版本中不存在。

9
在Mac OS X上,readlink -f命令不可用。 - Lri
@jared-beck,你能展示一下readlink -f是否有同样的功能吗?在我的(OS X El Capitan)安装中,readlink -f首先基本上就是stat -f,并显示:-f使用指定格式显示信息。请参阅FORMATS部分以获取有效格式的描述。也许你安装了coreutils - Kreisquadratur
1
目前,OS X El Capitan具有不同的readlink命令,可以找到符号链接的真实路径,而不是将相对路径解析为绝对路径。因此,如果我们想要可移植性,看起来我们只能改变目录,然后返回。 - Spencer Williams

51

以下内容来自这个源

#!/bin/bash

# Assume parameter passed in is a relative path to a directory.
# For brevity, we won't do argument type or length checking.

ABS_PATH=`cd "$1"; pwd` # double quotes for paths that contain spaces etc...
echo "Absolute path: $ABS_PATH"

您也可以使用 Perl 一行代码,例如使用Cwd::abs_path来转换路径为绝对路径。


4
非常好。如果文件名也被给出,例如 /x/y/../../a/b/z/../c/d.txt => /a/b/c/d.txt - josh
1
第一个失败的是包含空格的路径(有趣的是,像 foo; echo BAR 这样的路径并没有以我预期的方式出现问题)。-1 是为了引起你的注意... - j_random_hacker
4
有趣的是,如果指定的路径不存在,Cwd::abs_path函数将失败。 - RJFalconer
@oberlies - 当然你可以自由地出于任何原因投票,但是对于在SO上标准的格式化风格上存在分歧并不被普遍认为是下投票的适当理由。如果你有异议,请随时在Meta上提问。 - DVK
@oberlies - 实际上,今天有人 - 大概是你 - 试图编辑该帖子两次,但显然这些编辑未被批准(因为该帖子没有被编辑)- 这意味着两个不是我本人的审阅者完全不同意您的观点,即您的偏好更好。 - DVK
显示剩余3条评论

21

使用

# Directory
relative_dir="folder/subfolder/"
absolute_dir="$( cd "$relative_dir" && pwd )"

# File
relative_file="folder/subfolder/file"
absolute_file="$( cd "${relative_file%/*}" && pwd )"/"${relative_file##*/}"
  • ${relative_file%/*}的结果与dirname "$relative_file"相同。
  • ${relative_file##*/}的结果与basename "$relative_file"相同。

注意:不会解析符号链接(即不规范化路径)=> 如果使用符号链接,则可能无法区分所有重复项。


使用realpath

命令realpath可以完成任务。另一种选择是使用readlink -e(或readlink -f)。但是realpath通常不会默认安装。如果您无法确定realpathreadlink是否存在,可以使用Perl进行替代(参见下文)。


使用

Steven Kramer提出了一个shell别名,如果您的系统中没有realpath

$ alias realpath="perl -MCwd -e 'print Cwd::realpath(\$ARGV[0]),qq<\n>'"
$ realpath path/folder/file
/home/user/absolute/path/folder/file

或者如果您更喜欢直接使用Perl:

$ perl -MCwd -e 'print Cwd::realpath($ARGV[0]),qq<\n>' path/folder/file
/home/user/absolute/path/folder/file

这个单行的Perl命令使用Cwd::realpath。实际上有三个Perl函数,它们接收单个参数并返回绝对路径名。以下详细信息来自文档Perl5 > 核心模块 > Cwd.

  • abs_path() 使用与 getcwd() 相同的算法。符号链接和相对路径组件(...)被解析为返回规范化的路径名,就像realpath一样。

use Cwd 'abs_path';
my $abs_path = abs_path($file);
  • realpath()abs_path() 的同义词。

    use Cwd 'realpath';
    my $abs_path = realpath($file);
    
  • fast_abs_path() 是一个更危险但潜在更快的 abs_path() 版本。

    use Cwd 'fast_abs_path';
    my $abs_path = fast_abs_path($file);
    
  • 这些函数仅在请求时导出=> 因此请使用Cwd以避免由arielf指出的"未定义子例程"错误。 如果您想导入所有这三个函数,可以使用单个use Cwd语句:

    use Cwd qw(abs_path realpath fast_abs_path);
    

    3
    对于安装了合适版本的Perl但没有realpath的发行版,可以使用以下一行命令:alias realpath="perl -MCwd -e 'print Cwd::realpath ($ARGV[0]), qq<\n>'"。该命令会为realpath创建一个别名,并调用Perl的Cwd模块来获取路径信息。 - Steven Kramer
    感谢您提出使用 perl 作为 realpath 替代方案的想法。我已将您的一行命令整合到答案中,以便其他 Stack Overflow 的读者也能受益。祝好!(我还给了您一些赞作为奖励) - oHo
    1
    @olibre MacOSX,Perl v5.18.2。脚本在此处:https://gist.github.com/thefosk/43296cb8bf74497c9d69 - Mark
    1
    @Mark,你的shell在别名定义中展开了$ARGV,因此Perl看到了Cwd::realpath([0]),它是一个数组引用。在别名中转义$ARGV。zsh没有这个问题。 - Steven Kramer
    1
    请查看我的编辑,它转义了别名并且适用于bash/zsh/ksh。 - Steven Kramer
    显示剩余6条评论

    20

    看一下 'realpath'。

    $ realpath
    
    usage: realpath [-q] path [...]
    
    $ realpath ../../../../../
    
    /data/home
    

    1

    由于多年来我遇到了这个问题很多次,而这一次我需要一个纯Bash便携版本,可以在OSX和Linux上使用,所以我决定自己写一个:

    最新版本在此处获取:

    https://github.com/keen99/shell-functions/tree/master/resolve_path

    但为了SO的缘故,这是当前版本(我觉得它经过了充分的测试...但我乐意听取反馈!)

    也许将其适用于普通的Bourne Shell(sh)并不难,但我没有尝试过...我太喜欢$FUNCNAME了。 :)

    #!/bin/bash
    
    resolve_path() {
        #I'm bash only, please!
        # usage:  resolve_path <a file or directory> 
        # follows symlinks and relative paths, returns a full real path
        #
        local owd="$PWD"
        #echo "$FUNCNAME for $1" >&2
        local opath="$1"
        local npath=""
        local obase=$(basename "$opath")
        local odir=$(dirname "$opath")
        if [[ -L "$opath" ]]
        then
        #it's a link.
        #file or directory, we want to cd into it's dir
            cd $odir
        #then extract where the link points.
            npath=$(readlink "$obase")
            #have to -L BEFORE we -f, because -f includes -L :(
            if [[ -L $npath ]]
             then
            #the link points to another symlink, so go follow that.
                resolve_path "$npath"
                #and finish out early, we're done.
                return $?
                #done
            elif [[ -f $npath ]]
            #the link points to a file.
             then
                #get the dir for the new file
                nbase=$(basename $npath)
                npath=$(dirname $npath)
                cd "$npath"
                ndir=$(pwd -P)
                retval=0
                #done
            elif [[ -d $npath ]]
             then
            #the link points to a directory.
                cd "$npath"
                ndir=$(pwd -P)
                retval=0
                #done
            else
                echo "$FUNCNAME: ERROR: unknown condition inside link!!" >&2
                echo "opath [[ $opath ]]" >&2
                echo "npath [[ $npath ]]" >&2
                return 1
            fi
        else
            if ! [[ -e "$opath" ]]
             then
                echo "$FUNCNAME: $opath: No such file or directory" >&2
                return 1
                #and break early
            elif [[ -d "$opath" ]]
             then 
                cd "$opath"
                ndir=$(pwd -P)
                retval=0
                #done
            elif [[ -f "$opath" ]]
             then
                cd $odir
                ndir=$(pwd -P)
                nbase=$(basename "$opath")
                retval=0
                #done
            else
                echo "$FUNCNAME: ERROR: unknown condition outside link!!" >&2
                echo "opath [[ $opath ]]" >&2
                return 1
            fi
        fi
        #now assemble our output
        echo -n "$ndir"
        if [[ "x${nbase:=}" != "x" ]]
         then
            echo "/$nbase"
        else 
            echo
        fi
        #now return to where we were
        cd "$owd"
        return $retval
    }
    

    这里有一个经典的例子,感谢 brew:

    %% ls -l `which mvn`
    lrwxr-xr-x  1 draistrick  502  29 Dec 17 10:50 /usr/local/bin/mvn@ -> ../Cellar/maven/3.2.3/bin/mvn
    

    使用此函数,它将返回“真实”路径:
    %% cat test.sh
    #!/bin/bash
    . resolve_path.inc
    echo
    echo "relative symlinked path:"
    which mvn
    echo
    echo "and the real path:"
    resolve_path `which mvn`
    
    
    %% test.sh
    
    relative symlinked path:
    /usr/local/bin/mvn
    
    and the real path:
    /usr/local/Cellar/maven/3.2.3/libexec/bin/mvn
    

    0

    我想使用realpath,但我的系统(macOS)上没有这个命令,所以我写了这个脚本:

    #!/bin/sh
    
    # NAME
    #   absolute_path.sh -- convert relative path into absolute path
    #
    # SYNOPSYS
    #   absolute_path.sh ../relative/path/to/file
    
    echo "$(cd $(dirname $1); pwd)/$(basename $1)"
    

    例子:

    ./absolute_path.sh ../styles/academy-of-management-review.csl 
    /Users/doej/GitHub/styles/academy-of-management-review.csl
    

    -1

    也许这可以帮助:

    $path = "~user/dir/../file" 
    $resolvedPath = glob($path); #   (To resolve paths with '~')
    # Since glob does not resolve relative path, we use abs_path 
    $absPath      = abs_path($path);
    

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接