bash 如何解析shell脚本中的符号链接

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/7665/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-09 17:34:07  来源:igfitidea点击:

How to resolve symbolic links in a shell script

bashshellscriptingsymlink

提问by Greg Hewgill

Given an absolute or relative path (in a Unix-like system), I would like to determine the full path of the target after resolving any intermediate symlinks. Bonus points for also resolving ~username notation at the same time.

给定绝对或相对路径(在类 Unix 系统中),我想在解析任何中间符号链接后确定目标的完整路径。同时解析 ~username 符号的奖励积分。

If the target is a directory, it might be possible to chdir() into the directory and then call getcwd(), but I really want to do this from a shell script rather than writing a C helper. Unfortunately, shells have a tendency to try to hide the existence of symlinks from the user (this is bash on OS X):

如果目标是一个目录,则可以将 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
$

What I want is a function resolve() such that when executed from the tmp directory in the above example, resolve("foo") == "/Users/greg/tmp/bar".

我想要的是一个函数 resolve(),这样在上面的例子中从 tmp 目录执行时,resolve("foo") == "/Users/greg/tmp/bar"。

采纳答案by kauppi

According to the standards, pwd -Pshould return the path with symlinks resolved.

根据标准,pwd -P应返回已解析符号链接的路径。

C function char *getcwd(char *buf, size_t size)from unistd.hshould have the same behaviour.

C 函数char *getcwd(char *buf, size_t size)fromunistd.h应该具有相同的行为。

getcwdpwd

获取密码

回答by pixelbeat

readlink -f "$path"

Editor's note: The above works with GNUreadlinkand FreeBSD/PC-BSD/OpenBSDreadlink, but noton OS X as of 10.11.
GNUreadlinkoffers additional, related options, such as -mfor resolving a symlink whether or not the ultimate target exists.

编者注:以上适用于GNUreadlinkFreeBSD/PC-BSD/OpenBSDreadlink,但不适用于 OS X 10.11。
GNUreadlink提供了额外的相关选项,例如-m无论最终目标是否存在,都可以解析符号链接。

Note since GNU coreutils 8.15 (2012-01-06), there is a realpathprogram available that is less obtuse and more flexible than the above. It's also compatible with the FreeBSD util of the same name. It also includes functionality to generate a relative path between two files.

请注意,从 GNU coreutils 8.15 (2012-01-06) 开始,有一个realpath程序可用,它比上述程序更不笨拙且更灵活。它还与同名的 FreeBSD 实用程序兼容。它还包括在两个文件之间生成相对路径的功能。

realpath $path


[Admin addition below from comment by halloleodanorton]

[以下管理员添加来自Halloleodanorton 的评论]

For Mac OS X (through at least 10.11.x), use readlinkwithout the -foption:

对于 Mac OS X(至少到 10.11.x),readlink不带-f选项使用:

readlink $path

