基于文件位置而不是当前工作目录的相对路径

给出:

some.txt
dir
|-cat.sh

Cat.sh 拥有以下内容:

cat ../some.txt

然后在 dir中运行 ./cat.sh可以正常工作,而在与 dir相同的级别上运行 ./dir/cat.sh则不能。我希望这是由于不同的工作目录。是否有一种简单的方法使路径 ../some.txt相对于 cat.sh的位置?

167921 次浏览

您需要做的是获取脚本的绝对路径(可以通过 ${BASH_SOURCE[0]}获得) ,然后使用它获取脚本开始处的父目录和 cd

#!/bin/bash
parent_path=$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P )


cd "$parent_path"
cat ../some.text

这将使您的 shell 脚本独立于您从哪里调用它。每次运行它时,就好像在 dir中运行 ./cat.sh一样。

注意,这个脚本只有在直接调用脚本的情况下才能工作(例如,不能通过符号链接) ,否则查找脚本的当前位置会变得有点棘手)

只要一行就行了。

cat "`dirname $0`"/../some.txt

@ Martin Konecny 的回答 提供了一个有效的解决方案,但是——正如他所提到的——它只有在实际的脚本没有通过驻留在 不同的目录中的 Symlink调用的情况下才有效。

这个答案涵盖了这种情况: 当通过一个 < em > symlink 或者甚至一个 < em > symlinks 链调用脚本时,该解决方案也可以工作:


Linux/GNU readlink 解决方案:

如果您的脚本只需要在 Linux上运行,或者您知道 GNU readlink$PATH中,那么使用 readlink -f,它可以方便地解析到其 终极的目标的符号链接:

 scriptDir=$(dirname -- "$(readlink -f -- "$BASH_SOURCE")")

请注意,GNU readlink有3个相关的选项来解析到其最终目标的完整路径的符号链接: -f(--canonicalize)、 -e(--canonicalize-existing)和 -m(--canonicalize-missing)-参见 man readlink
由于目标根据定义存在于这个场景中,所以可以使用3个选项中的任何一个; 我在这里选择了 -f,因为它是最著名的一个。


多(类 Unix)平台解决方案 (包括具有 只有 POSIX实用程序集的平台) :

如果您的脚本必须在以下任何平台上运行:

  • 有一个 readlink实用程序,但是缺少 -f选项(在解析到其最终目标的符号链接的 GNU 意义上)-例如,MacOS

    • MacOS 使用 readlink的 BSD 实现的 旧版本; 请注意,FreeBSD/PC-BSD 的最新版本支持 -f
  • 甚至没有 readlink,但有 POSIX 兼容实用程序-例如,HP-UX(谢谢,@Charles Duffy)。

下面的解决方案受到 https://stackoverflow.com/a/1116890/45375的启发, 定义了 辅助外壳函数,rreadlink(),它在循环中解析给定的符号链接到它的最终目标-这个函数是 实际上是 GNU ABC1的 -e选项的 POSIX 兼容实现,也就是 类似于 -f选项,只不过最终目标必须 < em > be

注意 : 该函数是一个 bash函数,并且仅在使用了 POSIX 兼容选项的 POSIX 实用程序时才兼容 POSIX。有关使用符合 POSIX 的 shell 代码(对于 /bin/sh)编写的此函数的 本身版本,请参见 给你

  • 如果 readlink可用,则使用它(没有选项)——在大多数现代平台上都是如此。

  • 否则,将解析 ls -l的输出,这是确定符号链接目标的唯一符合 POSIX 的方法。
    警告 : 如果文件名或路径包含字面子字符串 ->,这将中断-但这是不太可能的。
    (请注意,缺少 readlink的平台仍然可以提供其他非 POSIX 方法来解析符号链接; 例如,@Charles Duffy 提到 HP-UX 的 find实用程序支持 %l格式的 char。为了简洁起见,函数不会尝试检测这种情况。)

  • 下面函数的一个可安装的 效用(脚本)形式(附加功能)可以找到作为 Npm 注册表中的 rreadlink; 在 Linux 和 macOS 上,安装它与 [sudo] npm install -g rreadlink; 在其他平台(假设他们有 bash) ,遵循 手动安装说明书

如果参数是符号链接,则返回最终目标的规范路径; 否则,返回参数自己的规范路径。

#!/usr/bin/env bash


# Helper function.
rreadlink() ( # execute function in a *subshell* to localize the effect of `cd`, ...


local target=$1 fname targetDir readlinkexe=$(command -v readlink) CDPATH=


# Since we'll be using `command` below for a predictable execution
# environment, we make sure that it has its original meaning.
{ \unalias command; \unset -f command; } &>/dev/null


while :; do # Resolve potential symlinks until the ultimate target is found.
[[ -L $target || -e $target ]] || { command printf '%s\n' "$FUNCNAME: 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 is defined
# relative to the symlink's own directory.
if [[ -n $readlinkexe ]]; then # Use `readlink`.
target=$("$readlinkexe" -- "$fname")
else # `readlink` utility not available.
# Parse `ls -l` output, which, unfortunately, is the only POSIX-compliant
# way to determine a symlink's target. Hypothetically, this can break with
# filenames containig literal ' -> ' and embedded newlines.
target=$(command ls -l -- "$fname")
target=${target#* -> }
fi
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
)
                

# Determine ultimate script dir. using the helper function.
# Note that the helper function returns a canonical path.
scriptDir=$(dirname -- "$(rreadlink "$BASH_SOURCE")")