给定绝对或相对路径(在类Unix系统中),我想在解析任何中间符号链接后确定目标的完整路径。奖励还可以同时解析〜用户名表示法。
如果目标是目录,则可以将chdir()放入目录然后调用getcwd(),但我真的想从shell脚本而不是编写C帮助程序。不幸的是,shell倾向于试图隐藏用户的符号链接(这是OS X上的bash):
$ 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目录执行时,解析(“foo”)==“/ Users / greg / tmp / bar”。
答案 0 :(得分:366)
readlink -f "$path"
编者注:以上内容适用于 GNU readlink
和 FreeBSD / PC-BSD / OpenBSD readlink
,但 10.11中OS X上不
GNU readlink
提供了其他相关选项,例如-m
用于解析符号链接,无论最终目标是否存在。
注意自GNU coreutils 8.15(2012-01-06)以来,有一个 realpath 程序可用,它比上面的更简单,更灵活。它还与同名的FreeBSD util兼容。它还包括在两个文件之间生成相对路径的功能。
realpath $path
对于Mac OS X(至少10.11.x),请使用readlink
而不使用-f
选项:
readlink $path
编者注:这不会递归地解析符号链接 ,因此不会报告终极目标;例如,给定符号链接a
指向b
,而c
指向b
,这只会报告perl
(并且不会确保将其输出为readlink -f
绝对路径)
在OS X上使用以下perl -MCwd -le 'print Cwd::abs_path(shift)' "$path"
命令填补缺少的{{1}}功能的空白:
{{1}}
答案 1 :(得分:86)
答案 2 :(得分:24)
#!/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
答案 3 :(得分:16)
我最喜欢的一个是realpath foo
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.
答案 4 :(得分:10)
readlink -e [filepath]
似乎正是你所要求的 - 它接受一个arbirary路径,解析所有符号链接,并返回“真实”路径 - 它可能是所有系统已经拥有的“标准* nix”
答案 5 :(得分:5)
另一种方式:
# 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|"; }
答案 6 :(得分:5)
将一些给定的解决方案放在一起,知道readlink在大多数系统上都可用,但需要不同的参数,这对我在OSX和Debian上运行良好。我不确定BSD系统。也许条件需要[[ $OSTYPE != darwin* ]]
才能从OSX中排除-f
。
#!/bin/bash
MY_DIR=$( cd $(dirname $(readlink `[[ $OSTYPE == linux* ]] && echo "-f"` $0)) ; pwd -P)
echo "$MY_DIR"
答案 7 :(得分:2)
注意:我认为这是一个可靠的,可移植的,现成的解决方案,由于这个原因,它总是冗长。
以下是完全符合POSIX标准的脚本/功能,因此跨平台(也适用于macOS,其readlink
仍然不支持截至10.12(Sierra)的-f
- 它仅使用POSIX shell language features且仅使用符合POSIX的实用程序调用。
这是GNU的readlink -e
的可移植实现(更严格的readlink -f
版本)。
您可以使用sh
运行脚本或来源bash
中的功能,{ {1}}和ksh
:
例如,在脚本内部,您可以按如下方式使用它来获取正在运行的脚本的真实目录,并解析符号链接:
zsh
trueScriptDir=$(dirname -- "$(rreadlink "$0")")
脚本/功能定义:
代码改编为this answer的感激之情
我还创建了基于rreadlink
的独立实用程序版本here,您可以使用它安装
bash
,如果您安装了Node.js.
npm install rreadlink -g
安全相切:
jarno,参考确保内置#!/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\n' "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\n' "${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\n' "$(command dirname -- "${targetDir}")"
else
command printf '%s\n' "${targetDir%/}/$fname"
fi
)
rreadlink "$@"
没有被同名的别名或shell函数遮蔽的函数,请在注释中提问:
如果将
command
或unalias
和unset
设置为别名或shell函数,该怎么办?
确保[
具有其原始含义的rreadlink
背后的动机是使用它来绕过(良性)便利别名和通常用于影响交互式标准命令的函数shell,例如重新定义command
以包含喜欢的选项。
我认为可以肯定地说,除非您处理不受信任的恶意环境,担心ls
或unalias
- 或者就此而言unset
,{{ 1}},... - 被重新定义不是一个问题。
该函数必须依赖某些东西才能拥有其原始含义和行为 - 没有办法解决这个问题。
类似POSIX的shell允许重新定义内置函数甚至语言关键字本质上存在安全风险(并且编写偏执代码通常很难)。
具体解决您的疑虑:
该函数依赖于while
和do
具有其原始含义。将它们重新定义为 shell函数以改变其行为的方式将是一个问题;重新定义为别名
不一定是一个问题,因为引用(部分)命令名称(例如unalias
)会绕过别名。
但是,引用不是shell 关键字的选项(unset
,\unalias
,while
,{{1}虽然shell关键字确实优先于shell 函数,但在for
和if
别名中具有最高优先级,因此要防止shell-keyword重定义您必须使用其名称运行do
(尽管在非交互式 bash
shell(例如脚本)中,默认情况下不扩展别名 - 仅限如果首先显式调用zsh
。
要确保unalias
- 作为内置 - 具有其原始含义,您必须首先使用bash
,这要求shopt -s expand_aliases
具有其原始含义:
unalias
是一个shell builtin ,所以为了确保它本身被调用,你必须确保它本身没有被重新定义为函数< / em>的。虽然您可以使用引号绕过别名表单,但您无法绕过shell函数表单 - catch 22。
因此,除非你能依靠\unset
具有其原始含义,否则我无法保证能够抵御所有恶意重新定义。
答案 8 :(得分:1)
由于多年来我遇到过这么多次,这次我需要一个可以在OSX和Linux上使用的纯bash便携版本,我继续编写了一个:
活着的版本住在这里:
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
使用此功能,它将返回-real- path:
%% 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
答案 9 :(得分:1)
如果无法使用pwd(例如,从其他位置调用脚本),请使用realpath(带或不带目录名):
$(dirname $(realpath $PATH_TO_BE_RESOLVED))
在通过(多个)符号链接进行调用时或从任何位置直接调用脚本时都可以使用。
答案 10 :(得分:1)
您的路径是目录还是文件?如果是目录,就很简单:
(cd "$DIR"; pwd -P)
但是,如果它可能是文件,则将无法使用:
DIR=$(cd $(dirname "$FILE"); pwd -P); echo "${DIR}/$(readlink "$FILE")"
因为符号链接可能解析为相对路径或完整路径。
在脚本中,我需要找到真实路径,以便引用配置文件或与之一起安装的其他脚本,我使用以下代码:
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
设置为任何文件路径。基本上,只要路径是符号链接,它就会解析该符号链接。诀窍在循环的最后一行。如果解析的符号链接是绝对的,它将用作SOURCE
。但是,如果它是相对的,它将在DIR
之前,通过我首先描述的简单技巧将其解析为真实位置。
答案 11 :(得分:1)
以下是如何使用内联Perl脚本在MacOS / Unix中获取文件的实际路径:
FILE=$(perl -e "use Cwd qw(abs_path); print abs_path('$0')")
同样,要获取符号链接文件的目录:
DIR=$(perl -e "use Cwd qw(abs_path); use File::Basename; print dirname(abs_path('$0'))")
答案 12 :(得分:1)
试试这个:
cd $(dirname $([ -L $0 ] && readlink -f $0 || echo $0))
答案 13 :(得分:1)
要解决Mac不兼容问题,我想出了
echo `php -r "echo realpath('foo');"`
不太好,但跨越OS
答案 14 :(得分:1)
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"))/..
答案 15 :(得分:1)
常见的shell脚本通常必须找到它们的“home”目录,即使它们被作为符号链接调用。因此,剧本必须从$ 0找到他们的“真实”位置。
cat `mvn`
我系统上的会打印一个包含以下内容的脚本,这应该是您需要的一个很好的提示。
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`
答案 16 :(得分:0)
我相信这是使用Bash解析符号链接的真正和明确的“方法”,无论是目录还是非目录:
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\n' "$(pwd -P)" "${link##*/}"
)}
请注意,所有cd
和set
内容都在子shell中进行。
答案 17 :(得分:0)
在这里,我提出我认为是一种跨平台(至少是Linux和macOS)解决方案,以解决目前对我来说很有效的答案。
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(仅?)解决方案。可能更适合原始问题。
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
}
答案 18 :(得分:0)
我的回答Bash: how to get real path of a symlink?
但是简而言之在脚本中非常方便:
script_home=$( dirname $(realpath "$0") )
echo Original script home: $script_home
这些是GNU coreutils的一部分,适用于Linux系统。
要测试所有内容,我们将符号链接放到/ home / test2 /中,修改一些其他内容,然后从根目录运行/调用它:
/$ /home/test2/symlink
/home/test
Original script home: /home/test
哪里
Original script is: /home/test/realscript.sh
Called script is: /home/test2/symlink