How to resolve symbolic links in a shell script
给定绝对或相对路径(在类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"。
1 | readlink -f"$path" |
编者按:以上与GNU
注意:由于GNU coreutils 8.15(2012-01-06),有一个realpath程序可用,比上述程序更不钝,更灵活。它还与同名的freebsd-util兼容。它还包括在两个文件之间生成相对路径的功能。
1 | realpath $path |
[以下由Halloeo-Danorton评论的管理员添加]
对于Mac OS X(至少10.11.x),使用不带
1 | readlink $path |
编者按:这不会递归地解析symlinks,因此不会报告最终目标;例如,给定symlink
根据标准,
来自
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 |
我最喜欢的是
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. |
1 | readlink -e [filepath] |
似乎正是你想要的-它接受一个仲裁路径,解析所有符号链接,并返回"真实"路径。-它是"标准*尼克斯",可能所有系统都已经有了。
把一些给定的解决方案放在一起,知道readlink在大多数系统上都可用,但需要不同的参数,这对我在OSX和Debian上很有效。我不确定BSD系统。可能情况需要是
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)起,它的
它是GNU的
您可以使用
例如,在脚本内,您可以按如下方式使用它来获取运行脚本的真正源目录,并解析符号链接:
1 | trueScriptDir=$(dirname --"$(rreadlink"$0")") |
出于对这个答案的感激,代码被修改了。我还创建了一个基于
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引用了确保内置
What if
unalias orunset and[ are set as aliases or shell functions?
我认为可以肯定地说,除非你在处理一个不可信的恶意环境,担心
函数必须依赖某些东西才能有其原始含义和行为——这是不可能的。像posix这样的shell允许对内置关键字甚至语言关键字进行重新定义,这在本质上是一种安全风险(一般来说,编写偏执代码很困难)。
具体解决您的问题:
该函数依赖于
但是,对shell关键字(
为了确保
因此,除非你可以依靠
因为我在过去的几年中遇到过很多次,而这一次我需要一个可以在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 | cat `mvn` |
在我的系统上打印一个包含以下内容的脚本,这应该是您需要什么的一个很好的提示。
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 | (cd"$DIR"; pwd -P) |
但是,如果它可能是一个文件,那么这将不起作用:
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 |
您可以将
为了解决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##*/}" )} |
请注意,所有的