如何通过Shell(BASH/ZSH/SH)获取文件的绝对路径?

704

问题:是否有一个简单的sh/bash/zsh/fish/...命令,可以打印出我输入的任何文件的绝对路径?

使用场景:我在目录/a/b中,我想要在命令行中打印文件c的完整路径,以便我可以轻松地将其粘贴到另一个程序中:/a/b/c。这很简单,但是编写一个小程序来完成此任务可能会为我节省5秒钟左右的时间,当处理长路径时这些时间最终会相加。所以让我感到惊讶的是,我找不到一个标准的实用程序来完成这个任务 - 确实没有吗?

这里是一个示例实现,abspath.py:

#!/usr/bin/python
# Author: Diggory Hardy <diggory.hardy@gmail.com>
# Licence: public domain
# Purpose: print the absolute path of all input paths

import sys
import os.path
if len(sys.argv)>1:
    for i in range(1,len(sys.argv)):
        print os.path.abspath( sys.argv[i] )
    sys.exit(0)
else:
    print >> sys.stderr, "Usage: ",sys.argv[0]," PATH."
    sys.exit(1)

我认为@DennisWilliamson的回答(使用-m选项)更优秀,因为它通常更具可移植性,并且可以处理不存在的文件。 - TTT
或者Flimm的答案也很好; 但是Bannier的回答最符合我的原始问题。 - dhardy
3
苹果电脑用户请查看此答案。链接 - Ian
可能是重复的问题Bash: 获取相对路径的绝对路径 - user
23个回答

979

使用realpath

$ realpath example.txt
/home/username/example.txt

187
+1,非常好的程序。但请注意它是一个外部命令(不是shell内置),并且可能不会在每个系统上默认存在,也就是说你可能需要安装它。在Debian中,它位于一个同名的单独包中。 - Roman Cheplyaka
5
我在路径中添加了一个realpath,其内容为:https://github.com/westonruter/misc-cli-tools/blob/master/realpath。 - Weston Ruter
17
尝试安装coreutils,该二进制文件名为grealpath。 - Toni Penya-Alba
9
这个工具是在 GNU coreutils 8.15 中引入的(2012年),因此它相当新。 - marbu
7
@WalterTross,通过Homebrew的coreutils配方可获得该软件。 - Benjamin Bannier
显示剩余17条评论

377

尝试使用 readlink 命令来解析符号链接:

readlink -e /foo/bar/baz

64
我更倾向于使用 '-f' 而不是 '-e',这样我们可以看到一个不存在文件的绝对路径。 - hluk
5
这对我来说似乎是最简单易行的选择,同时也很便携。readlink 是 GNU coreutils 的一个端口,因此它将被安装在几乎所有 Linux 发行版上,除了一些嵌入式系统。 - catphive
20
在我看来,使用“-m”是最好的选择。 - Matt Joiner
2
hluk:-f选项也是在OpenBSD中使用的选项。 - mykhal
17
在OSX上没有这些选项,并且根据BSD的“readlink”手册:只会打印符号链接的目标。如果给定的参数不是一个符号链接,readlink将不会输出任何内容并退出错误,所以在OSX上readlink不能用于此目的。 - SnoringFrog
显示剩余8条评论

325
#! /bin/sh
echo "$(cd "$(dirname -- "$1")" >/dev/null; pwd -P)/$(basename -- "$1")"

9
别忘了引用所有的东西。如果你不明白为什么,可以在文件“a b”(a和b之间有两个空格,Stack Overflow会吃掉其中一个)上尝试一下你的程序。 - Roman Cheplyaka
42
到目前为止,唯一的符合POSIX标准的解决方案是不需要编写和编译可执行文件,因为readlink和realpath不符合POSIX标准。 - Ciro Santilli OurBigBook.com
34
我使用了function realpath { echo $(cd $(dirname $1); pwd)/$(basename $1); }在OS X上制作了一个realpath命令(目前被接受的答案)。谢谢! - James Bedford
1
@BrunoBronosky 这个答案满足了问题的要求,有时非常有用。看起来你对这个问题很感兴趣,它还包括扩展符号链接(也称为规范化):http://unix.stackexchange.com/questions/24293/converting-relative-path-to-absolute-path - Robin Green
1
你说得对!需要给pwd命令传递一个“-P”参数才能解析符号链接。我会编辑答案的。谢谢! - James Bedford
显示剩余11条评论

123

忘记关于系统中可能安装了或未安装了的 readlinkrealpath

