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

问:有没有简单的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)
519191 次浏览

使用# EYZ1

$ realpath example.txt
/home/username/example.txt
#! /bin/sh
echo "$(cd "$(dirname -- "$1")" >/dev/null; pwd -P)/$(basename -- "$1")"

尝试readlink,它将解析符号链接:

readlink -e /foo/bar/baz

如果你没有readlink或realpath实用工具,那么你可以使用下面的函数,它可以在bash和zsh中工作(不确定其他的)。

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

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

不幸的是,abspath ./../somefile没有去掉圆点。

find命令可能会有所帮助

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

列出名称与模式匹配的当前目录中或下面的所有文件。你可以简化它,如果你只会得到几个结果(例如,目录接近树的底部包含几个文件),只是

find $PWD

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

这里有一个只使用zsh的函数,我喜欢它的紧凑性。它使用了“A”扩展修饰符—参见zshexpn(1)。

realpath() { for f in "$@"; do echo ${f}(:A); done }
$ readlink -m FILE
/path/to/FILE

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

对于一个文件来说,通常没有 absolute path这样的东西(这句话意味着通常可能有不止一个,因此使用定冠词是不合适的)。absolute path是从根“/”开始的任何路径,并且与工作目录无关地指定文件。(参见示例维基百科)。

a# EYZ0是要从另一个目录开始解释的路径。如果它是由应用程序操作的relative path,则它可能是工作目录 (虽然不一定)。当它位于目录中的符号链接中时,它通常是相对于该目录的(尽管用户可能有其他用途)

因此,绝对路径只是相对于根目录的路径。

路径(绝对或相对)可以包含也可以不包含符号链接。如果没有,那么它在某种程度上也不受链接结构变化的影响,但这并不一定是必需的,甚至也不是可取的。有些人将canonical path(或canonical file nameresolved path)称为absolute path,其中所有的符号链接都已被解析,即已被链接到的路径所取代。命令realpathreadlink都寻找规范路径,但只有realpath有一个选项可以获得绝对路径,而不需要解析符号链接(还有其他几个选项可以获得各种路径,绝对路径或相对于某个目录的路径)。

这里有几点需要说明:

  1. 符号链接只能在它们应该被解析的情况下被解析 链接已经创建,但显然情况并非总是如此。命令realpathreadlink有相应的选项
  2. 路径上的目录稍后可以变成符号链接,这意味着该路径不再是canonical。因此,这个概念是时间(或环境)相关的。
  3. 即使在理想情况下,当所有符号链接都可以被解析时, 对于两个文件来说,一个文件中仍然可能有多个canonical path 原因:
    • 包含文件的分区可能同时被挂载在几个挂载点上(ro)。
    • 可能存在指向文件的硬链接,这意味着该文件本质上存在于几个不同的目录中。
    • 李< / ul > < / >

因此,即使使用更严格的canonical path定义,一个文件也可能有几个规范路径。这也意味着限定符canonical在某种程度上是不够的,因为它通常暗示了唯一性的概念。

在回答Bash:获取给定相对的绝对路径上的另一个类似问题时,本文扩展了对该主题的简短讨论

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

忘记readlinkrealpath,它们可能安装也可能不安装在您的系统上。

在上面的夹竹桃的回答上展开,它表示为一个函数:

#!/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发行版的默认安装中通常不存在。

如果dir不存在呢?

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

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

现在如果父dirs不存在,它将返回一个空字符串。

你如何处理拖尾?'或'。输入?

在这种情况下,它确实给出了一条绝对路径,但不是最小路径。它看起来像:

/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
}

对于目录dirname将绊倒../并返回./

nolan6000的功能可以修改来修复这个问题:

get_abs_filename() {
# $1 : relative filename
if [ -d "${1%/*}" ]; then
echo "$(cd ${1%/*}; pwd)/${1##*/}"
fi
}

这个相对路径到绝对路径转换器外壳函数

  • 不需要任何工具(只是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,但修复了文件情况。

我也理解最初的问题是关于一个现有的命令行实用程序。但由于这似乎是关于stackoverflow的问题,包括shell脚本,希望有最小的依赖关系,我把这个脚本解决方案放在这里,所以我可以稍后找到它:)

#! /bin/bash