Editor's note: This will not resolve symlinks recursivelyand thus won't report the ultimatetarget; e.g., given symlink athat points to b, which in turn points to c, this will only report b(and won't ensure that it is output as an absolute path).
Use the following perlcommand on OS X to fill the gap of the missing readlink -ffunctionality:
perl -MCwd -le 'print Cwd::abs_path(shift)' "$path"

编者注:这不会递归解析符号链接,因此不会报告最终目标;例如,给定a指向 的符号链接,b它又指向c,这只会报告b(并且不会确保它作为绝对路径输出)。在 OS X 上
使用以下perl命令来填补缺失readlink -f功能的空白:
perl -MCwd -le 'print Cwd::abs_path(shift)' "$path"

回答by tlrobinson

"pwd -P" seems to work if you just want the directory, but if for some reason you want the name of the actual executable I don't think that helps. Here's my solution:

如果您只想要目录,“pwd -P”似乎可以工作,但是如果出于某种原因您想要实际可执行文件的名称,我认为这没有帮助。这是我的解决方案:

#!/bin/bash

# get the absolute path of the executable
SELF_PATH=$(cd -P -- "$(dirname -- "
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.
")" && pwd -P) && SELF_PATH=$SELF_PATH/$(basename -- "
readlink -e [filepath]
") # 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

回答by Gregory

One of my favorites is realpath foo

我的最爱之一是 realpath foo

#!/bin/bash
MY_DIR=$( cd $(dirname $(readlink `[[ $OSTYPE == linux* ]] && echo "-f"` 
# Gets the real path of a link, following all links
myreadlink() { [ ! -h "" ] && echo "" || (local link="$(expr "$(command ls -ld -- "")" : '.*-> \(.*\)$')"; cd $(dirname ); myreadlink "$link" | sed "s|^\([^/].*\)$|$(dirname )/|"); }

# Returns the absolute path to a command, maybe in $PATH (which) or not. If not found, returns the same
whereis() { echo  | sed "s|^\([^/].*/.*\)|$(pwd)/|;s|^\([^/]*\)$|$(which -- )|;s|^$||"; } 

# Returns the realpath of a called command.
whereis_realpath() { local SCRIPT_PATH=$(whereis ); myreadlink ${SCRIPT_PATH} | sed "s|^\([^/].*\)$|$(dirname ${SCRIPT_PATH})/|"; } 
)) ; pwd -P) echo "$MY_DIR"

回答by Chuck Kollars

FILE=$(perl -e "use Cwd qw(abs_path); print abs_path('
DIR=$(perl -e "use Cwd qw(abs_path); use File::Basename; print dirname(abs_path('
(cd "$DIR"; pwd -P)
'))")
')")

seems to be exactly what you're asking for - it accepts an arbirary path, resolves all symlinks, and returns the "real" path - and it's "standard *nix" that likely all systems already have

似乎正是您所要求的 - 它接受任意路径,解析所有符号链接,并返回“真实”路径 - 它是所有系统可能已经拥有的“标准* nix”

回答by hpvw

Putting some of the given solutions together, knowing that readlink is available on most systems, but needs different arguments, this works well for me on OSX and Debian. I'm not sure about BSD systems. Maybe the condition needs to be [[ $OSTYPE != darwin* ]]to exclude -ffrom OSX only.

将一些给定的解决方案放在一起,知道 readlink 在大多数系统上可用,但需要不同的参数,这对我在 OSX 和 Debian 上效果很好。我不确定 BSD 系统。也许条件只需要从 OSX 中[[ $OSTYPE != darwin* ]]排除-f

DIR=$(cd $(dirname "$FILE"); pwd -P); echo "${DIR}/$(readlink "$FILE")"

回答by Keymon

Another way:

其它的办法:

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

回答by Igor Afanasyev

Here's how one can get the actual path to the file in MacOS/Unix using an inline Perl script:

以下是如何使用内联 Perl 脚本在 MacOS/Unix 中获取文件的实际路径:

trueScriptDir=$(dirname -- "$(rreadlink "
#!/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 "##代码##")")
rreadlink() ( # Execute the function in a *subshell* to localize variables and the effect of `cd`.

  target= 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 "$@"
")")

Similarly, to get the directory of a symlinked file:

同样,要获取符号链接文件的目录:

##代码##

回答by Daniel C. Sobral

Is your path a directory, or might it be a file? If it's a directory, it's simple:

您的路径是目录还是文件?如果是目录,很简单:

##代码##

However, if it might be a file, then this won't work:

但是,如果它可能是一个文件,那么这将不起作用:

##代码##

because the symlink might resolve into a relative or full path.

因为符号链接可能会解析为相对或完整路径。

On scripts I need to find the real path, so that I might reference configuration or other scripts installed together with it, I use this:

在脚本上,我需要找到真正的路径,以便我可以参考配置或与它一起安装的其他脚本,我使用这个:

##代码##

You could set SOURCEto any file path. Basically, for as long as the path is a symlink, it resolves that symlink. The trick is in the last line of the loop. If the resolved symlink is absolute, it will use that as SOURCE. However, if it is relative, it will prepend the DIRfor it, which was resolved into a real location by the simple trick I first described.

您可以设置SOURCE为任何文件路径。基本上,只要路径是符号链接,它就会解析该符号链接。诀窍是在循环的最后一行。如果解析的符号链接是绝对的,它将使用它作为SOURCE. 然而,如果它是相对的,它会DIR为它加上,它被我首先描述的简单技巧解析为一个真实的位置。

回答by mklement0

Note: I believe this to be a solid, portable, ready-made solution, which is invariably lengthyfor that very reason.

注意:我相信这是一个可靠的、便携的、现成的解决方案,因此它总是很长

Below is a fully POSIX-compliant script / functionthat is therefore cross-platform(works on macOS too, whose readlinkstill doesn't support -fas of 10.12 (Sierra)) - it uses only POSIX shell language featuresand only POSIX-compliant utility calls.

下面是一个完全兼容 POSIX 的脚本/函数,因此是跨平台的(也适用于 macOS,从 10.12 (Sierra) 开始,它readlink仍然不支持-f)-它仅使用POSIX shell 语言功能和仅使用 POSIX 兼容的实用程序调用.

It is a portable implementation of GNU's readlink -e(the stricter version of readlink -f).

它是GNU 的可移植实现readlink -e(更严格的 GNU版本readlink -f)。

You can run the scriptwith shor source the functionin bash, ksh, and zsh:

您可以运行该脚本sh功能bashkshzsh

For instance, inside a script you can use it as follows to get the running's script true directory of origin, with symlinks resolved:

例如,在脚本中,您可以按如下方式使用它来获取运行脚本的真实原始目录,并解析符号链接:

##代码##

rreadlinkscript / function definition:

rreadlink脚本/函数定义:

The code was adapted with gratitude from this answer.
I've also created a bash-based stand-alone utility version here, which you can install with
npm install rreadlink -g, if you have Node.js installed.

出于对这个答案的感激之情改编了代码。
我还在此处创建了一个bash基于独立的实用程序版本,如果您安装了Node.js ,您可以使用安装它。
npm install rreadlink -g

##代码##

A tangent on security:

安全问题:

jarno, in reference to the function ensuring that builtin commandis not shadowed by an alias or shell function of the same name, asks in a comment:

jarno参考确保内置函数command不被同名别名或 shell 函数遮蔽的函数,在注释中询问:

What if unaliasor unsetand [are set as aliases or shell functions?

如果unaliasunset[被设置为别名或外壳函数怎么办?

The motivation behind rreadlinkensuring that commandhas its original meaning is to use it to bypass (benign) conveniencealiases and functions often used to shadow standard commands in interactive shells, such as redefining lsto include favorite options.

rreadlink确保command具有其原始含义背后的动机是使用它来绕过(良性)便利别名和通常用于隐藏交互式 shell 中的标准命令的函数,例如重新定义ls以包含最喜欢的选项。

I think it's safe to say that unless you're dealing with an untrusted, malicious environment, worrying about unaliasor unset- or, for that matter, while, do, ... - being redefined is not a concern.

我认为可以肯定地说,除非您正在处理不受信任的恶意环境,unalias否则担心或unset- 或者,就此而言,while, do, ... - 被重新定义不是问题。

There is somethingthat the function must rely on to have its original meaning and behavior - there is no way around that.
That POSIX-like shells allow redefinition of builtins and even language keywords is inherentlya security risk (and writing paranoid code is hard in general).

一些函数必须依靠有它的本义和行为-有周围没有办法。
类似于 POSIX 的 shell 允许重新定义内置函数,甚至语言关键字本身就是一个安全风险(并且编写偏执的代码通常很困难)。

To address your concerns specifically:

具体解决您的疑虑:

The function relies on unaliasand unsethaving their original meaning. Having them redefined as shell functionsin a manner that alters their behavior would be a problem; redefinition as an aliasis not necessarily a concern, because quoting(part of) the command name (e.g., \unalias) bypasses aliases.

该功能依赖unaliasunset具有其原始含义。让它们以改变其行为的方式重新定义为shell 函数将是一个问题;重新定义为别名不一定是一个问题,因为引用(部分)命令名称(例如,\unalias)会绕过别名。

However, quoting is notan option for shell keywords(while, for, if, do, ...) and while shell keywords do take precedence over shell functions, in bashand zshaliases have the highest precedence, so to guard against shell-keyword redefinitions you must run unaliaswith their names (although in non-interactivebashshells (such as scripts) aliases are notexpanded by default - only if shopt -s expand_aliasesis explicitly called first).

然而,引用是不是壳的选项关键字whileforifdo,...),并同时外壳的关键字做优先于外壳的功能,在bashzsh别名具有最高的优先级,所以对你必须运行壳关键字重定义后卫unalias与它们的名称(尽管在非交互式bashshell(例如脚本)中,默认情况下不会扩展别名- 仅当shopt -s expand_aliases首先显式调用时)。

To ensure that unalias- as a builtin - has its original meaning, you must use \unseton it first, which requires that unsethave its original meaning:

为了确保unalias- 作为内置 - 具有其原始含义,您必须先使用\unseton 它,这要求unset具有其原始含义:

unsetis a shell builtin, so to ensure that it is invoked as such, you'd have to make sure that it itself is not redefined as a function. While you can bypass an alias form with quoting, you cannot bypass a shell-function form - catch 22.

unset是一个内置的 shell,所以为了确保它被这样调用,你必须确保它本身没有被重新定义为函数。虽然您可以通过引用绕过别名形式,但不能绕过 shell 函数形式 - catch 22。

Thus, unless you can rely on unsetto have its original meaning, from what I can tell, there is no guaranteed way to defend against all malicious redefinitions.

因此,除非你可以依靠unset它的本义,据我所知,没有保证的方法来防御所有恶意的重新定义。