dogbane的回答上进行扩展,这里将其表述为一个函数:

#!/bin/bash
get_abs_filename() {
  # $1 : relative filename
  echo "$(cd "$(dirname "$1")" && pwd)/$(basename "$1")"
}

然后您可以像这样使用它:

myabsfile=$(get_abs_filename "../../foo/bar/file.txt")

如何以及为什么它起作用?

该解决方案利用了Bash内置的pwd命令在没有参数的情况下调用时将打印当前目录的绝对路径的事实。

我为什么喜欢这个解决方案?

它是可移植的,并且不需要readlinkrealpath,这些在给定的Linux/Unix发行版的默认安装中经常不存在。

如果目录不存在怎么办?

如上所述,如果给定的目录路径不存在,该函数将失败并在stderr上打印。这可能不是您想要的。您可以扩展该功能以处理该情况:

#!/bin/bash
get_abs_filename() {
  # $1 : relative filename
  if [ -d "$(dirname "$1")" ]; then
    echo "$(cd "$(dirname "$1")" && pwd)/$(basename "$1")"
  fi
}

如果其中一个父目录不存在,现在它将返回一个空字符串。

如何处理输入中的尾随'..'或'.'?

嗯,在这种情况下它会给出一个绝对路径,但不是最小的路径。它看起来像:

/Users/bob/Documents/..

如果你想解决 "..",你需要让脚本像这样:

get_abs_filename() {
  # $1 : relative filename
  filename=$1
  parentdir=$(dirname "${filename}")

  if [ -d "${filename}" ]; then
      echo "$(cd "${filename}" && pwd)"
  elif [ -d "${parentdir}" ]; then
    echo "$(cd "${parentdir}" && pwd)/$(basename "${filename}")"
  fi
}

2
@dhardy。不确定我是否理解了您的评论。这里提出的解决方案中有什么是排除在交互环境之外的?...顺便说一句,就像本页面上呈现的所有其他替代答案一样。 - peterh
1
既不是realpath也不是realink是“标准Unix实用程序”,所以如果这就是你想要的,那么你选择了错误的答案。 - peterh
6
我想补充一下,realpath 是来自于 coreutils 软件包的一部分,该软件包还包含诸如 whotouchcat 之类的工具。这是一个相当标准的 GNU 工具集,因此可以确定几乎所有基于 Linux 的机器都安装了它。话虽如此,你说得对:它有两个问题:i) 在非 GNU Unix 系统(如 OpenBSD)的默认安装中可能会遗漏它;ii) 这个工具是在 8.15 版本中引入的(因此它很新,来自于2012年),所以在长期稳定系统(如 RHEL 6)上可能会遗漏它。 - marbu
1
嗯,看起来 Continuum Analytics (Anaconda Python 分发版的制造商)喜欢这个答案。他们在他们的"activate"脚本中实现了它(并链接回到了这里)。这个脚本被用于激活由conda工具创建的虚拟环境。所以...做得好! - wjv
4
在Ubuntu 14.04或Debian Wheezy上不存在realpath命令。随着时间的推移,它可能会变得更加常见,但现在肯定还不是。另外请注意,原帖中没有提到linux或GNU。Bash比它们更广泛地使用。 - mc0e
显示剩余9条评论

81
$ readlink -m FILE
/path/to/FILE

这比 readlink -e FILErealpath 更好,因为它即使文件不存在也能正常工作。

很好。这也适用于存在但不是符号链接的文件/目录。 - quietmint
我在OS X 10.9(Mavericks)上有可用的readlink,因此这绝对是最好的答案。 - Kenneth Hoste
6
“-m”选项在Mavericks系统上不可用。 - iconoclast
4
这绝对不是在默认的OSX安装中。 - Fran Marzoa
可以在Linux(CentOS)上工作,谢谢,因为它没有realpath。 - jimh

