给定绝对或相对路径(在类Unix系统中),我想在解析任何中间符号链接之后确定目标的完整路径。同时解析~username符号的奖励积分。
如果目标是一个目录,那么可以先将chdir()放入该目录,然后调用getcwd(),但我确实希望从shell脚本中执行此操作,而不是编写一个C助手。不幸的是,shell有一种倾向,试图向用户隐藏符号链接的存在(这是OS X上的bash):
1 2 3 4 5 6 7
| $ ls -ld foo bar
drwxr-xr-x 2 greg greg 68 Aug 11 22:36 bar
lrwxr-xr-x 1 greg greg 3 Aug 11 22:36 foo -> bar
$ cd foo
$ pwd
/Users/greg/tmp/foo
$ |
我想要的是一个函数resolve(),这样当在上面的示例中从tmp目录执行时,resolve("foo")="/users/greg/tmp/bar"。
编者按:以上与GNU readlink和freebsd/pc-bsd/openbsd readlink一起工作,但10.11以后不在OS X上工作。GNU readlink提供了额外的相关选项,如-m以解决符号链接,无论最终目标是否存在。
注意:由于GNU coreutils 8.15(2012-01-06),有一个realpath程序可用,比上述程序更不钝,更灵活。它还与同名的freebsd-util兼容。它还包括在两个文件之间生成相对路径的功能。
[以下由Halloeo-Danorton评论的管理员添加]
对于Mac OS X(至少10.11.x),使用不带-f选项的readlink:
编者按:这不会递归地解析symlinks,因此不会报告最终目标;例如,给定symlink a指向b,而该symlink c指向c,这只会报告b,并且不会确保它作为绝对路径输出。在OS X上使用以下perl命令来填补缺少的readlink -f功能的空白:perl -MCwd -le 'print Cwd::abs_path(shift)'"$path"
根据标准,pwd -P应返回已解决符号链接的路径。
来自unistd.h的c函数char *getcwd(char *buf, size_t size)应具有相同的行为。
GETCWD随钻测井
如果您只需要目录,"pwd-p"似乎可以工作,但是如果出于某种原因您需要实际可执行文件的名称,我认为这没有帮助。我的解决方案是:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| #!/bin/bash
# get the absolute path of the executable
SELF_PATH=$(cd -P --"$(dirname --"$0")" && pwd -P) && SELF_PATH=$SELF_PATH/$(basename --"$0")
# resolve symlinks
while [[ -h $SELF_PATH ]]; do
# 1) cd to directory of the symlink
# 2) cd to the directory of where the symlink points
# 3) get the pwd
# 4) append the basename
DIR=$(dirname --"$SELF_PATH")
SYM=$(readlink"$SELF_PATH")
SELF_PATH=$(cd"$DIR" && cd"$(dirname --"$SYM")" && pwd)/$(basename --"$SYM")
done |
我最喜欢的是realpath foo。
1 2 3 4 5
| realpath - return the canonicalized absolute pathname
realpath expands all symbolic links and resolves references to '/./', '/../' and extra '/' characters in the null terminated string named by path and
stores the canonicalized absolute pathname in the buffer of size PATH_MAX named by resolved_path. The resulting path will have no symbolic link, '/./' or
'/../' components. |
似乎正是你想要的-它接受一个仲裁路径,解析所有符号链接,并返回"真实"路径。-它是"标准*尼克斯",可能所有系统都已经有了。
把一些给定的解决方案放在一起,知道readlink在大多数系统上都可用,但需要不同的参数,这对我在OSX和Debian上很有效。我不确定BSD系统。可能情况需要是[[ $OSTYPE != darwin* ]]才能将-f排除在OSX之外。
1 2 3
| #!/bin/bash
MY_DIR=$( cd $(dirname $(readlink `[[ $OSTYPE == linux* ]] && echo"-f"` $0)) ; pwd -P)
echo"$MY_DIR" |
另一种方式:
1 2 3 4 5 6 7 8
| # Gets the real path of a link, following all links
myreadlink() { [ ! -h"$1" ] && echo"$1" || (local link="$(expr"$(command ls -ld --"$1")" : '.*-> \(.*\)$')"; cd $(dirname $1); myreadlink"$link" | sed"s|^\([^/].*\)\$|$(dirname $1)/\1|"); }
# Returns the absolute path to a command, maybe in $PATH (which) or not. If not found, returns the same
whereis() { echo $1 | sed"s|^\([^/].*/.*\)|$(pwd)/\1|;s|^\([^/]*\)$|$(which -- $1)|;s|^$|$1|"; }
# Returns the realpath of a called command.
whereis_realpath() { local SCRIPT_PATH=$(whereis $1); myreadlink ${SCRIPT_PATH} | sed"s|^\([^/].*\)\$|$(dirname ${SCRIPT_PATH})/\1|"; } |
注:我认为这是一个坚实的,可移植的,现成的解决方案,因为这个原因总是很长的。
下面是一个完全符合POSIX的脚本/函数,因此它是跨平台的(也适用于MacOS,从10.12(Sierra)起,它的readlink仍然不支持-f)——它只使用POSIX外壳语言功能,只使用符合POSIX的实用程序调用。
它是GNU的readlink -e的可移植实现(更严格的readlink -f版本)。
您可以使用sh运行脚本,也可以在bash、ksh和zsh中源代码该函数:
例如,在脚本内,您可以按如下方式使用它来获取运行脚本的真正源目录,并解析符号链接:
1
| trueScriptDir=$(dirname --"$(rreadlink"$0")") |
rreadlink脚本/函数定义:
出于对这个答案的感激,代码被修改了。我还创建了一个基于bash的独立实用程序版本,您可以使用如果安装了node.js,则为npm install rreadlink -g。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
| #!/bin/sh
# SYNOPSIS
# rreadlink <fileOrDirPath>
# DESCRIPTION
# Resolves <fileOrDirPath> to its ultimate target, if it is a symlink, and
# prints its canonical path. If it is not a symlink, its own canonical path
# is printed.
# A broken symlink causes an error that reports the non-existent target.
# LIMITATIONS
# - Won't work with filenames with embedded newlines or filenames containing
# the string ' -> '.
# COMPATIBILITY
# This is a fully POSIX-compliant implementation of what GNU readlink's
# -e option does.
# EXAMPLE
# In a shell script, use the following to get that script's true directory of origin:
# trueScriptDir=$(dirname --"$(rreadlink"$0")")
rreadlink() ( # Execute the function in a *subshell* to localize variables and the effect of `cd`.
target=$1 fname= targetDir= CDPATH=
# Try to make the execution environment as predictable as possible:
# All commands below are invoked via `command`, so we must make sure that
# `command` itself is not redefined as an alias or shell function.
# (Note that command is too inconsistent across shells, so we don't use it.)
# `command` is a *builtin* in bash, dash, ksh, zsh, and some platforms do not
# even have an external utility version of it (e.g, Ubuntu).
# `command` bypasses aliases and shell functions and also finds builtins
# in bash, dash, and ksh. In zsh, option POSIX_BUILTINS must be turned on for
# that to happen.
{ \unalias command; \unset -f command; } >/dev/null 2>&1
[ -n"$ZSH_VERSION" ] && options[POSIX_BUILTINS]=on # make zsh find *builtins* with `command` too.
while :; do # Resolve potential symlinks until the ultimate target is found.
[ -L"$target" ] || [ -e"$target" ] || { command printf '%s
'"ERROR: '$target' does not exist.">&2; return 1; }
command cd"$(command dirname --"$target")" # Change to target dir; necessary for correct resolution of target path.
fname=$(command basename --"$target") # Extract filename.
["$fname" = '/' ] && fname='' # !! curiously, `basename /` returns '/'
if [ -L"$fname" ]; then
# Extract [next] target path, which may be defined
# *relative* to the symlink's own directory.
# Note: We parse `ls -l` output to find the symlink target
# which is the only POSIX-compliant, albeit somewhat fragile, way.
target=$(command ls -l"$fname")
target=${target#* -> }
continue # Resolve [next] symlink target.
fi
break # Ultimate target reached.
done
targetDir=$(command pwd -P) # Get canonical dir. path
# Output the ultimate target's canonical path.
# Note that we manually resolve paths ending in /. and /.. to make sure we have a normalized path.
if ["$fname" = '.' ]; then
command printf '%s
'"${targetDir%/}"
elif ["$fname" = '..' ]; then
# Caveat: something like /var/.. will resolve to /private (assuming /var@ -> /private/var), i.e. the '..' is applied
# AFTER canonicalization.
command printf '%s
'"$(command dirname --"${targetDir}")"
else
command printf '%s
'"${targetDir%/}/$fname"
fi
)
rreadlink"$@" |
安全性的切线:
Jarno引用了确保内置command不被同名别名或shell函数隐藏的函数,在注释中问道:
What if unalias or unset and [ are set as aliases or shell functions?
rreadlink背后确保command具有其原始含义的动机是使用它来绕过(良性)便利别名和通常用于在交互shell中隐藏标准命令的函数,例如重新定义ls以包含最喜欢的选项。
我认为可以肯定地说,除非你在处理一个不可信的恶意环境,担心unalias或unset—或者,就此而言,while或do……-被重新定义不是一个问题。
函数必须依赖某些东西才能有其原始含义和行为——这是不可能的。像posix这样的shell允许对内置关键字甚至语言关键字进行重新定义,这在本质上是一种安全风险(一般来说,编写偏执代码很困难)。
具体解决您的问题:
该函数依赖于unalias和unset具有其原始含义。让它们以改变行为的方式重新定义为shell函数是一个问题;重新定义为别名是不必担心,因为引用(部分)命令名(例如,\unalias)会绕过别名。
但是,对shell关键字(while、for、if、do…)来说,引用不是一种选择;虽然shell关键字优先于shell函数,但在bash和zsh别名中,它们的优先级最高,因此为了防止shell关键字重新定义,必须使用它们的n运行unalias。Ames(尽管在非交互的bashshell(如脚本)中,别名在默认情况下不会扩展—仅当明确地首先调用shopt -s expand_aliases时才扩展)。
为了确保unalias作为一个内置物具有其原始含义,您必须首先在其上使用\unset,这要求unset具有其原始含义:
unset是一个shell内置函数,因此为了确保它被这样调用,您必须确保它本身没有被重新定义为函数。虽然可以通过引用绕过别名窗体,但不能绕过shell函数窗体-catch 22。
因此,除非你可以依靠unset来有它的原始含义,从我所知,没有保证的方法来抵御所有恶意的重新定义。
因为我在过去的几年中遇到过很多次,而这一次我需要一个可以在OSX和Linux上使用的纯bash可移植版本,所以我继续写了一个:
活生生的版本生活在这里:
https://github.com/keen99/shell-functions/tree/master/resolve_路径
但是为了这个,这是最新的版本(我觉得它经过了很好的测试……但是我乐于接受反馈!)
对于普通的BourneShell(sh),这可能并不难,但我没有尝试……我太喜欢$funcname了。:)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88
| #!/bin/bash
resolve_path() {
#I'm bash only, please!
# usage: resolve_path
# 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:
1 2
| %% 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 |
使用此函数,它将返回-real-路径:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| %% 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 |
常用的shell脚本通常必须找到它们的"home"目录,即使它们是作为符号链接调用的。因此脚本必须从0美元中找到他们的"真实"位置。
在我的系统上打印一个包含以下内容的脚本,这应该是您需要什么的一个很好的提示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| if [ -z"$M2_HOME" ] ; then
## resolve links - $0 may be a link to maven's home
PRG="$0"
# need this for relative symlinks
while [ -h"$PRG" ] ; do
ls=`ls -ld"$PRG"`
link=`expr"$ls" : '.*-> \(.*\)$'`
if expr"$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG="`dirname"$PRG"`/$link"
fi
done
saveddir=`pwd`
M2_HOME=`dirname"$PRG"`/..
# make it fully qualified
M2_HOME=`cd"$M2_HOME" && pwd` |
1 2 3 4 5 6 7 8 9 10 11
| function realpath {
local r=$1; local t=$(readlink $r)
while [ $t ]; do
r=$(cd $(dirname $r) && cd $(dirname $t) && pwd -P)/$(basename $t)
t=$(readlink $r)
done
echo $r
}
#example usage
SCRIPT_PARENT_DIR=$(dirname $(realpath"$0"))/.. |
试试这个:
1
| cd $(dirname $([ -L $0 ] && readlink -f $0 || echo $0)) |
下面介绍如何使用内联Perl脚本获取macos/unix中文件的实际路径:
1
| FILE=$(perl -e"use Cwd qw(abs_path); print abs_path('$0')") |
同样,要获取符号链接文件的目录:
1
| DIR=$(perl -e"use Cwd qw(abs_path); use File::Basename; print dirname(abs_path('$0'))") |
您的路径是目录还是文件?如果是一个目录,很简单:
但是,如果它可能是一个文件,那么这将不起作用:
1
| DIR=$(cd $(dirname"$FILE"); pwd -P); echo"${DIR}/$(readlink"$FILE")" |
因为符号链接可能解析为相对路径或完整路径。
在脚本上,我需要找到真正的路径,以便引用配置或与之一起安装的其他脚本,我使用此路径:
1 2 3 4 5 6
| SOURCE="${BASH_SOURCE[0]}"
while [ -h"$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink
DIR="$( cd -P"$( dirname"$SOURCE" )" && pwd )"
SOURCE="$(readlink"$SOURCE")"
[[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE" # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located
done |
您可以将SOURCE设置为任何文件路径。基本上,只要路径是symlink,它就解析该symlink。技巧在循环的最后一行。如果解析的符号链接是绝对的,它将使用它作为SOURCE。然而,如果它是相对的,它将为它预先准备好DIR,这是通过我第一次描述的简单技巧解决的,成为一个真实的位置。
为了解决mac的不兼容性,我想出了
1
| echo `php -r"echo realpath('foo');"` |
不是很好,但交叉操作系统
在这里,我介绍了我所认为的跨平台(至少是Linux和MacOS)解决方案,以解决目前对我来说工作良好的问题。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| crosspath()
{
local ref="$1"
if [ -x"$(which realpath)" ]; then
path="$(realpath"$ref")"
else
path="$(readlink -f"$ref" 2> /dev/null)"
if [ $? -gt 0 ]; then
if [ -x"$(which readlink)" ]; then
if [ ! -z"$(readlink"$ref")" ]; then
ref="$(readlink"$ref")"
fi
else
echo"realpath and readlink not available. The following may not be the final path." 1>&2
fi
if [ -d"$ref" ]; then
path="$(cd"$ref"; pwd -P)"
else
path="$(cd $(dirname"$ref"); pwd -P)/$(basename"$ref")"
fi
fi
fi
echo"$path"
} |
这是一台MacOS(只有?)解决方案。可能更适合最初的问题。
1 2 3 4 5 6 7 8 9 10 11 12
| mac_realpath()
{
local ref="$1"
if [[ ! -z"$(readlink"$ref")" ]]; then
ref="$(readlink"$1")"
fi
if [[ -d"$ref" ]]; then
echo"$(cd"$ref"; pwd -P)"
else
echo"$(cd $(dirname"$ref"); pwd -P)/$(basename"$ref")"
fi
} |
我相信这是一个真正和明确的"解决符号链接的方法",不管它是目录还是非目录,使用bash:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| function readlinks {(
set -o errexit -o nounset
declare n=0 limit=1024 link="$1"
# If it's a directory, just skip all this.
if cd"$link" 2>/dev/null
then
pwd -P"$link"
return 0
fi
# Resolve until we are out of links (or recurse too deep).
while [[ -L $link ]] && [[ $n -lt $limit ]]
do
cd"$(dirname --"$link")"
n=$((n + 1))
link="$(readlink --"${link##*/}")"
done
cd"$(dirname --"$link")"
if [[ $n -ge $limit ]]
then
echo"Recursion limit ($limit) exceeded.">&2
return 2
fi
printf '%s/%s
'"$(pwd -P)""${link##*/}"
)} |
请注意,所有的cd和set都发生在一个子壳中。