bash 基于文件位置而不是当前工作目录的相对路径
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/24112727/
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
Relative paths based on file location instead of current working directory
提问by ben
Given:
鉴于:
some.txt
dir
|-cat.sh
With cat.sh having the content:
cat.sh 具有以下内容:
cat ../some.txt
Then running ./cat.sh
inside dir
works fine while running ./dir/cat.sh
on the same level as dir
does not. I expect this to be due the different working directories. Is there an easy was to make the path ../some.txt
relative to the location of cat.sh
?
然后在./cat.sh
内部运行时dir
可以正常运行./dir/cat.sh
,而在同一级别上运行dir
时却没有。我希望这是由于不同的工作目录。是否有一种简单的方法可以使路径../some.txt
相对于 的位置cat.sh
?
回答by Martin Konecny
What you want to do is get the absolute path of the script (available via ${BASH_SOURCE[0]}
) and then use this to get the parent directory and cd
to it at the beginning of the script.
您想要做的是获取脚本的绝对路径(可通过${BASH_SOURCE[0]}
),然后使用它来获取父目录并cd
在脚本的开头访问它。
#!/bin/bash
parent_path=$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P )
cd "$parent_path"
cat ../some.text
This will make your shell script work independent of where you invoke it from. Each time you run it, it will be as if you were running ./cat.sh
inside dir
.
这将使您的 shell 脚本独立于您从何处调用它。每次运行它,就像在./cat.sh
里面运行一样dir
。
Note that this script only works if you're invoking the script directly (i.e. not via a symlink), otherwise the finding the current location of the script gets a little more tricky)
请注意,此脚本仅在您直接调用脚本(即不通过符号链接)时才有效,否则查找脚本的当前位置会变得更加棘手)
回答by mklement0
@Martin Konecny's answerprovides the correct answer, but - as he mentions - it only works if the actual script is not invoked through a symlinkresiding in a different directory.
@Martin Konecny 的回答提供了正确的答案,但是 - 正如他所提到的 - 只有当实际脚本不是通过驻留在不同目录中的符号链接调用时才有效。
This answer covers that case: a solution that also works when the script is invoked through a symlinkor even a chain of symlinks:
这个答案涵盖了这种情况:当通过符号链接甚至符号链接链调用脚本时也可以使用的解决方案:
Linux / GNU readlink
solution:
Linux/GNUreadlink
解决方案:
If your script needs to run on Linuxonly or you know that GNU readlink
is in the $PATH
, use readlink -f
, which conveniently resolves a symlink to its ultimatetarget:
如果您的脚本只需要在Linux上运行,或者您知道GNUreadlink
在 中$PATH
,请使用readlink -f
,它可以方便地将符号链接解析到其最终目标:
scriptDir=$(dirname -- "$(readlink -f -- "$BASH_SOURCE")")
Note that GNU readlink
has 3 related options for resolving a symlink to its ultimate target's full path: -f
(--canonicalize
), -e
(--canonicalize-existing
), and -m
(--canonicalize-missing
) - see man readlink
.
Since the target by definition exists in this scenario, any of the 3 options can be used; I've chosen -f
here, because it is the most well-known one.
需要注意的是GNUreadlink
具有用于消除符号链接到其最终目标的完整路径3级相关的选项:-f
(--canonicalize
), (-e
)--canonicalize-existing
和-m
(--canonicalize-missing
) -见man readlink
。
由于此场景中存在定义的目标,因此可以使用 3 个选项中的任何一个;我选择-f
这里,因为它是最知名的。
Multi-(Unix-like-)platform solution(including platforms with a POSIX-onlyset of utilities):
多(类 Unix)平台解决方案(包括具有仅 POSIX实用程序集的平台):
If your script must run on any platform that:
如果您的脚本必须在以下任何平台上运行:
has a
readlink
utility, but lacks the-f
option (in the GNU sense of resolving a symlink to its ultimate target) - e.g., macOS.- macOS uses an older versionof the BSD implementation of
readlink
; note that recent versions of FreeBSD/PC-BSD dosupport-f
.
- macOS uses an older versionof the BSD implementation of
does not even have
readlink
, but has POSIX-compatibleutilities - e.g., HP-UX(thanks, @Charles Duffy).
有一个
readlink
实用程序,但缺乏-f
选项(在 GNU 意义上将符号链接解析为其最终目标) - 例如,macOS。- macOS 使用旧版本的 BSD 实现
readlink
;请注意,最新版本的 FreeBSD/PC-BSD确实支持-f
.
- macOS 使用旧版本的 BSD 实现
甚至没有
readlink
,但具有与POSIX 兼容的实用程序 - 例如,HP-UX(谢谢,@Charles Duffy)。
The following solution, inspired by https://stackoverflow.com/a/1116890/45375,
defines helper shell function, rreadlink()
, which resolves a given symlink to its ultimate target in a loop - this function is in effect a POSIX-compliant implementation of GNU readlink
's -e
option, which is similar to the -f
option, except that the ultimate target must exist.
以下解决方案受https://stackoverflow.com/a/1116890/45375 的启发,定义了辅助 shell 函数 ,rreadlink()
该函数将给定的符号链接解析为循环中的最终目标 - 该函数实际上是符合 POSIX 的实现GNUreadlink
的-e
选项,与-f
选项类似,只是最终目标必须存在。
Note: The function is a bash
function, and is POSIX-compliant only in the sense that only POSIX utilities with POSIX-compliant options are used. For a version of this function that is itselfwritten in POSIX-compliant shell code (for /bin/sh
), see here.
注意:该函数是一个bash
函数,并且仅在仅使用具有 POSIX 兼容选项的 POSIX 实用程序的意义上才符合 POSIX。有关此函数本身以符合 POSIX 标准的 shell 代码(用于/bin/sh
)编写的版本,请参见此处。
If
readlink
is available, it is used (without options) - true on most modern platforms.Otherwise, the output from
ls -l
is parsed, which is the only POSIX-compliant way to determine a symlink's target.
Caveat: this will break if a filename or path contains the literal substring->
- which is unlikely, however.
(Note that platforms that lackreadlink
may still provide other, non-POSIX methods for resolving a symlink; e.g., @Charles Duffy mentions HP-UX'sfind
utility supporting the%l
format char. with its-printf
primary; in the interest of brevity the function does NOT try to detect such cases.)An installable utility(script) form of the function below (with additional functionality) can be found as
rreadlink
in the npm registry; on Linux and macOS, install it with[sudo] npm install -g rreadlink
; on other platforms (assuming they havebash
), follow the manual installation instructions.
如果
readlink
可用,则使用(不带选项) - 在大多数现代平台上为 true。否则,将
ls -l
解析来自的输出,这是确定符号链接目标的唯一符合 POSIX 的方法。
警告:如果文件名或路径包含文字子字符串->
,这将中断- 然而,这不太可能。
(请注意,缺少的平台readlink
可能仍会提供其他非 POSIX 方法来解析符号链接;例如,@Charles Duffy 提到 HP-UX 的find
实用程序支持%l
格式 char. 及其-printf
主要格式;为了简洁起见,该函数不会尝试检测此类情况。)可以在 npm 注册表中找到以下函数的可安装实用程序(脚本)形式(具有附加功能);在 Linux 和 macOS 上,安装它;在其他平台上(假设有),请按照手动安装说明进行操作。
rreadlink
[sudo] npm install -g rreadlink
bash
If the argument is a symlink, the ultimate target's canonical path is returned; otherwise, the argument's own canonical path is returned.
如果参数是符号链接,则返回最终目标的规范路径;否则,返回参数自己的规范路径。
#!/usr/bin/env bash
# Helper function.
rreadlink() ( # execute function in a *subshell* to localize the effect of `cd`, ...
local target= 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")")
回答by Nancy
Just one line will be OK.
只要一行就可以了。
cat "`dirname ##代码##`"/../some.txt