78
这个相对路径转绝对路径的转换器是一个shell函数,具有以下特点:
  • 不需要任何工具(只需使用cdpwd
  • 适用于目录和文件
  • 处理...
  • 处理目录或文件名中的空格
  • 要求文件或目录存在
  • 如果给定路径上不存在任何内容,则不返回任何结果
  • 可以处理输入的绝对路径(基本上直接通过)

代码:

function abspath() {
    # generate absolute path from relative path
    # $1     : relative filename
    # return : absolute path
    if [ -d "$1" ]; then
        # dir
        (cd "$1"; pwd)
    elif [ -f "$1" ]; then
        # file
        if [[ $1 = /* ]]; then
            echo "$1"
        elif [[ $1 == */* ]]; then
            echo "$(cd "${1%/*}"; pwd)/${1##*/}"
        else
            echo "$(pwd)/$1"
        fi
    fi
}

示例:

# assume inside /parent/cur
abspath file.txt        => /parent/cur/file.txt
abspath .               => /parent/cur
abspath ..              => /parent
abspath ../dir/file.txt => /parent/dir/file.txt
abspath ../dir/../dir   => /parent/dir          # anything cd can handle
abspath doesnotexist    =>                      # empty result if file/dir does not exist
abspath /file.txt       => /file.txt            # handle absolute path input

注意:这是基于 from nolan6000bsingh 的答案,但修复了文件大小写的问题。
我也知道原始问题是关于现有命令行实用程序的。但由于这似乎是stack overflow上包括希望具有最小依赖项的shell脚本在内的问题,因此我将此脚本解决方案放在这里,以便稍后查找 :)

@george 谢谢你指出,我已经修复了。 - Alexander Klimetschek
1
我查看了数十个不成熟的解决方案,这绝对是最简洁和准确的仅使用bash的解决方案。你可以通过将echo "$(cd "$1"; pwd)"替换为(cd "$1"; pwd)来进一步精简它。这里不需要echo。 - Six
@六 确实,已更新。括号 ( ... ) 仍然需要,因此 cd 发生在子shell中,我们不希望更改任何调用 abspath 的工作目录。 - Alexander Klimetschek
这是一个经过深思熟虑的答案,但它不适用于符号链接到文件,只适用于目录。因此,如果您有一个查找与脚本相同目录下配置文件的脚本,那么您无法将脚本符号链接到~/bin。对于受版本控制的工具而言,这是一个非常常见的做法。 - Bruno Bronosky
@BrunoBronosky,使用符号链接对我而言可以正常工作。你有一个无法正常工作的示例吗?请注意,它将返回绝对路径(如果基于您传递的符号链接),而不是“规范”路径,我认为为此单独编写工具/函数可能要难得多,在简单的 shell 函数中实现。 - Alexander Klimetschek
显示剩余7条评论

28

可以使用find命令来帮助查询。

find $PWD -name ex*
find $PWD -name example.log

列出当前目录或其子目录下名称匹配模式的所有文件。如果你只需要得到少量结果(例如,包含少量文件的树底部附近的目录),则可以简化处理。

find $PWD

我在Solaris 10上使用这个,该系统没有其他提到的实用工具。


1
我第一次阅读时没有理解这个评论;这里的关键点是,如果您给find命令一个绝对路径,那么它将输出绝对路径的结果。因此,使用$PWD是您所在位置的绝对路径,因此您会得到绝对路径的输出。 - simbo1905
4
如果你依赖于PWD,那么你可以简单地使用$PWD/$filename - jonny
这种方法可以通过使用-maxdepth 1来改进。此外,不要忘记引用 - 根据您的使用方式,这里可能存在潜在的安全问题。 - mc0e
1
@jonny:如果$filename已经是绝对路径,$PWD/$filename会失败。 - mc0e
如果文件名以“./filename.txt”(带有前导点和斜杠)的形式给出,则此操作将失败。 - Mark Stosberg
如果在$PWD下有多个匹配的文件名,则会失败。如果有许多目录,可能会非常缓慢。 - user48956

21

这是一个仅适用于zsh的函数,我喜欢它的紧凑性。它使用了“A”扩展修饰符 - 请参见zshexpn(1)。

realpath() { for f in "$@"; do echo ${f}(:A); done }

2
哇,这是一个优雅的解决方案!! - ShellFish

17

如果您没有readlink或realpath实用程序,那么您可以使用以下函数,在bash和zsh中运行(不确定其他情况)。

