问题:是否有一个简单的 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)
尝试 readlink
它将解析符号链接:
readlink -e /foo/bar/baz
-m
是最好的选择。
-f
选项也是在 OpenBSD 中使用的选项
readlink
手册页:only the target of the symbolic link is printed. If the given argument is not a symbolic link, readlink will print nothing and exit with an error
,因此 readlink 在 OSX 上无法用于此目的
#! /bin/sh
echo "$(cd "$(dirname -- "$1")" >/dev/null; pwd -P)/$(basename -- "$1")"
a b
上尝试您的程序(a 和 b 之间有两个空格,所以会吃掉其中一个)。
function realpath { echo $(cd $(dirname $1); pwd)/$(basename $1); }
使自己成为 realpath
命令(当前接受的答案)。谢谢!
LOG=$(echo $(cd $(dirname $0); pwd)/$(basename $0 .sh).log)
忘记您的系统上可能安装或未安装的 readlink
和 realpath
。
在这里扩展 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
命令在不带参数调用时将打印当前目录的绝对路径这一事实。
为什么我喜欢这个解决方案?
它是可移植的,不需要 readlink
或 realpath
,这在给定 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
}
realpath
来自 coreutils 包,其中还包含 who
、touch
或 cat
等工具。它是一套非常标准的 GNU 工具,因此您可以确定它几乎安装在任何基于 linux 的机器上。也就是说,您是对的:它有两个问题:i) 您可能会在默认安装时错过它非 GNU unix 系统(如 OpenBSD)ii)此工具是在 8.15 版中引入的(因此它是 2012 年以来的新功能),因此您会在长期稳定的系统(如 RHEL 6)上错过它。
$ readlink -m FILE
/path/to/FILE
这比 readlink -e FILE
或 realpath
好,因为即使文件不存在它也能工作。
-m
选项在 Mavericks 上不可用。
这个相对路径到绝对路径转换器shell函数
不需要实用程序(只需 cd 和 pwd)
适用于目录和文件
处理 .. 和 .
处理目录或文件名中的空格
要求该文件或目录存在
如果给定路径不存在任何内容,则不返回任何内容
处理绝对路径作为输入(本质上通过它们)
代码:
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 nolan6000 和 bsingh,但修正了文件大小写。
我也明白最初的问题是关于现有的命令行实用程序。但由于这似乎是关于 stackoverflow 的问题,包括希望具有最小依赖关系的 shell 脚本,所以我把这个脚本解决方案放在这里,这样我以后可以找到它:)
$ abspath 'a b c/file.txt'
那是因为 cd
的参数没有被引用。为了克服这个问题,不要将整个参数引用到 echo
(这些引号是否有原因?)仅引用路径参数。即用echo $(cd "${1%/*}"; pwd)/${1##*/}
替换echo "$(cd ${1%/*}; pwd)/${1##*/}"
。
echo "$(cd "$1"; pwd)"
替换为 (cd "$1"; pwd)
来进一步精简它。这里不需要回声。
( ... )
仍然需要,因此 cd
发生在子 shell 中,因为我们不想更改 abspath
的任何调用者的工作目录。
find
命令可能会有所帮助
find $PWD -name ex*
find $PWD -name example.log
列出当前目录中或下方名称与模式匹配的所有文件。如果您只会得到一些结果(例如,靠近树底部的目录包含少量文件),您可以简化它,只需
find $PWD
我在 Solaris 10 上使用它,它没有提到其他实用程序。
PWD
,您可以简单地使用 $PWD/$filename
。
这是一个 zsh-only 函数,我喜欢它的紧凑性。它使用“A”扩展修饰符——参见 zshexpn(1)。
realpath() { for f in "$@"; do echo ${f}(:A); done }
如果您没有 readlink 或 realpath 实用程序,则可以使用以下适用于 bash 和 zsh 的功能(不确定其余功能)。
abspath () { case "$1" in /*)printf "%s\n" "$1";; *)printf "%s\n" "$PWD/$1";; esac; }
这也适用于不存在的文件(python 函数 os.path.abspath
也是如此)。
不幸的是,abspath ./../somefile
并没有消除这些点。
echo "$1"
替换为 printf "%s\n" "$1"
(与第二个回显相同)。 echo
可以解释其参数中的反斜杠。
文件中通常没有 the absolute path
之类的东西(此陈述意味着通常可能有多个,因此使用定冠词 the不合适)。 absolute path
是从根“/”开始的任何路径,它指定一个独立于工作目录的无歧义文件。(参见示例 wikipedia)。
relative path
是从另一个目录开始解释的路径。如果它是被应用程序操作的 relative path
,它可能是工作目录(尽管不一定)。当它位于目录中的符号链接中时,它通常是相对于该目录的(尽管用户可能有其他用途)。
因此,绝对路径只是相对于根目录的路径。
路径(绝对或相对)可能包含也可能不包含符号链接。如果不是这样,它也有点不受链接结构变化的影响,但这不是必需的,甚至不是可取的。有些人将 canonical path
(或 canonical file name
或 resolved path
)称为 absolute path
,其中所有符号链接都已解析,即已被链接到的路径替换。命令 realpath
和 readlink
都查找规范路径,但只有 realpath
可以选择获取绝对路径而无需解析符号链接(以及获取各种路径的其他几个选项,绝对路径或相对于某个目录)。
这需要几点说明:
符号链接只有在它们应该链接到的东西已经创建时才能被解析,显然情况并非总是如此。命令 realpath 和 readlink 有解决这个问题的选项。路径上的目录以后可以成为符号链接,这意味着该路径不再是规范的。因此,这个概念是时间(或环境)相关的。即使在理想情况下,当所有符号链接都可以解析时,文件的规范路径可能仍然不止一个,原因有两个:包含文件的分区可能已同时(ro)安装在多个安装点上。可能存在指向文件的硬链接,这意味着该文件本质上存在于几个不同的目录中。
因此,即使使用更严格的 canonical path
定义,文件也可能有多个规范路径。这也意味着限定符 canonical
有点不够用,因为它通常意味着唯一性的概念。
这扩展了对 Bash: retrieve absolute path given relative 上另一个类似问题的回答中对该主题的简短讨论
我的结论是,realpath
比 readlink
设计得更好,也更灵活。 realpath
未涵盖的 readlink
的唯一用途是调用没有返回符号链接值的选项。
realpath
与 readlink
甚至符号链接的任何评论都超出了我的要求。
canonical
正是指相对于一组转换的这种唯一性。但是,例如,/dev/cdrom 是一个非常好的绝对路径,即使它实际上是到 /dev/sr0 的链接。两者都指向同一个设备。我最初的回答指向了一篇相关的网络文章。
如果您只想使用内置函数,最简单的可能是:
find `pwd` -name fileName
只需输入额外的两个单词,这将适用于所有 unix 系统以及 OSX。
-name
之前添加 -maxdepth 1
(假设文件在当前目录中)。没有它,它将与 pwd 的任何子目录中的文件一起使用,但不适用于其他文件。
find
在 pwd
下的目录树之外找不到文件。例如,如果您尝试 find . -name ../existingFile
,它会失败。
dogbane
answer 说明即将发生的事情:
#! /bin/sh
echo "$(cd "$(dirname "$1")"; pwd)/$(basename "$1")"
解释:
该脚本获取相对路径作为参数 "$1" 然后我们获取该路径的 dirname 部分(您可以将 dir 或文件传递给此脚本): dirname "$1" 然后我们 cd "$(dirname "$1") 进入这个相对目录并通过运行 pwd shell 命令获取它的绝对路径之后,我们将 basename 附加到绝对路径: $(basename "$1") 作为最后一步,我们回显它
用自制软件回答
realpath
是最好的答案,但如果您没有安装它,则必须先运行 brew install coreutils
,这将安装 coreutils 以及许多很棒的功能。编写一个自定义函数并导出它是太多的工作和这样的错误风险,这里有两行:
$ brew install coreutils
$ realpath your-file-name.json
在某些情况下,此问题的最佳答案可能会产生误导。想象一下,您要查找其绝对路径的文件位于 $PATH
变量中:
# node is in $PATH variable
type -P node
# /home/user/.asdf/shims/node
cd /tmp
touch node
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 -e
和 readlink -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
对于目录,dirname
被 ../
触发并返回 ./
。
可以修改 nolan6000's function 以解决该问题:
get_abs_filename() {
# $1 : relative filename
if [ -d "${1%/*}" ]; then
echo "$(cd ${1%/*}; pwd)/${1##*/}"
fi
}
.profile
并因此可用于交互使用。
$1
是纯文件名,这将不起作用:get_abs_filename foo
->什么都没有(如果 foo
是一个目录,则为 <current_dir>/foo/foo
)。
这不是问题的答案,而是针对那些编写脚本的人:
echo `cd "$1" 2>/dev/null&&pwd||(cd "$(dirname "$1")";pwd|sed "s|/*\$|/${1##*/}|")`
它正确处理 / .. ./ 等。我似乎也在 OSX 上工作
我在我的系统上放置了以下脚本,当我想快速获取当前目录中文件的完整路径时,我将其称为 bash 别名:
#!/bin/bash
/usr/bin/find "$PWD" -maxdepth 1 -mindepth 1 -name "$1"
我不知道为什么,但是,在 OS X 上,当被脚本“$PWD”调用时,它会扩展为绝对路径。在命令行上调用 find 命令时,它不会。但它做我想要的……享受。
$PWD
始终具有工作目录的绝对路径。此外,find
不会容忍斜杠,因此您没有按要求回答问题。 echo "$PWD/$1"
#! /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.
realpath "foo bar"
-> /home/myname/foo bar
。如果您懒得引用命令行参数,那不是 realpath
的错。
在 Ruby 中获取绝对路径的另一种方法:
realpath() {ruby -e "require 'Pathname'; puts Pathname.new('$1').realpath.to_s";}
使用无参数(当前文件夹)和相对和绝对文件或文件夹路径作为参数。
如果您的脚本可能坚持存在 bash 或 bash 兼容的 shell,则 Alexander Klimetschek 的答案是可以的。它不适用于仅符合 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,或者任何操作数包含字符,则结果由实现定义。
- https://pubs.opengroup.org/onlinepubs/9699919799/utilities/echo.html
因此,每当您想向标准输出写入内容并且不是为了向用户打印消息时,建议使用 printf
,因为 printf
的行为已完全定义。我的函数使用标准输出来传递结果,这不是给用户的消息,因此仅使用 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
中起作用。
嘿伙计们,我知道这是一个旧线程,但我只是将其发布以供其他像我一样访问过此的人参考。如果我正确理解了这个问题,我认为是 locate $filename
命令。它显示提供的文件的绝对路径,但前提是它存在。
locate
是一个按名称搜索文件/路径的实用程序。它可能会完成这项工作,但也可能会找到其他匹配项,并且如果不首先以 root 身份调用 updatedb
就可能找不到现有文件。
realpath
,其中包括:github.com/westonruter/misc-cli-tools/blob/master/realpathcoreutils
公式获得。