file="$@"
realpath "$file" 2>/dev/null || eval realpath $(echo $file | sed 's/ /\\ /g')

这弥补了realpath的缺点,将它存储在shell脚本fullpath中。你现在可以调用:

$ cd && touch a\ a && rm A 2>/dev/null
$ fullpath "a a"
/home/user/a a
$ fullpath ~/a\ a
/home/user/a a
$ fullpath A
A: No such file or directory.

嘿,伙计们,我知道这是一个旧帖子,但我只是发布这个帖子供其他像我一样访问这个帖子的人参考。如果我正确理解了问题,我认为是locate $filename命令。它只显示所提供文件的绝对路径,但前提是该文件存在。

这并不是问题的答案,但对于那些编写脚本的人来说:

echo `cd "$1" 2>/dev/null&&pwd||(cd "$(dirname "$1")";pwd|sed "s|/*\$|/${1##*/}|")`

它正确地处理/ .. / etc。我似乎也在OSX工作

我已经在我的系统上放置了以下脚本&当我想快速获取当前目录下文件的完整路径时,我将其称为bash别名:

#!/bin/bash
/usr/bin/find "$PWD" -maxdepth 1 -mindepth 1 -name "$1"

我不知道为什么,但是,在OS X上,当被脚本“$PWD”调用时,扩展到绝对路径。当在命令行上调用find命令时,它不会执行。但这是我想要的…享受。

Ruby中获取绝对路径的另一种方法:

# EYZ0

不带参数(当前文件夹)和相对和绝对文件或文件夹路径作为参数。

dogbane 回答和描述将要发生的事情:

#! /bin/sh
echo "$(cd "$(dirname "$1")"; pwd)/$(basename "$1")"

解释:

  1. 这个脚本获取相对路径作为参数"$1"
  2. 然后我们得到该路径的目录名部分(您可以将dir或file传递给这个脚本):dirname "$1"
  3. 然后我们cd "$(dirname "$1")到这个相对目录,并通过运行pwd shell命令获得它的绝对路径
  4. 之后,我们将basename附加到绝对路径:$(basename "$1")
  5. 作为最后一步,我们echo

如果你只想使用内置程序,最简单的方法可能是:

find `pwd` -name fileName

只需多输入两个字,就可以在所有unix系统和OSX上工作。

用Homebrew回答

realpath是最好的答案,但如果你没有安装它,你必须首先运行brew install coreutils,它将安装coreutils和许多很棒的函数。编写一个自定义函数并导出它是太多的工作和错误的风险,对于这样的东西,这里有两行:

$ brew install coreutils
$ realpath your-file-name.json

Alexander Klimetschek的答案是可以的,如果您的脚本可能坚持使用bash或bash兼容的shell。它不能与只符合POSIX的shell一起工作。

此外,当最终文件是根目录下的文件时,输出将是//file,这在技术上并不是不正确的(系统将两个/视为单个),但它看起来很奇怪。

下面是一个适用于所有符合POSIX标准的shell的版本,它所使用的所有外部工具也是POSIX标准所要求的,并且它显式地处理根文件的情况:

#!/bin/sh


abspath ( ) {
if [ ! -e "$1" ]; then
return 1
fi


file=""
dir="$1"
if [ ! -d "$dir" ]; then
file=$(basename "$dir")
dir=$(dirname "$dir")
fi


case "$dir" in
/*) ;;
*) dir="$(pwd)/$dir"
esac
result=$(cd "$dir" && pwd)


if [ -n "$file" ]; then
case "$result" in
*/) ;;
*) result="$result/"
esac
result="$result$file"
fi


printf "%s\n" "$result"
}


abspath "$1"

将其放入一个文件并使其可执行,您就有了一个CLI工具来快速获取文件和目录的绝对路径。或者只是复制该函数并在您自己的POSIX符合脚本中使用它。它将相对路径转换为绝对路径并返回绝对路径。

有趣的修改:

如果将行result=$(cd "$dir" && pwd)替换为result=$(cd "$dir" && pwd -P),那么最终文件路径中的所有符号链接也将被解析。

如果你对第一个修改不感兴趣,你可以通过提前返回来优化绝对情况:

abspath ( ) {
if [ ! -e "$1" ]; then
return 1
fi


case "$1" in
/*)
printf "%s\n" "$1"
return 0
esac


file=""
dir="$1"
if [ ! -d "$dir" ]; then
file=$(basename "$dir")
dir=$(dirname "$dir")
fi


result=$(cd "$dir" && pwd)


if [ -n "$file" ]; then
case "$result" in
*/) ;;
*) result="$result/"
esac
result="$result$file"
fi


printf "%s\n" "$result"
}

既然问题会出现:为什么printf而不是echo?

echo主要用于打印消息供用户进行标准输出。脚本编写者所依赖的许多echo行为实际上是未指定的。甚至有名的-n和标签的使用\t都不是标准化的。POSIX标准说:

要写入标准输出的字符串。如果第一个操作数是-n,或者任何一个操作数都包含一个字符,则结果是实现定义的
- # EYZ0 < / p >

因此,每当您想要向标准输出写入一些内容,而不是为了将消息打印给用户时,建议使用printf,因为printf的行为是精确定义的。我的函数使用stdout传递一个结果,这不是给用户的消息,因此只使用printf可以保证完美的可移植性。

我用单线

(cd ${FILENAME%/*}; pwd)

但是,这只能在$FILENAME具有实际存在的任何类型的前路径(相对或绝对)时使用。如果根本没有引导路径,那么答案就是$PWD。如果前导路径不存在,则答案可能是不确定的,否则,如果路径是绝对路径,则答案就是${FILENAME%/*}

把这些放在一起,我建议使用下面的函数

function abspath() {
# argument 1: file pathname (relative or absolute)
# returns: file pathname (absolute)
if [ "$1" == "${1##*/}" ]; then # no path at all
echo "$PWD"
elif [ "${1:0:1}" == "/" -a "${1/../}" == "$1" ]; then # strictly absolute path
echo "${1%/*}"
else # relative path (may fail if a needed folder is non-existent)
echo "$(cd ${1%/*}; pwd)"
fi
}

还要注意,这只适用于bash和兼容的shell。我不相信替换在简单的shell sh中起作用。

在某些情况下,这个问题最上面的答案可能具有误导性。假设你想要找到的文件的绝对路径在$PATH变量中:

# node is in $PATH variable
type -P node
# /home/user/.asdf/shims/node
cd /tmp
touch node  # But because there is a file with the same name inside the current dir check out what happens below
readlink -e node
# /tmp/node
readlink -m node
# /tmp/node
readlink -f node
# /tmp/node
echo "$(cd "$(dirname "node")"; pwd -P)/$(basename "node")"
# /tmp/node
realpath node
# /tmp/node
realpath -e node
# /tmp/node


# Now let's say that for some reason node does not exist in current directory
rm node
readlink -e node
# <nothing printed>
readlink -m node
# /tmp/node         # Note: /tmp/node does not exist, but is printed
readlink -f node
# /tmp/node         # Note: /tmp/node does not exist, but is printed
echo "$(cd "$(dirname "node")"; pwd -P)/$(basename "node")"
# /tmp/node         # Note: /tmp/node does not exist, but is printed
realpath node
# /tmp/node         # Note: /tmp/node does not exist, but is printed
realpath -e node
# realpath: node: No such file or directory

基于以上,我可以得出结论:realpath -ereadlink -e可以用来找到一个文件的绝对路径,我们期望存在于当前目录而不会受到$PATH变量的影响中。唯一的区别是realpath输出到stderr,但如果没有找到文件,两者都会返回错误代码:

cd /tmp
rm node
realpath -e node ; echo $?
# realpath: node: No such file or directory
# 1
readlink -e node ; echo $?
# 1

现在,如果您需要存在于$PATH中的文件的绝对路径a,那么下面的命令将是合适的,这与当前目录中是否存在同名文件无关。

type -P example.txt
# /path/to/example.txt


# Or if you want to follow links
readlink -e $(type -P example.txt)
# /originalpath/to/example.txt


# If the file you are looking for is an executable (and wrap again through `readlink -e` for following links )
which executablefile
# /opt/bin/executablefile


如果没有,回退到$PATH,示例:

cd /tmp
touch node
echo $(readlink -e node || type -P node)
# /tmp/node
rm node
echo $(readlink -e node || type -P node)
# /home/user/.asdf/shims/node