abspath () { case "$1" in /*)printf "%s\n" "$1";; *)printf "%s\n" "$PWD/$1";; esac; }

这也适用于不存在的文件(Python函数os.path.abspath也是如此)。

不幸的是,abspath ./../somefile并不能去掉点号。


1
看起来对我来说是可移植的。另一方面,例如在包含换行符的文件名上会出现错误。 - Roman Cheplyaka
1
为了进一步改进,请使用 printf "%s\n" "$1" 替换 echo "$1"(第二个 echo 同理)。echo 可能会解释其参数中的反斜杠。 - Roman Cheplyaka
你又对了!实际上,zsh中echo命令的行为与bash不同。 - hluk
1
严格来说,如果参数包含反斜杠,则 echo 的 POSIX 行为是未定义的。然而,在 XSI 扩展下定义了它(即 echo 应解释转义序列)。但是,无论是 bash 还是 zsh,都远离 POSIX 兼容性... - Roman Cheplyaka
抱歉,我不明白你的意思。我已经提供了一个带有更多功能的脚本作为答案,它不需要在shell脚本中使用。 - dhardy

10
通常情况下,并不存在一个绝对的文件路径(这意味着一般情况下可能不止一个,因此使用定冠词“the”是不合适的)。绝对路径是指任何从根目录“/”开始并独立于工作目录的可以明确指定文件的路径。(例如参见维基百科)。
相对路径是指需要从另一个目录开始解释的路径。如果是由应用程序处理的相对路径,则可能是工作目录(但不一定)。当它在目录中的符号链接中时,通常意味着它是相对于该目录的(尽管用户可能有其他用途)。
因此,绝对路径只是相对于根目录的路径。
路径(绝对或相对)可能包含符号链接,也可能不包含。如果不包含,则对链接结构的更改也会有些影响,但这并不一定是必要的或者理想的。有些人将所有符号链接都已解析的绝对路径称为规范路径(或规范文件名或已解析路径),即已将其替换为链接到的路径。命令realpath和readlink都会查找规范路径,但只有realpath有一个选项可以获取绝对路径而不必解析符号链接(以及其他几个选项以获取各种路径,无论是绝对还是相对于某个目录)。
这需要几点说明:
1.只有在符号链接指向的任何内容已经创建时才能解析符号链接,这显然并不总是情况。命令realpath和readlink有选项来解决这个问题。
2.路径上的目录后来可能会成为符号链接,这意味着该路径不再是规范的。因此,该概念是时间(或环境)依赖的。
3.即使在理想情况下,当所有符号链接都可以解析时,一个文件可能仍然存在多个规范路径,原因如下:
- 包含文件的分区可能同时被挂载到多个挂载点上。 - 文件可能有硬链接,这基本上意味着该文件存在于几个不同的目录中。
因此,即使使用更为严格的规范路径定义,一个文件也可能有多个规范路径。这也意味着限定词“规范”有些不充分,因为它通常暗示唯一性的概念。

以下内容是对另一个类似问题的回答进行的简短讨论:Bash: retrieve absolute path given relative

我的结论是,realpathreadlink 设计更好,而且更加灵活。 唯一不能被 realpath 覆盖的 readlink 用法是不带选项调用并返回符号链接的值。


1
(a) 这个问题有一个有效的答案,是在2年半前给出的。(b) 存在一个(单一的)绝对路径对应于一个相对路径(这正是我想要的),尽管可能不是指向一个文件,正如你所指出的。顺便说一句,关于realpathreadlink甚至关于符号链接的任何评论都超出了我所问的范围。 - dhardy
1
第二条评论:简短的回答往往比冗长的讲解更有用。Stack Overflow通常用于提出具体问题。 - dhardy
5
一个问题已经有了有效的答案,并不意味着它已经关闭。这些问题不是仅供个人使用的。实际上,对于获得比验证答案更多投票的回复,还有徽章可以颁发。而且随着新工具的出现,“最佳”答案也可能随时间变化。很多人通过网络搜索访问旧的答案。 - babou
1
2 - 你坚称一个相对路径存在(唯一)绝对路径与之对应。虽然我猜测你所说的“对应”是指通过一组给定的转换从原始路径派生出来的路径,但你的陈述仍然是不正确的。有一个唯一的“对应”的规范路径。规范一词精确地指出了这种相对于一组转换的唯一性。例如,/dev/cdrom是一个完美的绝对路径,尽管它实际上是一个链接到/dev/sr0的路径。两者都指向同一设备。我的原始答案指向了一个相关的网页文章。 - babou
3 - 由于有许多解决方案被建议作为答案,为读者提供比较评估答案很有用,以了解为什么以及如何。我碰巧同意您的答案,但这无关紧要。我花了一些时间(比您想象的要多得多)才能对该评估感到舒适,并且我试图使其他人免于这项工作。有时我会编辑自己的问题以解释为什么选择某个答案,以帮助未来的读者。实际上,关于realpathreadlink似乎存在一些争议。 - babou
显示剩余10条评论

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